SpringBoot第二讲 利用Spring Data JPA实现数据库的访问(二)_分页和JpaSpecificationExecutor接口介绍

来源:互联网 发布:商务合作意向书 知乎 编辑:程序博客网 时间:2024/05/03 17:48

我们继续研究spring jpa data,首先看看分页和排序的实现,在原来的代码中,我们如果希望实现分页,首先得创建一个Pager的对象,在这个对象中记录total(总数),totalPager(总页数),pageSize(每页多少条记录),pageIndex(当前第几页),offset(查询时的offset),在spring Data JPA中实现分页需要用到三个接口 
- PagingAndSortingRepository 
- Pageable 
- Page

PagingAndSortingRepository是spring data jpa实现分页的工厂,用法和Repository完全一致,先看看源码

@NoRepositoryBeanpublic interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {    Iterable<T> findAll(Sort var1);    Page<T> findAll(Pageable var1);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第二个findAll方法就是实现分页的方法,参数是Pageable类型,同参数传入当前的分页对象(如:第几页,每页多少条记录,排序信息等),查询完成之后会返回一个Page的对象。Page对象中就存储了所有的分页信息。Pageable的源码如下

public interface Pageable {    int getPageNumber();    int getPageSize();    int getOffset();    Sort getSort();    Pageable next();    Pageable previousOrFirst();    Pageable first();    boolean hasPrevious();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Pageable是一个接口,它的实现类是PageRequest,PageRequest有三个构造方法

//这个构造出来的分页对象不具备排序功能public PageRequest(int page, int size) {    this(page, size, (Sort)null);}//Direction和properties用来做排序操作public PageRequest(int page, int size, Direction direction, String... properties) {    this(page, size, new Sort(direction, properties));}//自定义一个排序的操作public PageRequest(int page, int size, Sort sort) {    super(page, size);    this.sort = sort;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Page实现了一个Slice的接口,通过这个接口获取排序之后的各个数值,这些方法都比较直观,通过名称就差不多知道该是什么样的一个操作了,大家可以自行查阅一下Page和Slice的源码,这里就不列出了。

接下实现以下分页的操作, 创建一个StudentPageRepository来实现分页操作。

public interface StudentPageRepository extends PagingAndSortingRepository<Student,Integer> {    Page<Student> findByAge(int age, Pageable pageable);}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

虽然PagingAndSortingRepository接口中只有findAll方法,但是我们依然可以使用Repository中的衍生查询,我们只要把Pageable放到最后一个参数即可。测试代码

@Testpublic void testPage() {    //显示第1页每页显示3条    PageRequest pr = new PageRequest(1,3);    //根据年龄进行查询    Page<Student> stus = studentPageRepository.findByAge(22,pr);    Assert.assertEquals(2,stus.getTotalPages());    Assert.assertEquals(6,stus.getTotalElements());    Assert.assertEquals(1,stus.getNumber());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

分页的方法非常的简单,下面我们来实现一下排序的操作,排序和分页类似,我们需要传递一个Sort对象进去,Sort是一排序类,首先有一个内部枚举对象Direction,Direction中有两个值ASC和DESC分别用来确定升序还是降序,Sort还有一个内部类OrderOrder有有两个比较重要的属性Sort.Directionproperty,第一个用来确定排序的方向,第二个就是排序的属性。

Sort有如下几个构造函数

//可以输入多个Sort.Order对象,在进行多个值排序时有用public Sort(Sort.Order... orders)//和上面的方法一样,无非把多个参数换成了一个Listpublic Sort(List<Sort.Order> orders)//当排序方向固定时,使用这个比较方便,第一个参数是排序方向,第二个开始就是排序的字段,还有一个方法第二个参数是list,原理相同public Sort(Sort.Direction direction, String... properties)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看看排序的代码

public interface StudentPageRepository extends PagingAndSortingRepository<Student,Integer> {    Page<Student> findByAge(int age, Pageable pageable);    List<Student> findByAge(int age, Sort sort);}//排序的实现代码@Testpublic void testSort() {    //设置排序方式为name降序    List<Student> stus = studentPageRepository.findByAge(22            ,new Sort(Sort.Direction.DESC,"name"));    Assert.assertEquals(5,stus.get(0).getId());    //设置排序以name和address进行升序    stus = studentPageRepository.findByAge(22            ,new Sort(Sort.Direction.ASC,"name","address"));    Assert.assertEquals(8,stus.get(0).getId());    //设置排序方式以name升序,以address降序    Sort sort = new Sort(                    new Sort.Order(Sort.Direction.ASC,"name"),                    new Sort.Order(Sort.Direction.DESC,"address"));    stus = studentPageRepository.findByAge(22,sort);    Assert.assertEquals(7,stus.get(0).getId());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

如果希望在分页的时候进行排序,一样也非常容易,看一下下面PageReques的构造函数

public PageRequest(int page, int size, Direction direction, String... properties) {    this(page, size, new Sort(direction, properties));}public PageRequest(int page, int size, Sort sort) {    super(page, size);    this.sort = sort;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看到这里我相信大家已经会各种排序操作了,这里就不演示了,但是在实际的开发中我们还需要对排序和分页操作进行一下封装,让操作更方便一些,这个话题我们在后面的章节再来详细介绍。 
Spring data jpa 在PagingAndSortingRepository接口下还提供了一个JpaRepository接口,该接口封装了更常用的一些方法,使用方式都类似,如果将来在实现的过程中没有特殊的需求(如:不希望公开所有接口方法之类的需求),一般都继承JPARepository来操作。

Spring Data Jpa同样提供了类似Hibernated 的Criteria的查询方式,要使用这种方式只要继承JpaSpecificationExecutor,该接口提供了如下一些方法

T findOne(Specification<T> var1);List<T> findAll(Specification<T> var1);Page<T> findAll(Specification<T> var1, Pageable var2);List<T> findAll(Specification<T> var1, Sort var2);long count(Specification<T> var1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

该接口通过Specification来定义查询条件,很多朋友可能使用的方式都是基于SQL的,对这种方式可能不太习惯,在下一讲中将会对Specification进行一下封装,让查询操作变得更加的简单方便。这里先简单看一下示例。

@Test   public void testSpecificaiton() {       List<Student> stus = studentSpecificationRepository.findAll(new Specification<Student>() {           @Override           public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {               //root.get("address")表示获取address这个字段名称,like表示执行like查询,%zt%表示值               Predicate p1 = criteriaBuilder.like(root.get("address"), "%zt%");               Predicate p2 = criteriaBuilder.greaterThan(root.get("id"),3);               //将两个查询条件联合起来之后返回Predicate对象               return criteriaBuilder.and(p1,p2);           }       });       Assert.assertEquals(2,stus.size());       Assert.assertEquals("oo",stus.get(0).getName());   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用Specification的要点就是CriteriaBuilder,通过这个对象来创建条件,之后返回一个Predicate对象。这个对象中就有了相应的查询需求,我们同样可以定义多个Specification,之后通过Specifications对象将其连接起来。以下是一个非常典型的应用

@Testpublic void testSpecificaiton2() {//第一个Specification定义了两个or的组合Specification<Student> s1 = new Specification<Student>() {    @Override    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {        Predicate p1 = criteriaBuilder.equal(root.get("id"),"2");        Predicate p2 = criteriaBuilder.equal(root.get("id"),"3");        return criteriaBuilder.or(p1,p2);    }};//第二个Specification定义了两个or的组合Specification<Student> s2 = new Specification<Student>() {    @Override    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {        Predicate p1 = criteriaBuilder.like(root.get("address"),"zt%");        Predicate p2 = criteriaBuilder.like(root.get("name"),"foo%");        return criteriaBuilder.or(p1,p2);    }};//通过Specifications将两个Specification连接起来,第一个条件加where,第二个是andList<Student> stus = studentSpecificationRepository.findAll(Specifications.where(s1).and(s2));    Assert.assertEquals(1,stus.size());    Assert.assertEquals(3,stus.get(0).getId());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这个代码生成的sql是select * from t_student where (id=2 or id=3) and (address like 'zt%' and name like 'foo%'),这其实是一个非常典型的应用,但是相信大家已经发现这个操作实在是太繁杂了,所以个人认为Specification这个方案其实就是为了让我们对其进行封装,而不是直接使用的。

另外在toPredicate中还有一个CriteriaQuery的参数,这个对象提供了更多有用的查询,如分组之类的,可以使用该对象组成复杂的SQL语句来查询,这块内容和具体的封装实现将会在下一章节介绍。

1 0