利用JPA的Specification<T>接口和元模型就实现动态查询了。但是这样每一个需要动态查询的地方都需要写一个这样类似的findByConditions方法,小型项目还好,大型项目中其实会造成人力资源的浪费,进行了大量的重复工作,所以想着对动态查询进行封装,使其使用起来更加方便。

  在开发中,用到动态查询的地方,所有的查询条件包括分页参数,都会被封装成一个查询类XxxQuery,我们封装的思路是创建一个BaseQuery类,在其中实现动态查询的封装,即提供几个模板方法,将查询类的所有属性按照连接规则,拼装成一个Specification型的对象返回,那么问题来了,如何去标识这些字段该用怎样的查询条件连接呢,还要考虑到每个查询类都可以通用,可以用字段注解,来标识字段的查询连接条件。

  创建枚举类MatchType,列出所有的连接条件

package powerx.io;

public enum MatchType {

    equal,        // filed = value
//下面四个用于Number类型的比较
gt, // filed > value
ge, // field >= value
lt, // field < value
le, // field <= value notEqual, // field != value
like, // field like value
notLike, // field not like value
// 下面四个用于可比较类型(Comparable)的比较
greaterThan, // field > value
greaterThanOrEqualTo, // field >= value
lessThan, // field < value
lessThanOrEqualTo // field <= value
}

  自定义注解,用来标识字段

package powerx.io;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface QueryCondition { // 数据库中字段名,默认为空字符串,则Query类中的字段要与数据库中字段一致
String column() default ""; // equal, like, gt, lt...
MatchType func() default MatchType.equal; // object是否可以为null
boolean nullable() default false; // 字符串是否可为空
boolean emptyable() default false; }

  改造查询实体

package powerx.io.bean;

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

import powerx.io.MatchType;
import powerx.io.QueryCondition; public class ProductQuery { @QueryCondition(func=MatchType.like)
private String name; @QueryCondition(func=MatchType.le)
private Double price; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Double getPrice() {
return price;
} public void setPrice(Double price) {
this.price = price;
} }

  下面是最核心的部分,公共查询实体类,其中在toSpecWithLogicType方法中利用反射机制,将所有的属性按照注解的规则加入到动态查询条件中

package powerx.io.bean;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification; import powerx.io.QueryCondition; public abstract class BaseQuery<T> { // start from 0
protected int pageIndex = 0;
protected int pageSize = 10; /**
* 将查询转换成Specification
* @return
*/
public abstract Specification<T> toSpec(); //JPA分页查询类
public Pageable toPageable() {
return new PageRequest(pageIndex, pageSize);
} //JPA分页查询类,带排序条件
public Pageable toPageable(Sort sort) {
return new PageRequest(pageIndex, pageSize, sort);
} //动态查询and连接
protected Specification<T> toSpecWithAnd() {
return this.toSpecWithLogicType("and");
} //动态查询or连接
protected Specification<T> toSpecWithOr() {
return this.toSpecWithLogicType("or");
} //logicType or/and
@SuppressWarnings({ "rawtypes", "unchecked" })
private Specification<T> toSpecWithLogicType(String logicType) {
BaseQuery outerThis = this;
return (root, criteriaQuery, cb) -> {
Class clazz = outerThis.getClass();
//获取查询类Query的所有字段,包括父类字段
List<Field> fields = getAllFieldsWithRoot(clazz);
List<Predicate> predicates = new ArrayList<>(fields.size());
for (Field field : fields) {
//获取字段上的@QueryWord注解
QueryCondition qw = field.getAnnotation(QueryCondition.class);
if (qw == null)
continue; // 获取字段名
String column = qw.column();
//如果主注解上colume为默认值"",则以field为准
if (column.equals(""))
column = field.getName(); field.setAccessible(true); try { // nullable
Object value = field.get(outerThis);
//如果值为null,注解未标注nullable,跳过
if (value == null && !qw.nullable())
continue; // can be empty
if (value != null && String.class.isAssignableFrom(value.getClass())) {
String s = (String) value;
//如果值为"",且注解未标注emptyable,跳过
if (s.equals("") && !qw.emptyable())
continue;
} //通过注解上func属性,构建路径表达式
Path path = root.get(column);
switch (qw.func()) {
case equal:
predicates.add(cb.equal(path, value));
break;
case like:
predicates.add(cb.like(path, "%" + value + "%"));
break;
case gt:
predicates.add(cb.gt(path, (Number) value));
break;
case lt:
predicates.add(cb.lt(path, (Number) value));
break;
case ge:
predicates.add(cb.ge(path, (Number) value));
break;
case le:
predicates.add(cb.le(path, (Number) value));
break;
case notEqual:
predicates.add(cb.notEqual(path, value));
break;
case notLike:
predicates.add(cb.notLike(path, "%" + value + "%"));
break;
case greaterThan:
predicates.add(cb.greaterThan(path, (Comparable) value));
break;
case greaterThanOrEqualTo:
predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));
break;
case lessThan:
predicates.add(cb.lessThan(path, (Comparable) value));
break;
case lessThanOrEqualTo:
predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));
break;
}
} catch (Exception e) {
continue;
}
}
Predicate p = null;
if (logicType == null || logicType.equals("") || logicType.equals("and")) {
p = cb.and(predicates.toArray(new Predicate[predicates.size()]));//and连接
} else if (logicType.equals("or")) {
p = cb.or(predicates.toArray(new Predicate[predicates.size()]));//or连接
}
return p;
};
} //获取类clazz的所有Field,包括其父类的Field
private List<Field> getAllFieldsWithRoot(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
Field[] dFields = clazz.getDeclaredFields();//获取本类所有字段
if (null != dFields && dFields.length > 0)
fieldList.addAll(Arrays.asList(dFields)); // 若父类是Object,则直接返回当前Field列表
Class<?> superClass = clazz.getSuperclass();
if (superClass == Object.class) return Arrays.asList(dFields); // 递归查询父类的field列表
List<Field> superFields = getAllFieldsWithRoot(superClass); if (null != superFields && !superFields.isEmpty()) {
superFields.stream().
filter(field -> !fieldList.contains(field)).//不重复字段
forEach(field -> fieldList.add(field));
}
return fieldList;
} public int getPageIndex() {
return pageIndex;
} public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
} public int getPageSize() {
return pageSize;
} public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}

  在BaseQuery里,就通过toSpecWithAnd() toSpecWithOr()方法动态构建出了查询条件。那现在ItemQuery就要继承BaseQuery,并实现toSpec()抽象方法

package powerx.io.bean;

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

import powerx.io.MatchType;
import powerx.io.QueryCondition; public class ProductQuery extends BaseQuery<Product>{ @QueryCondition(func=MatchType.like)
private String name; @QueryCondition(func=MatchType.le)
private Double price; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Double getPrice() {
return price;
} public void setPrice(Double price) {
this.price = price;
} @Override
public Specification<Product> toSpec() { return super.toSpecWithAnd();
} }

  当然肯定还有其他不能在BaseQuery中构建的查询条件,比如说查询价格在某个区间的情况,那就在子类的toSpec()实现中添加

@Override
public Specification<Product> toSpec() { Specification<Product> spec = super.toSpecWithAnd();
return ((root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicatesList = new ArrayList<>();
predicatesList.add(spec.toPredicate(root, criteriaQuery, criteriaBuilder));
if (priceMin != null) {
predicatesList.add(
criteriaBuilder.and(
criteriaBuilder.ge(
root.get(Product_.price), priceMin)));
}
if (priceMax != null) {
predicatesList.add(
criteriaBuilder.and(
criteriaBuilder.le(
root.get(Product_.price), priceMax)));
}
return criteriaBuilder.and(predicatesList.toArray(new Predicate[predicatesList.size()]));
});
}

  调用代码

public Page<Product> findByConditions3(String name, Double price) {
ProductQuery pq = new ProductQuery();
pq.setName(name);
pq.setPrice(price);
return productDao.findAll(pq.toSpec(), pq.toPageable());
}

  响应如下:

  现在这个BaseQueryQuertWord就可以在各个动态查询处使用了,只需在查询字段上标注@QueryWord注解。然后实现BaseQuery中的抽象方法toSpec(),通过JpaSpecificationExecutor接口中的这几个方法,就可以实现动态查询了。

spring data jpa 动态查询(工具类封装)的更多相关文章

  1. springboot整合spring data jpa 动态查询

    Spring Data JPA虽然大大的简化了持久层的开发,但是在实际开发中,很多地方都需要高级动态查询,在实现动态查询时我们需要用到Criteria API,主要是以下三个: 1.Criteria ...

  2. Spring Data JPA动态查询(多条件and)

    entity: @Entity @Table(name = "data_illustration") public class Test { @Id @GenericGenerat ...

  3. 【Spring Data 系列学习】Spring Data JPA 基础查询

    [Spring Data 系列学习]Spring Data JPA 基础查询 前面的章节简单讲解了 了解 Spring Data JPA . Jpa 和 Hibernate,本章节开始通过案例上手 S ...

  4. spring data jpa 分页查询

    https://www.cnblogs.com/hdwang/p/7843405.html spring data jpa 分页查询   法一(本地sql查询,注意表名啥的都用数据库中的名称,适用于特 ...

  5. Spring Boot 入门系列(二十七)使用Spring Data JPA 自定义查询如此简单,完全不需要写SQL!

    前面讲了Spring Boot 整合Spring Boot JPA,实现JPA 的增.删.改.查的功能.JPA使用非常简单,只需继承JpaRepository ,无需任何数据访问层和sql语句即可实现 ...

  6. spring data JPA entityManager查询 并将查询到的值转为实体对象

    spring data JPA entityManager查询 并将查询到的值转为实体对象 . https://blog.csdn.net/qq_34791233/article/details/81 ...

  7. Spring Data JPA 多个实体类表联合视图查询

    Spring Data JPA 查询数据库时,如果两个表有关联,那么就设个外键,在查询的时候用Specification创建Join 查询便可.但是只支持左连接,不支持右连接,虽说左右连接反过来就能实 ...

  8. Spring Data JPA 实例查询

    一.相关接口方法     在继承JpaRepository接口后,自动拥有了按"实例"进行查询的诸多方法.这些方法主要在两个接口中定义,一是QueryByExampleExecut ...

  9. springboot集成Spring Data JPA数据查询

    1.JPA介绍 JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据.它的出现主要是为 ...

随机推荐

  1. 编写高质量代码改善C#程序的157个建议——建议97:优先考虑将基类型或接口作为参数传递

    建议97:优先考虑将基类型或接口作为参数传递 除了公开及类型或接口外,方法的参数也应该考虑基类型或接口. 以Enumerable类型为例,它的成员方法中只要涉及需要操作集合对象的地方,都要使用IEnu ...

  2. 编写高质量代码改善C#程序的157个建议——建议62:避免嵌套异常

    建议62:避免嵌套异常 应该允许异常在调用堆栈上往上传,不要过多的使用catch,然后再throw.过多的使用catch会带来两个问题: 1)代码更多了.这看上去好像你根本不知道怎么处理异常,所以你总 ...

  3. modelsim使用常见问题及解决办法集锦 ②

    二.Error deleting “msim_transcript” Error deleting “msim_transcript”:permission denied. Check the Nat ...

  4. Windows7中7种不同关机模式介绍

    在Win7关机选项中一共有7种关闭方式,分别为 Switch user(切换用户), Log off(登出), Lock(锁定), Restart(重启), Sleep(睡眠), Hibernate( ...

  5. centos 7 安装mysql5.6rpm格式

    1查看是否安装了mysql   rpm -qa|grep -i mysql 如果安装了请卸载:rpm -e --nodeps MySQL... 2.没有安装则进行如下操作 下载mysql rpm ta ...

  6. [自动化专题]JDBC操作mysql时遇到的拦路虎

    在挫折中成长,在错误中学习.聊聊我们在Selenium自动化中使用JDBC操作mysql数据库中遇到的那些拦路虎: 错误一:Can not issue data manipulation statem ...

  7. linux命令之磁盘与文件系统管理命令(上)

    1.fdisk:磁盘分区工具 该命令是linux下常用的磁盘分区工具,但是只能给小于2TB的磁盘划分分区. 常用参数为-l,显示所有磁盘分区的信息. 示例: 1)显示磁盘分区列表 [root@boxi ...

  8. python list的append()和extend()方法

    引用自:https://www.cnblogs.com/subic/p/6553187.html

  9. 973. K Closest Points to Origin

    We have a list of points on the plane.  Find the K closest points to the origin (0, 0). (Here, the d ...

  10. redis 限制接口访问频率

    代码: <?php /** * */ class myRedis { private static $redis = null; /** * @return null|Redis */ publ ...