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
//這個構造出來的分頁物件不具備排序功能
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
還有一個內部類Order
,Order
有有兩個比較重要的屬性Sort.Direction
和property
,第一個用來確定排序的方向,第二個就是排序的屬性。
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語句來查詢,這塊內容和具體的封裝實現將會在下一章節介紹。