1. 程式人生 > >SpringBoot第二講利用Spring Data JPA實現資料庫的訪問(二)_分頁和JpaSpecificationExecutor介面介紹

SpringBoot第二講利用Spring Data JPA實現資料庫的訪問(二)_分頁和JpaSpecificationExecutor介面介紹

我們繼續研究spring jpa data,首先看看分頁和排序的實現,在原來的程式碼中,我們如果希望實現分頁,首先得建立一個Pager的物件,在這個物件中記錄total(總數),totalPager(總頁數),pageSize(每頁多少條記錄),pageIndex(當前第幾頁),offset(查詢時的offset),在Spring Data JPA中實現分頁需要用到三個介面
- PagingAndSortingRepository
- Pageable
- Page

PagingAndSortingRepository是spring data jpa實現分頁的工廠,用法和Repository完全一致,先看看原始碼

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort var1);

    Page<T> findAll(Pageable var1);
}

第二個findAll方法就是實現分頁的方法,引數是Pageable型別,同參數傳入當前的分頁物件(如:第幾頁,每頁多少條記錄,排序資訊等),查詢完成之後會返回一個Page

的物件。Page物件中就儲存了所有的分頁資訊。Pageable的原始碼如下

public interface Pageable {
    int getPageNumber();

    int getPageSize();

    int getOffset();

    Sort getSort();

    Pageable next();

    Pageable previousOrFirst();

    Pageable first();

    boolean hasPrevious();
}

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;
}

Page實現了一個Slice的介面,通過這個介面獲取排序之後的各個數值,這些方法都比較直觀,通過名稱就差不多知道該是什麼樣的一個操作了,大家可以自行查閱一下Page和Slice的原始碼,這裡就不列出了。

接下實現以下分頁的操作, 建立一個StudentPageRepository來實現分頁操作。

public interface StudentPageRepository extends PagingAndSortingRepository<Student,Integer> {

    Page<Student> findByAge(int age, Pageable pageable);
}

雖然PagingAndSortingRepository介面中只有findAll方法,但是我們依然可以使用Repository中的衍生查詢,我們只要把Pageable放到最後一個引數即可。測試程式碼

@Test
public 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());
}

分頁的方法非常的簡單,下面我們來實現一下排序的操作,排序和分頁類似,我們需要傳遞一個Sort物件進去,Sort是一排序類,首先有一個內部列舉物件Direction,Direction中有兩個值ASC和DESC分別用來確定升序還是降序,Sort還有一個內部類OrderOrder有有兩個比較重要的屬性Sort.Directionproperty,第一個用來確定排序的方向,第二個就是排序的屬性。

Sort有如下幾個建構函式

//可以輸入多個Sort.Order物件,在進行多個值排序時有用
public Sort(Sort.Order... orders)
//和上面的方法一樣,無非把多個引數換成了一個List
public Sort(List<Sort.Order> orders)
//當排序方向固定時,使用這個比較方便,第一個引數是排序方向,第二個開始就是排序的欄位,還有一個方法第二個引數是list,原理相同
public Sort(Sort.Direction direction, String... properties)

看看排序的程式碼

public interface StudentPageRepository extends PagingAndSortingRepository<Student,Integer> {
    Page<Student> findByAge(int age, Pageable pageable);
    List<Student> findByAge(int age, Sort sort);
}

//排序的實現程式碼
@Test
public 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());
}

如果希望在分頁的時候進行排序,一樣也非常容易,看一下下面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;
}

看到這裡我相信大家已經會各種排序操作了,這裡就不演示了,但是在實際的開發中我們還需要對排序和分頁操作進行一下封裝,讓操作更方便一些,這個話題我們在後面的章節再來詳細介紹。
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);

該介面通過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());
   }

使用Specification的要點就是CriteriaBuilder,通過這個物件來建立條件,之後返回一個Predicate物件。這個物件中就有了相應的查詢需求,我們同樣可以定義多個Specification,之後通過Specifications物件將其連線起來。以下是一個非常典型的應用

@Test
public 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,第二個是and
List<Student> stus = studentSpecificationRepository.findAll(Specifications.where(s1).and(s2));

    Assert.assertEquals(1,stus.size());
    Assert.assertEquals(3,stus.get(0).getId());
}

這個程式碼生成的sql是select * from t_student where (id=2 or id=3) and (address like 'zt%' and name like 'foo%'),這其實是一個非常典型的應用,但是相信大家已經發現這個操作實在是太繁雜了,所以個人認為Specification這個方案其實就是為了讓我們對其進行封裝,而不是直接使用的。

另外在toPredicate中還有一個CriteriaQuery的引數,這個物件提供了更多有用的查詢,如分組之類的,可以使用該物件組成複雜的SQL語句來查詢,這塊內容和具體的封裝實現將會在下一章節介紹。