1. 程式人生 > 程式設計 >Spring data jpa 的使用與詳解(二):複雜動態查詢及分頁,排序

Spring data jpa 的使用與詳解(二):複雜動態查詢及分頁,排序

上一篇介紹了spring data jpa在spring boot中的基本搭建和整合,以及較為簡單的查詢,這一篇來說spring data jpa中如何實現較為複雜的查詢及分頁,排序。

1 使用Specification實現複雜查詢

1.1 什麼是Specification

Specification是springDateJpa中的一個介面,他是用於當jpa的一些基本CRUD操作的擴充套件,可以把他理解成一個spring jpa的複雜查詢介面。其次我們需要了解Criteria 查詢,這是是一種型別安全和更面向物件的查詢。而Spring Data JPA支援JPA2.0的Criteria查詢,相應的介面是JpaSpecificationExecutor。
而JpaSpecificationExecutor這個介面基本是圍繞著Specification介面來定義的, Specification介面中只定義瞭如下一個方法:

Predicate toPredicate(Root<T> root,CriteriaQuery<?> query,CriteriaBuilder cb); 
複製程式碼

Criteria查詢基本概念
Criteria 查詢是以元模型的概念為基礎的,元模型是為具體持久化單元的受管實體定義的,這些實體可以是實體類,嵌入類或者對映的父類。

CriteriaQuery介面
代表一個specific的頂層查詢物件,它包含著查詢的各個部分,比如:select 、from、where、group by、order by等注意:CriteriaQuery物件只對實體型別或嵌入式型別的Criteria查詢起作用。
Root:


代表Criteria查詢的根物件,Criteria查詢的查詢根定義了實體型別,能為將來導航獲得想要的結果,它與SQL查詢中的FROM子句類似。 Root例項是型別化的,且定義了查詢的FROM子句中能夠出現的型別。root代表查詢的實體類,query可以從中得到root物件,告訴jpa查詢哪一個實體類,還可以新增查詢條件,還可以結合EntityManager物件 得到最終查詢的 TypedQuery物件。 CriteriaBuilder介面
用來構建CritiaQuery的構建器物件Predicate:一個簡單或複雜的謂詞型別,其實就相當於條件或者是條件組合。 可通過 EntityManager.getCriteriaBuilder 而得。

2 使用Specification進行復雜的動態查詢

maven的依賴繼續使用上一章的就可以,這裡修改一下實體類和controller層。
請求實體類:

@Data
public class AccountRequest {

    //從第幾頁開始
    private Integer page;

    //每一頁查詢多少
    private Integer limit;

    private String id;

    private String name;

    private String pwd;

    private String email;

    private Integer[] types;

}
複製程式碼

實體類:

@Data
@Entity
@Table(name = "account")
@ToString
@EntityListeners(AuditingEntityListener.class)
public class Account {

    @Id
    @GenericGenerator(name = "idGenerator",strategy = "uuid")
    @GeneratedValue(generator = "idGenerator")
    private String id;

    @Column(name = "username",unique = true,nullable = false,length = 64)
    private String username;

    @Column(name = "password",length = 64)
    private String password;

    @Column(name = "email",length = 64)
    private String email;

    @Column(name = "type")
    private Short type;

    @CreatedDate
    @Column(name = "create_time",nullable = false)
    private LocalDateTime createTime;

}
複製程式碼

Repository層:

public interface AccountRepository extends JpaRepository<Account,String>,JpaSpecificationExecutor<Account> {}
複製程式碼

controller層(還是直接略過service層)

@Autowired
    private AccountRepository repository;


    @PostMapping("/get")
    public List<Account> get(@RequestBody AccountRequest request){
        Specification<Account> specification = new Specification<Account>() {

            @Override
            public Predicate toPredicate(Root<Account> root,CriteriaQuery<?> criteriaQuery,CriteriaBuilder builder) {
                //所有的斷言 及條件
                List<Predicate> predicates = new ArrayList<>();
                //精確匹配id pwd
                if (request.getId() != null) {
                    predicates.add(builder.equal(root.get("id"),request.getId()));
                }
                if (request.getPwd() != null) {
                    predicates.add(builder.equal(root.get("password"),request.getPwd()));
                }
                //模糊搜尋 name
                if (request.getName() != null && !request.getName().equals("")) {
                    predicates.add(builder.like(root.get("username"),"%" + request.getName() + "%"));
                }
                if (request.getEmail() != null && !request.getEmail().equals("")) {
                    predicates.add(builder.like(root.get("email"),"%" + request.getEmail() + "%"));
                }
                //in範圍查詢
                if (request.getTypes() != null) {
                    CriteriaBuilder.In<Object> types = builder.in(root.get("type"));
                    for (Integer type : request.getTypes()) {
                        types = types.value(type);
                    }
                    predicates.add(types);
                }
                return builder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
        List<Account> accounts = repository.findAll(specification);

        return accounts;
    }
複製程式碼

通過重寫Specification的toPredicate的方法,這樣一個複雜的動態sql查詢就完成了,通過post請求直接就可以呼叫了。

3 分頁及排序

@PostMapping("/page")
    public List<Account> getPage(@RequestBody AccountRequest request){

        Specification<Account> specification = new Specification<Account>() {
            @Override
            public Predicate toPredicate(Root<Account> root,CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                //do anything
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
        //表示通過createTime進行 ASC排序
        PageRequest page = new PageRequest(request.getPage() - 1,request.getLimit(),Sort.Direction.ASC,"createTime");
        Page<Account> pageInfo = repository.findAll(specification,page);

        return pageInfo.getContent();
    }
複製程式碼

上面的程式碼是在經過複雜查詢並進行分頁與排序,通過PageRequest來構建分頁排序的規則。傳入起始頁及每頁的數量,還有排序的規則及以哪個屬性排序。jpa中是以第0頁開始的,所以傳參的時候需要注意!
當然,如果你不需要進行復雜的查詢也可以對資料進行分頁及排序查詢。
修改repository,使其繼承PagingAndSortingRepository

@Repository
public interface AccountRepository extends JpaRepository<Account,JpaSpecificationExecutor<Account>,PagingAndSortingRepository<Account,String> {
    Page<Account> findByAge(int age,Pageable pageable);
}
複製程式碼

使用時先建立pageable引數,然後傳進去就可以了。

//顯示第1頁每頁顯示3條
PageRequest pr = new PageRequest(1,3);
//根據年齡進行查詢
Page<Account> stus = accountPageRepository.findByAge(22,pr); 
複製程式碼

排序也是一樣的,在repository中建立方法

List<Account> findByPwd(String pwd,Sort sort);
複製程式碼

呼叫的時候傳入sort物件

//設定排序方式為username降序
List<Account> accs = accountPageRepository.findByAge("123456",new Sort(Sort.Direction.DESC,"username"));
//設定排序以username和type進行升序
acc = accountPageRepository.findByAge("123456",new Sort(Sort.Direction.ASC,"username","type"));
//設定排序方式以name升序,以address降序
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"name"),new Sort.Order(Sort.Direction.DESC,"type"));
accs = accountPageRepository.findByAge("123456",sort);
複製程式碼