基于jpa的specification实现动态查询
spring data jpa为我们实现简单的crud操作提供了极大的方便。但大部分情况下,系统中都存在大量的动态查询操作,这个时候就可以借助spring data jpa的 Specification动态查询操作。但这个动态查询操作的大部分的代码都是重复的,因此可以考虑将Specification进一步封装.一个查询条件的构成,需要知道是查询的是那个字段(field),用的是什么类型的操作符(op),以及是什么数据类型的(dataType),如果我们前台封装好,传递到后台,后台在进行解析就知道如何进行查询了。
思路:
1、前台提交查询条件时,提交的 key: 为h_后台实体类中的字段_操作符_数据类型 value: 为实际的值
eg: 用户名:<input type="text" name="h_name_eq_s" />
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
 *
 *     <pre>
 * 使用方式:外部传递进来一个map->
 * 			key: h_实体类中的字段_操作符_数据类型  value: 实际的值
 *          eg:  h_name_eq_s             value: 12   ===> name=12
 *     </pre>
 */
@Slf4j
public class JpaSpecificationWrapper<T> implements Specification<T> {
	@Getter
	private Map<String, Object> params;
	public JpaSpecificationWrapper(Map<String, Object> params) {
		this.params = params;
	}
	private static final ConcurrentHashMap<String, String> 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<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
		if (null == params) {
			return null;
		}
		List<Predicate> predicates = new ArrayList<>();
		for (Map.Entry<String, Object> 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<T> 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<T> 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<T> 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<T> 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<T> 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<User, Integer>, JpaSpecificationExecutor<User> {
}四、测试
基于jpa的specification实现动态查询的更多相关文章
- JPA使用Specification构建动态查询
		封装Specification查询条件,在Spring Data JPA 2.0以前使用 Specifications 这个辅助类来操作where.not.and和or连接,在2.0版本以后这个类会被 ... 
- SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法
		软件152 尹以操 首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml ... 
- 使用Spring Data JPA的Specification构建数据库查询
		Spring Data JPA最为优秀的特性就是可以通过自定义方法名称生成查询来轻松创建查询SQL.Spring Data JPA提供了一个Repository编程模型,最简单的方式就是通过扩展Jpa ... 
- Spring data jpa 实现简单动态查询的通用Specification方法
		本篇前提: SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法 这篇文章中的第二种方法 实现Specification 这块的方法 只适用于一个对象针对某一个固定字 ... 
- springboot整合spring data jpa 动态查询
		Spring Data JPA虽然大大的简化了持久层的开发,但是在实际开发中,很多地方都需要高级动态查询,在实现动态查询时我们需要用到Criteria API,主要是以下三个: 1.Criteria ... 
- spring data jpa封装specification实现简单风格的动态查询
		github:https://github.com/peterowang/spring-data-jpa-demo 单一实体的动态查询: @Servicepublic class AdvancedUs ... 
- spring data jpa Specification动态查询
		package com.ytkj.entity; import javax.persistence.*; import java.io.Serializable; /** * @Entity * 作用 ... 
- Spring data jpa 复杂动态查询方式总结
		一.Spring data jpa 简介 首先我并不推荐使用jpa作为ORM框架,毕竟对于负责查询的时候还是不太灵活,还是建议使用mybatis,自己写sql比较好.但是如果公司用这个就没办法了,可以 ... 
- Spring Data JPA,一种动态条件查询的写法
		我们在使用SpringData JPA框架时,进行条件查询,如果是固定条件的查询,我们可以使用符合框架规则的自定义方法以及@Query注解实现. 如果是查询条件是动态的,框架也提供了查询接口. Jpa ... 
随机推荐
- Haproxy搭建web集群
			目录: 一.常见的web集群调度器 二.Haproxy应用分析 三.Haproxy调度算法原理 四.Haproxy特性 五.Haproxy搭建 Web 群集 一.常见的web集群调度器 目前常见的we ... 
- 【转载】小心 int 乘法溢出!
			C/C++ 语言里, 绝大部分平台上 int 类型是 32 位的, 无论你的操作系统是否是 64 位的. 而一些常用的函数, 如 malloc(), 它接受的参数是 size_t 类型: void * ... 
- 使用Java MVC模式设计一个学生管理系统
			最近在做web实验,要求是用jsp+servlet+mysql实现一个学生管理系统,完成对数据库的增删改查. 效果图: 代码: package dao; import java.util.List ... 
- 自制C++游戏 迷宫
			1 #include<bits/stdc++.h> 2 #include<conio.h> 3 using namespace std; 4 char mg[17][17]={ ... 
- PHP垃圾回收机制的一些浅薄理解
			相信只要入门学习过一点开发的同学都知道,不管任何编程语言,一个变量都会保存在内存中.其实,我们这些开发者就是在来回不停地操纵内存,相应地,我们如果一直增加新的变量,内存就会一直增加,如果没有一个好的机 ... 
- webpack learn1-初始化项目1
			使用Visual Studio Code软件使用准备,先安装一些插件,加快开发效率(还有Language Packs 选择简体中文安装后重启软件,可切换为中文): 下面是项目初始化步骤: 1 软件打 ... 
- 解决IE浏览器 点击子元素重复调用执行 mouseover  与mouseout兼容性问题
			将函数配对换为下面2个就可以解决兼容性问题. mouseenter() mouseleave(0 
- Docker系列(28)- 自定义网络
			自定义网络 网络模式 bridge:桥接docker(默认,自己创建也可以使用bridge模式) none:不配置网络 host:和宿主机共享网络 container:容器网络联通!(用的少!局限性大 ... 
- centos7 .net core 使用supervisor守护进程后台运行
			安装supervisor yum install supervisor 配置supervisor vi /etc/supervisord.conf 拉到最后,这里的意思是 /etc/superviso ... 
- CF848E-Days of Floral Colours【dp,分治NTT】
			正题 题目链接:https://www.luogu.com.cn/problem/CF848E 题目大意 \(2n\)个花排成一个圆环,\(n\)种颜色每种两个,要求两个相同颜色之间最小距离为\(1, ... 
