`

基于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> {

}

 四、测试

 

  • 大小: 194.8 KB
分享到:
评论

相关推荐

    spring data jpa 的Specifications动态查询

    总结来说,Spring Data JPA的`Specifications`接口为开发者提供了强大的动态查询能力,能够灵活地应对各种复杂的查询场景,同时保持代码的整洁和可维护性。通过熟练掌握`Specifications`,可以极大地提升开发效率,...

    JPA Specification

    ### JPA Specification详解 #### 一、概述 **JPA (Java Persistence API)** 是一种用于管理关系型数据库的对象-关系映射(ORM)标准。它为开发者提供了一种灵活且强大的方式来处理数据库操作,无需编写繁琐的SQL...

    springboot+jpa+swagger 动态查询

    6. 可能还会有Querydsl或Specification的示例,展示如何实现动态查询。 这个简单的demo案例是学习和实践Spring Boot、JPA以及Swagger集成的一个好起点,它帮助开发者理解如何在实际项目中应用这些技术,尤其是如何...

    JPA分页查询与条件分页查询

    Spring Data JPA 是基于 JPA 的一个扩展,它简化了数据库操作,使得开发者无需编写大量SQL语句,就能实现数据的增删改查。在这个场景中,我们将探讨如何使用JPA进行分页查询以及带有条件的分页查询。 首先,为了...

    spring data jpa分页查询示例代码

    Spring Data JPA 提供了多种方式来实现分页查询,包括使用 `Pageable`、`Specification` 和 `CriteriaQuery` 等。 使用 Pageable 实现分页查询 使用 `Pageable` 是 Spring Data JPA 中最简单的分页查询方式。可以...

    Spring Data JPA 简化 JPA 开发

    5. JpaSpecificationExecutor:这是一个用于执行复杂查询的接口,通过配合 Specification 接口,开发者可以构建动态的查询条件。 6. Specification:Spring Data JPA 提供的查询规范,用于构建复杂的查询逻辑。...

    详解Spring Data JPA动态条件查询的写法

    在实际开发中,我们经常需要根据不同的条件进行数据查询,而 Spring Data JPA 提供了多种方式来实现动态条件查询。本文将详细介绍 Spring Data JPA 动态条件查询的写法和实现原理。 固定条件查询 在使用 Spring ...

    Spring Data JPA 笔记

    4. **Specifications**:Spring Data JPA的`Specification`接口允许动态构建查询条件。这在处理复杂查询时非常有用,因为可以基于业务需求动态组合多个条件。 5. **事务管理**:Spring Data JPA集成Spring的事务...

    spring data jpa中文文档

    除了基于方法名的查询外,Spring Data JPA还支持使用Criteria API来构建复杂的动态查询。这是一种类型安全的API,可以避免SQL拼接错误。 6. **Transaction管理** Spring Data JPA整合了Spring的事务管理,可以...

    Spring Data JPA.zip

    - **强大的查询支持**:除了简单的 CRUD 方法,Spring Data JPA 还支持基于方法名的复杂查询,甚至可以使用 JPA Querydsl 或 Specification 进行更复杂的查询。 - **事务管理**:Spring Data JPA 结合 Spring 的事务...

    SpringData JPA 参考文档PDF 英文

    实体查询(Entity querying)部分深入讲解了如何利用Spring Data JPA进行高级实体查询,包括使用Specification(规范)来创建动态查询、使用事务性查询方法、锁定机制以及审计元数据(Auditing)的配置。 事务性...

    spring data jpa

    3. 高级查询:通过`Specification`接口,你可以构建复杂的查询条件,实现动态查询。 4. 转换和映射:`@Entity`注解用于标记数据库表对应的实体类,`@Table`、`@Column`等注解用于指定表名和字段属性,`@Id`用于标识...

    spring+JPA示例

    4. **Repository接口**:Spring Data JPA 提供了基于接口的数据访问,我们可以通过继承JpaRepository接口,自定义方法来实现对数据库的CRUD操作,而无需编写具体的实现类。 5. **事务管理**:Spring 提供了声明式...

    spring data jpa参考文档

    总结来说,Spring Data JPA 为开发者提供了简单而强大的 API 来处理数据库操作,它不仅极大地减少了模板代码的数量,还允许开发者轻松地实现复杂的查询逻辑。对于想要提高生产力并减少样板代码的 Java 开发者来说,...

    Spring Data JPA分页复合查询原理解析

    Spring Data JPA是一种基于JPA的数据访问技术,旨在简化数据访问层的实现,减少开发人员的工作量。在Spring Data JPA中,开发人员可以通过编写repository接口,包括自定义查找器方法,Spring将自动提供实现。例如,...

    Spring MVC结合Spring Data JPA实现按条件查询和分页

    在实现按条件查询和分页时,我们需要使用Spring Data JPA的Specification接口来定义查询条件。Specification接口提供了对查询条件的定义,我们可以使用它来定义复杂的查询条件。在本例中,我们使用了Specification...

    Spring JPA Example

    此外,Spring JPA还支持更复杂的查询,如使用`@Query`注解编写自定义SQL或HQL,以及使用`Specification`构建动态查询。 在实际开发中,我们还可以结合Spring Boot的自动化配置、事务管理、异常处理等功能,进一步...

    SpringDataJPA快速使用.pdf

    * 使用 Specification 接口,动态构建查询条件 配置文件规则 在使用 SpringDataJPA 时,我们需要在 Spring 配置文件中增加一些配置项: * &lt;jpa:repositories&gt; 标签,用于指定 Dao 接口的扫描路径 * &lt;jpa:vendor-...

    Spring Data JPA系列3:JPA项目中核心场景与进阶用法介绍.doc

    QueryByExampleExecutor接口则允许我们基于对象实例进行查询,这在处理动态查询时非常有用。 自定义Repository是Spring Data JPA的另一大优势。开发者可以根据需求扩展JpaRepository,添加自己的方法,实现特定的...

Global site tag (gtag.js) - Google Analytics