基于jpa的specification实现动态查询

阅读更多

    spring data jpa为我们实现简单的crud操作提供了极大的方便。但大部分情况下,系统中都存在大量的动态查询操作,这个时候就可以借助spring data jpa的 Specification动态查询操作。但这个动态查询操作的大部分的代码都是重复的,因此可以考虑将Specification进一步封装.一个查询条件的构成,需要知道是查询的是那个字段(field),用的是什么类型的操作符(op),以及是什么数据类型的(dataType),如果我们前台封装好,传递到后台,后台在进行解析就知道如何进行查询了。

思路:

   1、前台提交查询条件时,提交的 key: 为h_后台实体类中的字段_操作符_数据类型 value: 为实际的值

        eg:  用户名:

   2、后台从request中获取到所有的参数,然后判断参数是否是构成查询条件的。

        判断依据: 必须以 h_ 开头,中间以 _ 分割 ,且分割后的数组是 4.

   3、检测上一步的操作符是否是我们系统中支持的,检测 数据类型是否是系统中支持的。

   4、构成查询条件

         就是根据上一步得到的值,根据不同的数据类型和不同的操作符构成不同的Predicate,最终组合这些Predicate

代码如下:

一、写一个类实现Specification接口,在此接口中构造查询条件 (这个类主要是用于构造动态查询条件)

package com.huan.dubbo.bookshop.repositoy.ext;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 * 基于jpa specification动态查询条件的封装
 * 
 * @描述
 * @作者 huan
 * @时间 2017年10月29日 - 上午11:41:30
 * 
 *     
 * 使用方式:外部传递进来一个map->
 * 			key: h_实体类中的字段_操作符_数据类型  value: 实际的值
 *          eg:  h_name_eq_s             value: 12   ===> name=12
 *     
*/ @Slf4j public class JpaSpecificationWrapper implements Specification { @Getter private Map params; public JpaSpecificationWrapper(Map params) { this.params = params; } private static final ConcurrentHashMap DATE_TYPE_FORMATTER = new ConcurrentHashMap<>(); private static final String PREFIX = "h_"; private static final String SPLIT = "_"; static { DATE_TYPE_FORMATTER.put("d", "yyyy-MM-dd HH:mm:ss"); } @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { if (null == params) { return null; } List predicates = new ArrayList<>(); for (Map.Entry entry : params.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (!key.startsWith(PREFIX)) { continue; } String[] conditions = key.split(SPLIT); if (conditions.length != 4) { log.warn("非法的查询条件:{}", key); continue; } String field = conditions[1]; String op = conditions[2]; String dataType = conditions[3]; Predicate predicate = convertPredicate(root, cb, field, op, dataType, value); Optional.ofNullable(predicate).ifPresent(predicates::add); } return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction(); } private Predicate convertPredicate(Root root, CriteriaBuilder cb, String field, String op, String dataType, Object value) { if (field == null || value == null || value.toString().equals("")) { return null; } Predicate predicate; switch (op) { case "eq": predicate = cb.equal(root.get(field), wrapValue(dataType, op, value)); break; case "lt": predicate = convertLtPredicate(root, cb, field, op, dataType, value); break; case "lte": predicate = convertLtePredicate(root, cb, field, op, dataType, value); break; case "gt": predicate = convertGtPredicate(root, cb, field, op, dataType, value); break; case "gte": predicate = convertGtePredicate(root, cb, field, op, dataType, value); break; case "l": case "ll": case "rl": predicate = cb.like(root.get(field), (String) wrapValue(dataType, op, value)); break; default: throw new RuntimeException("暂不支持的sql操作符" + op); } return predicate; } private Predicate convertGtePredicate(Root root, CriteriaBuilder cb, String field, String op, String dataType, Object value) { switch (dataType) { case "s": return cb.greaterThanOrEqualTo(root.get(field), (String) wrapValue(dataType, op, value)); case "i": return cb.greaterThanOrEqualTo(root.get(field), (Double) wrapValue(dataType, op, value)); case "d": return cb.greaterThanOrEqualTo(root.get(field), (Date) wrapValue(dataType, op, value)); default: throw new RuntimeException("非法的数据类型." + dataType); } } private Predicate convertGtPredicate(Root root, CriteriaBuilder cb, String field, String op, String dataType, Object value) { switch (dataType) { case "s": return cb.greaterThan(root.get(field), (String) wrapValue(dataType, op, value)); case "i": return cb.greaterThan(root.get(field), (Double) wrapValue(dataType, op, value)); case "d": return cb.greaterThan(root.get(field), (Date) wrapValue(dataType, op, value)); default: throw new RuntimeException("非法的数据类型." + dataType); } } private Predicate convertLtePredicate(Root root, CriteriaBuilder cb, String field, String op, String dataType, Object value) { switch (dataType) { case "s": return cb.lessThanOrEqualTo(root.get(field), (String) wrapValue(dataType, op, value)); case "i": return cb.lessThanOrEqualTo(root.get(field), (Double) wrapValue(dataType, op, value)); case "d": return cb.lessThanOrEqualTo(root.get(field), (Date) wrapValue(dataType, op, value)); default: throw new RuntimeException("非法的数据类型." + dataType); } } private Predicate convertLtPredicate(Root root, CriteriaBuilder cb, String field, String op, String dataType, Object value) { switch (dataType) { case "s": return cb.lessThan(root.get(field), (String) wrapValue(dataType, op, value)); case "i": return cb.lessThan(root.get(field), (Double) wrapValue(dataType, op, value)); case "d": return cb.lessThan(root.get(field), (Date) wrapValue(dataType, op, value)); default: throw new RuntimeException("非法的数据类型." + dataType); } } private Object wrapValue(String dataType, String op, Object value) { switch (dataType) { case "s": String newValue = (String) value; if ("l".equals(op)) { newValue = "%" + value + "%"; } else if ("ll".equals(op)) { newValue = "%" + value; } else if ("rl".equals(op)) { newValue = value + "%"; } return newValue; case "i": return Integer.parseInt((String) value); case "d": try { return new SimpleDateFormat(DATE_TYPE_FORMATTER.get(dataType)).parse((String) value); } catch (ParseException e) { throw new RuntimeException("不合法的日期." + value); } default: throw new RuntimeException("非法的数据类型" + dataType); } } }

 注意事项:

   1、params 这个map中保存的是前台传递过来的查询条件和值

   2、PREFIX 这个用于判断查询条件是否是以这个开头,如果不是则说明不是查询条件

   3、SPLIT 这个用于进行查询条件的分割,看长度是否是4

   4、DATE_TYPE_FORMATTER 当数据类型是日期类型时,需要格式化日期操作。

   5、wrapValue 这个方法是根据不同的数据类型,将参数中的值进行包装。

   6、convertPredicate 这个方法是根据不同的操作符,封装不同的Predicate

二、编写一个实体类User

/**
 * 用户实体类映射
 * 
 * @描述
 * @作者 huan
 * @时间 2017年10月29日 - 上午11:35:14
 */
@Table(name = "t_user")
@Entity
@Data
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id")
	private String id;
	@Column(name = "name")
	private String name;
	@Column(name = "age")
	private Integer age;
}

 三、编写UserRepository接口,这个接口需要继承JpaSpecificationExecutor这个接口,这个接口是spring data jpa中实现动态查询所必须的

/**
 * 用户查询repository
 * 
 * @描述
 * @作者 huan
 * @时间 2017年10月29日 - 上午11:45:31
 */
public interface UserRepository extends JpaRepository, JpaSpecificationExecutor {

}

 四、测试
基于jpa的specification实现动态查询_第1张图片
 

  • 基于jpa的specification实现动态查询_第2张图片
  • 大小: 194.8 KB
  • 查看图片附件

你可能感兴趣的:(jpa,jap,specification,jpa,动态条件查询)