github:https://github.com/peterowang/spring-data-jpa-demo
單一實體的動態查詢:
@Service
public class AdvancedUserInfoService{
@Autowired
UserInfoRepository userInfoRepository;
/**
* 簡單分頁排序查詢
*/
public Page<UserInfo> pageList(int pageNo,int pageSize){
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable able = new PageRequest(pageNo, pageSize, sort);
return userInfoRepository.findAll(able);
}
/**
* 復雜動態多條件查詢
* @param username
* @param password
* @param id
* @return
*/
public List<UserInfo> listDynamic(final String username,final String password,final Integer id){
Specification<UserInfo> sf = new Specification<UserInfo>() {
List<Predicate> list = new ArrayList<>();
@Override
public Predicate toPredicate(Root<UserInfo> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/* Predicate p1 = cb.like(root.get("name").as(String.class), "%"+um.getName()+"%");
Predicate p2 = cb.equal(root.get("uuid").as(Integer.class), um.getUuid());
Predicate p3 = cb.gt(root.get("age").as(Integer.class), um.getAge());
//把Predicate應用到CriteriaQuery中去,因為還可以給CriteriaQuery添加其他的功能,比如排序、分組啥的
query.where(cb.and(p3,cb.or(p1,p2)));//where p3 and (p1 or p2)
//添加排序的功能
query.orderBy(cb.desc(root.get("uuid").as(Integer.class)));
return query.getRestriction();*/
List<Predicate> list = new ArrayList<>();
if(!StringUtils.isEmpty(username)){
list.add(criteriaBuilder.like(root.get("username").as(String.class), "%" + username + "%"));
}
if(!StringUtils.isEmpty(password)){
list.add(criteriaBuilder.isNotNull(root.get("password").as(String.class)));
}
if(id!=null){
list.add(criteriaBuilder.greaterThanOrEqualTo(root.get("id").as(Integer.class),id));
}
Predicate[] pd = new Predicate[list.size()];
criteriaQuery.where(list.toArray(pd));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get("id").as(Integer.class)));
return criteriaQuery.getRestriction();
}
} ;
return userInfoRepository.findAll(sf);
}
public Page<UserInfo> pageDynamic(final String username,final String password,final Integer id1,
final Integer id2,final Integer pageNo,final Integer pageSize){
return userInfoRepository.findAll(new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate p1=null;Predicate p2=null; Predicate p3=null;
if(StringUtils.isNotEmpty(username)){
p1 = criteriaBuilder.equal(root.get("username").as(String.class),username);
}
if(StringUtils.isNotEmpty(password)){
p2 = criteriaBuilder.equal(root.get("password").as(String.class), password);
}
if(id1!=null&&id2!=null){
p3 = criteriaBuilder.between(root.get("id").as(Integer.class), id1, id2);
}
criteriaQuery.where(criteriaBuilder.and(p1,criteriaBuilder.or(p2,p3)));
return criteriaQuery.getRestriction();
}
},new PageRequest(pageNo,pageSize,new Sort(Sort.Direction.DESC,"id")));
}
}
Spring Data JPA已經幫助我們很大程度上簡化了我們的查詢操作,我們甚至只要寫一個接口,然后單純的寫一些方法就可以完成各式各樣的查詢,但是對於我們程序設計人員而言,總希望所有的查詢變得更加的簡單方便,為了給程序人員進行再一次的封裝,Spring Data JPA提供了Specification的方式進行查詢,在前面的內容已經演示過這種查詢了,但是,我們在使用的過程中發現這種查詢異常的繁瑣和復雜,接下來的內容就是我們有效的對Specification進行封裝來快速實現一些簡單的查詢操作。當然如果涉及到更為復雜的操作,依然建議寫個方法來自己實現。
封裝自己的Specification的實現有很多種方法,我這里只給出了相對簡單的一種,而且並沒有考慮太復雜的查詢,個人感覺過於復雜的查詢還不如直接使用SQL或者HQL來處理方便,以下是幾個比較重要的類:
SpecificationOperator
表示操作符類,用來確定查詢條件和值。
package com.example.demo.SpecificationUtil;
/**
* Created by BFD-593 on 2017/8/17.
* 操作符類,這個類中存儲了鍵值對和操作符號,另外存儲了連接下一個條件的類型是and還是or
* 創建時通過 id>=7,其中id就是key,>=就是oper操作符,7就是value
* 特殊的自定義幾個操作符(:表示like %v%,l:表示v%,:l表示%v)
*/
public class SpecificationOperator {
/**
* 操作符的key,如查詢時的name,id之類
*/
private String key;
/**
* 操作符的value,具體要查詢的值
*/
private Object value;
/**
* 操作符,自己定義的一組操作符,用來方便查詢
*/
private String oper;
/**
* 連接的方式:and或者or
*/
private String join;
public SpecificationOperator(String key, Object value, String oper, String join) {
this.key = key;
this.value = value;
this.oper = oper;
this.join = join;
}
public SpecificationOperator() {
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getOper() {
return oper;
}
public void setOper(String oper) {
this.oper = oper;
}
public String getJoin() {
return join;
}
public void setJoin(String join) {
this.join = join;
}
}
接下來創建SimpleSpecification
來實現Specification
接口,並且根據條件生成Specification
對象,因為在最后查詢的時候需要這個對象
package com.example.demo.SpecificationUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;
/**
* 創建SimpleSpecification來實現Specification接口,
* 並且根據條件生成Specification對象,因為在最后查詢的時候需要這個對象
* SimpleSpecification是核心類型,
* 用來根據條件生成Specification對象,這個SimpleSpecification直接存儲了具體的查詢條件。
* Created by BFD-593 on 2017/8/17.
*/
public class SimpleSpecification<T> implements Specification<T> {
/**
* 查詢的條件列表,是一組列表
* */
private List<SpecificationOperator> opers;
public SimpleSpecification(List<SpecificationOperator> opers){
this.opers=opers;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
int index = 0;
Predicate resultPre = null;
for(SpecificationOperator so :opers){
if(index++==0){//第一次index=0 index++是先賦值再加
resultPre = generatePredicate(root, criteriaBuilder, so);
continue;
}
Predicate pre = generatePredicate(root, criteriaBuilder, so);
if(pre==null)continue;
if("and".equalsIgnoreCase(so.getJoin())){
resultPre = criteriaBuilder.and(resultPre, pre);
}else if("or".equalsIgnoreCase(so.getJoin())){
resultPre = criteriaBuilder.or(resultPre, pre);
}
}
return resultPre;
}
private Predicate generatePredicate(Root<T> root,CriteriaBuilder criteriaBuilder,SpecificationOperator so){
if(so!=null&&StringUtils.isNotEmpty(so.getOper())){
if("=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.equal(root.get(so.getKey()), so.getValue());
}else if(">=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.ge(root.get(so.getKey()).as(Number.class),(Number) so.getValue());
}else if("<=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.le(root.get(so.getKey()).as(Number.class),(Number)so.getValue());
}else if(">".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.gt(root.get(so.getKey()).as(Number.class), (Number) so.getValue());
}else if("<".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.lt(root.get(so.getKey()).as(Number.class), (Number) so.getValue());
}else if(":".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.like(root.get(so.getKey()).as(String.class), "%" + so.getValue() + "%");
}else if(":l".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.like(root.get(so.getKey()).as(String.class), "%" + so.getValue());
}else if("l:".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.like(root.get(so.getKey()).as(String.class), so.getValue() + "%");
}else if("null".equalsIgnoreCase(so.getOper())){
return criteriaBuilder.isNull(root.get(so.getKey()));
}else if("!null".equalsIgnoreCase(so.getOper())){
return criteriaBuilder.isNotNull(root.get(so.getKey()));
}else if("!=".equalsIgnoreCase(so.getOper())&&so.getValue()!=null){
return criteriaBuilder.notEqual(root.get(so.getKey()), so.getValue());
}
}
return null;
}
}
SimpleSpecification
是核心類型,用來根據條件生成Specification對象,這個SimpleSpecification
直接存儲了具體的查詢條件。
最后我們創建一個SimpleSpecificationBuilder
來具體創建SimpleSpecification
,這里為了方便調用簡單進行了一下設計。
package com.example.demo.SpecificationUtil;
import com.google.common.collect.Lists;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
/**
* 創建一個SimpleSpecificationBuilder來具體創建SimpleSpecification,
* 這里為了方便調用簡單進行了一下設計。
* Created by BFD-593 on 2017/8/17.
*/
public class SimpleSpecificationBuilder<T> {
/**
* 條件列表
*/
private List<SpecificationOperator> opers;
/**
* 構造函數,初始化的條件是and
*/
public SimpleSpecificationBuilder(String key,String oper,Object value,String join){
SpecificationOperator so = new SpecificationOperator(key, value, oper, join);
opers = Lists.newArrayList();
opers.add(so);
}
/**
* 構造,初始化無條件
*/
public SimpleSpecificationBuilder(){
opers = Lists.newArrayList();
}
/**
* 往list中填加條件
* @param key
* @param oper
* @param value
* @param join
* @return
*/
public SimpleSpecificationBuilder add(String key,String oper,Object value,String join){
SpecificationOperator so = new SpecificationOperator(key, value, oper, join);
opers.add(so);
return this;
}
/**
* 填加一個and條件
* @param key
* @param oper
* @param value
* @return
*/
public SimpleSpecificationBuilder and(String key,String oper,Object value){
return this.add(key, oper, value, "and");
}
/**
* 填加一個or條件
* @param key
* @param oper
* @param value
* @return
*/
public SimpleSpecificationBuilder or(String key,String oper,Object value){
return this.add(key, oper, value, "or");
}
/**
* 觸發SimpleSpecification並返回Specification
*/
public Specification getSpecification(){
Specification<T> sp = new SimpleSpecification<T>(opers);
return sp;
}
}
測試:
/**
* 在多條件動態查詢時需要繼承JpaSpecificationExecutor接口
* JpaSpecificationExecutor可以通過findAll方法傳入SimpleSpecification來進行查詢
* Created by BFD-593 on 2017/8/16.
*/
public interface RoleRepository extends BaseRepository<Role,Integer>,JpaSpecificationExecutor<Role> {
}
/**
* 測試封裝的specification
* 實現簡單風格的動態查詢
* id < id and roleName like %roleName% or id>id and roleName like roleName%的動態查詢
* 某個參數為空時,就不使用該參數所在的條件。
* @param roleName
* @param id
* @return
*/
public List<Role> spe(String roleName,Integer id) {
return roleRepository.findAll(new SimpleSpecificationBuilder<Role>().
and("id", "<", id).
and("roleName",":",roleName).
or("id",">",id).
and("roleName","l:",roleName).
getSpecification());
}