`

基于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进行分页查询以及带有条件的分页查询。 首先,为了...

    springboot使用JPA时间类型进行模糊查询的方法

    在实际开发中,我们经常需要使用 JPA 来实现数据库的查询操作,而在时间类型的模糊查询方面,需要特殊处理。本文将详细介绍 Spring Boot 使用 JPA 时间类型进行模糊查询的方法。 时间类型的模糊查询 在使用 JPA ...

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

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

    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 笔记

    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-...

Global site tag (gtag.js) - Google Analytics