我們繼續(xù)研究spring jpa data,首先看看分頁和排序的實現(xiàn),在原來的代碼中,我們?nèi)绻M麑崿F(xiàn)分頁,首先得創(chuàng)建一個Pager的對象,在這個對象中記錄total(總數(shù)),totalPager(總頁數(shù)),pageSize(每頁多少條記錄),pageIndex(當前第幾頁),offset(查詢時的offset),在Spring Data JPA中實現(xiàn)分頁需要用到三個接口
- PagingAndSortingRepository
- Pageable
- Page
PagingAndSortingRepository是spring data jpa實現(xiàn)分頁的工廠,用法和Repository完全一致,先看看源碼
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
第二個findAll方法就是實現(xiàn)分頁的方法,參數(shù)是Pageable類型,同參數(shù)傳入當前的分頁對象(如:第幾頁,每頁多少條記錄,排序信息等),查詢完成之后會返回一個Page的對象。Page對象中就存儲了所有的分頁信息。Pageable的源碼如下
public interface Pageable {
int getPageNumber();
int getPageSize();
int getOffset();
Sort getSort();
Pageable next();
Pageable previousOrFirst();
Pageable first();
boolean hasPrevious();
}
Pageable是一個接口,它的實現(xiàn)類是PageRequest,PageRequest有三個構(gòu)造方法
//這個構(gòu)造出來的分頁對象不具備排序功能
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實現(xiàn)了一個Slice的接口,通過這個接口獲取排序之后的各個數(shù)值,這些方法都比較直觀,通過名稱就差不多知道該是什么樣的一個操作了,大家可以自行查閱一下Page和Slice的源碼,這里就不列出了。
接下實現(xiàn)以下分頁的操作, 創(chuàng)建一個StudentPageRepository來實現(xiàn)分頁操作。
public interface StudentPageRepository extends PagingAndSortingRepository<Student,Integer> {
Page<Student> findByAge(int age, Pageable pageable);
}
雖然PagingAndSortingRepository接口中只有findAll方法,但是我們依然可以使用Repository中的衍生查詢,我們只要把Pageable放到最后一個參數(shù)即可。測試代碼
@Test
public void testPage() {
//顯示第1頁每頁顯示3條
PageRequest pr = new PageRequest(1,3);
//根據(jù)年齡進行查詢
Page<Student> stus = studentPageRepository.findByAge(22,pr);
Assert.assertEquals(2,stus.getTotalPages());
Assert.assertEquals(6,stus.getTotalElements());
Assert.assertEquals(1,stus.getNumber());
}
分頁的方法非常的簡單,下面我們來實現(xiàn)一下排序的操作,排序和分頁類似,我們需要傳遞一個Sort對象進去,Sort是一排序類,首先有一個內(nèi)部枚舉對象Direction,Direction中有兩個值ASC和DESC分別用來確定升序還是降序,Sort還有一個內(nèi)部類Order,Order有有兩個比較重要的屬性Sort.Direction和property,第一個用來確定排序的方向,第二個就是排序的屬性。
Sort有如下幾個構(gòu)造函數(shù)
//可以輸入多個Sort.Order對象,在進行多個值排序時有用
public Sort(Sort.Order... orders)
//和上面的方法一樣,無非把多個參數(shù)換成了一個List
public Sort(List<Sort.Order> orders)
//當排序方向固定時,使用這個比較方便,第一個參數(shù)是排序方向,第二個開始就是排序的字段,還有一個方法第二個參數(shù)是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);
}
//排序的實現(xiàn)代碼
@Test
public void testSort() {
//設(shè)置排序方式為name降序
List<Student> stus = studentPageRepository.findByAge(22
,new Sort(Sort.Direction.DESC,"name"));
Assert.assertEquals(5,stus.get(0).getId());
//設(shè)置排序以name和address進行升序
stus = studentPageRepository.findByAge(22
,new Sort(Sort.Direction.ASC,"name","address"));
Assert.assertEquals(8,stus.get(0).getId());
//設(shè)置排序方式以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的構(gòu)造函數(shù)
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;
}
看到這里我相信大家已經(jīng)會各種排序操作了,這里就不演示了,但是在實際的開發(fā)中我們還需要對排序和分頁操作進行一下封裝,讓操作更方便一些,這個話題我們在后面的章節(jié)再來詳細介紹。
Spring data jpa 在PagingAndSortingRepository接口下還提供了一個JpaRepository接口,該接口封裝了更常用的一些方法,使用方式都類似,如果將來在實現(xiàn)的過程中沒有特殊的需求(如:不希望公開所有接口方法之類的需求),一般都繼承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表示執(zhí)行l(wèi)ike查詢,%zt%表示值
Predicate p1 = criteriaBuilder.like(root.get("address"), "%zt%");
Predicate p2 = criteriaBuilder.greaterThan(root.get("id"),3);
//將兩個查詢條件聯(lián)合起來之后返回Predicate對象
return criteriaBuilder.and(p1,p2);
}
});
Assert.assertEquals(2,stus.size());
Assert.assertEquals("oo",stus.get(0).getName());
}
使用Specification的要點就是CriteriaBuilder,通過這個對象來創(chuàng)建條件,之后返回一個Predicate對象。這個對象中就有了相應(yīng)的查詢需求,我們同樣可以定義多個Specification,之后通過Specifications對象將其連接起來。以下是一個非常典型的應(yīng)用
@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%'),這其實是一個非常典型的應(yīng)用,但是相信大家已經(jīng)發(fā)現(xiàn)這個操作實在是太繁雜了,所以個人認為Specification這個方案其實就是為了讓我們對其進行封裝,而不是直接使用的。
另外在toPredicate中還有一個CriteriaQuery的參數(shù),這個對象提供了更多有用的查詢,如分組之類的,可以使用該對象組成復(fù)雜的SQL語句來查詢,這塊內(nèi)容和具體的封裝實現(xiàn)將會在下一章節(jié)介紹。