我們繼續研究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
還有一個內部類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語句來查詢,這塊內容和具體的封裝實現將會在下一章節介紹。