利用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. Reconstruction(三维重建)文件被修改

    修改内容: 该函数被修改了一部分,然后修改中止了,可能是牵一发而动全身,导致中止.无论什么原因,这个Reconstruction.cpp文件是唯一被修改的文件了.如果没有被修改该多好!!!!!! 如何 ...

  2. 【小梅哥FPGA进阶教程】第十四章 TFT屏显示图片

    十四.TFT屏显示图片 本文由杭电网友曾凯峰贡献,特此感谢 学习了小梅哥的TFT显示屏驱动设计后,想着在此基础上通过TFT屏显示一张图片,有了这个想法就开始动工了.首先想到是利用FPGA内部ROM存储 ...

  3. 51nod1298圆与三角形——(二分法)

    1298 圆与三角形  题目来源: HackerRank 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 给出圆的圆心和半径,以及三角形的三个顶点,问圆同 ...

  4. centos 7 安装jdk8

    到官网下载jdk http://www.oracle.com/technetwork/java/javase/downloads/index.html 选择liunx的tar.gz文件下载 下载好后 ...

  5. opencv——通过面积筛选最大轮廓,并求凸包矩形的长和宽

    #include "stdafx.h" #include <iostream> #include<string> #include <stdio.h& ...

  6. C# xsd 验证 XML数据有效性 问题

    使用XSD进行批量数据导入时生成的XML数据有效性这样的功能已经不是第一次做了,之前做的时候都没有碰到什么问题,这些天在开发中遇到了一个很头痛的问题就是无论XSD文件规则怎么写,验证都是通过的. 下面 ...

  7. word2010多级列表编号为什么会变成黑块

    把光标放置在黑块的后面 在键盘上按左方向键,则黑块变灰色(为选中状态) 然后ctrl+shift+s, 出现窗口“apply styles" 点击"reapply", 搞 ...

  8. 在 Mac OSX 上安装 nginx

    今天在使用 brew 安装 nginx 时,提示错误,安装不上去: brew install nginx, 提示:/usr/local is not writable. 这个是需要修改 /usr/lo ...

  9. OLEDB方式操作oracle数据库

    OLEDB方式操作oracle数据库 一.查询语句: using (OleDbConnection conn = new OleDbConnection(System.Configuration.Co ...

  10. Oracle彻底卸载

    Oracle彻底卸载 卸载:oracle卸载1.删除注册表:打开注册表:regedit 打开路径: <找注册表 :开始->运行->regedit> HKEY_LOCAL_MAC ...