您的位置:首页 > 编程语言 > ASP

如何优雅的使用Jpa的JpaSpecificationExecutor提供的复杂查询,实现Specification对象的简单操作

2019-08-23 00:06 941 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/a627428179/article/details/100026568

首先看一个查询的列子,输入一个名称,模糊匹配username或者nickName

[code]return adminJpa.findAll(
adminJpa.support().like("username",name).or(support -> support.like("nickName",name)),
PageRequest.of(pageNum - 1,pageSize)
);

以上代码所打印的sql

[code]Hibernate: select * from table table0_ where table0_.nick_name like ? or table0_.username like ? limit ?

相信你直接看都能看得懂以上代码所代表的是什么意思,那么这种操作是怎么实现的呢?

可以看到,我们只用了三四行的代码,就实现了原本需要十几行,并且可读性极差的代码所实现的功能

代码块

首先,我们先自定义一个BaseJpa,将原先继承JpaRepository的接口改成此接口,看以下代码,这里最主要的功能由JpaRepository<T>提供,复杂查询由JpaSpecificationExecutor<T>提供

[code]/**
* @author hexm
* @date 2019/8/20 10:54
*/
@NoRepositoryBean
public interface BaseJpa<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {

/**
* 批量删除
* @param ids
* @return
*/
int deleteByIdIn(Iterable<ID> ids);

/**
* 提供默认复杂查询支持
* @return
*/
default SpecificationSupport<T> support(){
return SpecificationSupport.of();
}
}

我们需要做的就是提供一个Specification<T>的实现,这个实现是JpaSpecificationExecutor接口所要求的参数对象,对此我们做了一层简单的封装,以达到平常需要用到的条件查询,如果你找不到你要的方法,可以在原有的基础上进行添加,相信这一定很简单!下面提供Specification实现

[code]/**
* Jpa复杂查询支持
*
* @author hexm
* @date 2019/8/19 15:25
*/
public class SpecificationSupport<T> implements Specification<T> {

/**
* 额外添加的or或者and条件
*/
private Map<OptionType, SpecificationSupport<T>> AND_OR = new LinkedHashMap<>();

/**
* 里面都是and条件
*/
private List<FieldOption> condition = new ArrayList<>();

/**
* 被忽略的属性
*/
private List<String> ignoreColumn = new ArrayList<>();

/**
* 忽略大小写
*/
private List<String> ignoreCase = new ArrayList<>();

private T t;

private boolean ignoreNullString = true;

private SpecificationSupport() {
}

/**
* 得到一个查询构造器
*
* @param probe
* @param <T>
* @param <K>
* @return
*/
public static <T, K extends T> SpecificationSupport<T> of(K probe) {
SpecificationSupport<T> s = new SpecificationSupport<>();
s.t = probe;
return s;
}

/**
* 得到一个查询构造器
*
* @param <T>
* @return
*/
public static <T> SpecificationSupport<T> of(Class<T> clazz) {
return new SpecificationSupport<>();
}

/**
* 得到一个查询构造器
*
* @param <T>
* @return
*/
public static <T> SpecificationSupport<T> of() {
return new SpecificationSupport<>();
}

/**
* 前匹配 like %column
*
* @param column
* @return
*/
public SpecificationSupport<T> startsLike(String column) {
addCondition(column, OptionType.STARTS_LIKE);
return this;
}

/**
* 后匹配 like column%
*
* @param column
* @return
*/
public SpecificationSupport<T> endLike(String column) {
addCondition(column, OptionType.END_LIKE);
return this;
}

/**
* 匹配 like %column%
*
* @param column
* @return
*/
public SpecificationSupport<T> like(String column) {
addCondition(column, OptionType.LIKE);
return this;
}

/**
* 匹配 = column
*
* @param column
* @return
*/
public SpecificationSupport<T> eq(String column) {
addCondition(column, OptionType.EQ);
return this;
}

/**
* 匹配 != column
*
* @param column
* @return
*/
public SpecificationSupport<T> neq(String column) {
addCondition(column, OptionType.NOT_EQ);
return this;
}

/**
* 匹配 not like %column
*
* @param column
* @return
*/
public SpecificationSupport<T> notStartLike(String column) {
addCondition(column, OptionType.NOT_STARTS_LIKE);
return this;
}

/**
* 匹配 not like column%
*
* @param column
* @return
*/
public SpecificationSupport<T> notEndLike(String column) {
addCondition(column, OptionType.NOT_END_LIKE);
return this;
}

/**
* 匹配 not like %column%
*
* @param column
* @return
*/
public SpecificationSupport<T> notLike(String column) {
addCondition(column, OptionType.NOT_LIKE);
return this;
}

/**
* 匹配 > column
*
* @param column 字段属性必须为数字类型,或字符串的数字
* @return
*/
public SpecificationSupport<T> gt(String column) {
addCondition(column, OptionType.GT);
return this;
}

/**
* 匹配 >= column
*
* @param column 字段属性必须为数字类型,或字符串的数字
* @return
*/
public SpecificationSupport<T> ge(String column) {
addCondition(column, OptionType.GE);
return this;
}

/**
* 匹配 < column
*
* @param column 字段属性必须为数字类型,或字符串的数字
* @return
*/
public SpecificationSupport<T> lt(String column) {
addCondition(column, OptionType.LT);
return this;
}

/**
* 匹配 <= column
*
* @param column 字段属性必须为数字类型,或字符串的数字
* @return
*/
public SpecificationSupport<T> le(String column) {
addCondition(column, OptionType.LE);
return this;
}

/**
* 前匹配 like %column
*
* @param column
* @return
*/
public SpecificationSupport<T> startsLike(String column, String value) {
addCondition(column, OptionType.STARTS_LIKE, value);
return this;
}

/**
* 后匹配 like column%
*
* @param column
* @return
*/
public SpecificationSupport<T> endLike(String column, String value) {
addCondition(column, OptionType.END_LIKE, value);
return this;
}

/**
* 匹配 like %column%
*
* @param column
* @return
*/
public SpecificationSupport<T> like(String column, String value) {
addCondition(column, OptionType.LIKE, value);
return this;
}

/**
* 匹配 = column
*
* @param column
* @return
*/
public SpecificationSupport<T> eq(String column, Object value) {
addCondition(column, OptionType.EQ, value);
return this;
}

/**
* 匹配 != column
*
* @param column
* @return
*/
public SpecificationSupport<T> neq(String column, Object value) {
addCondition(column, OptionType.NOT_EQ, value);
return this;
}

/**
* 匹配 not like %column
*
* @param column
* @return
*/
public SpecificationSupport<T> notStartLike(String column, String value) {
addCondition(column, OptionType.NOT_STARTS_LIKE, value);
return this;
}

/**
* 匹配 not like column%
*
* @param column
* @return
*/
public SpecificationSupport<T> notEndLike(String column, String value) {
addCondition(column, OptionType.NOT_END_LIKE, value);
return this;
}

/**
* 匹配 not like %column%
*
* @param column
* @return
*/
public SpecificationSupport<T> notLike(String column, String value) {
addCondition(column, OptionType.NOT_LIKE, value);
return this;
}

/**
* 匹配 > column
*
* @param column 字段属性必须为数字类型
* @return
*/
public SpecificationSupport<T> gt(String column, Number value) {
addCondition(column, OptionType.GT, value);
return this;
}

/**
* 匹配 >= column
*
* @param column 字段属性必须为数字类型
* @return
*/
public SpecificationSupport<T> ge(String column, Number value) {
addCondition(column, OptionType.GE, value);
return this;
}

/**
* 匹配 < column
*
* @param column 字段属性必须为数字类型
* @return
*/
public SpecificationSupport<T> lt(String column, Number value) {
addCondition(column, OptionType.LT, value);
return this;
}

/**
* 匹配 <= column
*
* @param column 字段属性必须为数字类型
* @return
*/
public SpecificationSupport<T> le(String column, Number value) {
addCondition(column, OptionType.LE, value);
return this;
}

/**
* 匹配 between column1 and column2
*
* @param column 字段
* @param value1 继承了比较接口的对象
* @param value2 继承了比较接口的对象
* @return
*/
public SpecificationSupport<T> between(String column, Comparable value1, Comparable value2) {
addCondition(column, OptionType.BETWEEN, value1, value2);
return this;
}

/**
* 匹配 in (column)
*
* @param column 字段
* @param value 多个比较值
* @return
*/
public SpecificationSupport<T> in(String column, Object... value) {
addCondition(column, OptionType.IN, value);
return this;
}

/**
* 忽略某个字段属性
*
* @param columns
* @return
*/
public SpecificationSupport<T> ignoreProperty(String... columns) {
ignoreColumn.addAll(Arrays.asList(columns));
return this;
}

/**
* 忽略大小写匹配 = lower(column)
*
* @param columns
* @return
*/
public SpecificationSupport<T> ignoreCase(String... columns) {
ignoreCase.addAll(Arrays.asList(columns));
return this;
}

/**
* 添加条件
*
* @param column
* @param optionType
*/
private void addCondition(String column, OptionType optionType) {
try {
addCondition(column, optionType, ObjectUtils.getField(t, column).get(t));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

/**
* 添加条件
*
* @param column
* @param optionType
*/
@SafeVarargs
private final <K> void addCondition(String column, OptionType optionType, K... value) {
boolean flag = true;
for (K k : value) {
if (k == null) {
flag = false;
break;
}
if ("".equals(k) && ignoreNullString) {
flag = false;
break;
}
}
if (flag) {
condition.add(new FieldOption<>(column, optionType, value));
}
}

/**
* and操作,创建一个消费者,依靠lambed表达式,消费一个创建好的对象,以添加到定义的队列中
*
* @param and
* @return
*/
public SpecificationSupport<T> and(Consumer<SpecificationSupport<T>> and) {
if (and != null) {
SpecificationSupport<T> s = SpecificationSupport.of();
and.accept(s);
if (!s.isEmpty()) {
AND_OR.put(OptionType.AND, s);
}
}
return this;
}

/**
* or操作,创建一个消费者,依靠lambed表达式,消费一个创建好的对象,以添加到定义的队列中
*
* @param or
* @return
*/
public SpecificationSupport<T> or(Consumer<SpecificationSupport<T>> or) {
if (or != null) {
SpecificationSupport<T> s = SpecificationSupport.of();
or.accept(s);
if (!s.isEmpty()) {
AND_OR.put(OptionType.OR, s);
}
}
return this;
}

/**
* @param root
* @param query
* @param cb
* @return
*/
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if (t != null){
Map<String, Field> fieldMap = ObjectUtils.getFieldMap(t.getClass());
fieldMap.forEach((s1, field) -> {
try {
if (!Modifier.isFinal(field.getModifiers())) {
if (!condition.contains(new FieldOption<>(field.getName(), null, null))) {
addCondition(field.getName(), OptionType.EQ, field.get(t));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
Predicate p = null;
List<Predicate> predicates = new ArrayList<>();
for (FieldOption option : condition) {
//不在忽略字段中
if (!ignoreColumn.contains(option.name)) {
Predicate predicate = getPredicate(root, cb, option);
if (predicate != null) {
predicates.add(predicate);
}
}
}
if (!predicates.isEmpty()) {
p = cb.and(predicates.toArray(new Predicate[0]));
}
for (Map.Entry<OptionType, SpecificationSupport<T>> entry : AND_OR.entrySet()) {
OptionType optionType = entry.getKey();
SpecificationSupport<T> specificationSupport = entry.getValue();
if (optionType == OptionType.AND) {
if (p == null) {
p = cb.and(specificationSupport.toPredicate(root, query, cb));
} else {
p = cb.and(specificationSupport.toPredicate(root, query, cb), p);
}
} else {
if (p == null) {
p = cb.or(specificationSupport.toPredicate(root, query, cb));
} else {
p = cb.or(specificationSupport.toPredicate(root, query, cb), p);
}
}
}
return p;
}

/**
* 设置是否忽略空串
*
* @param ignoreNullString
*/
public void setIgnoreNullString(boolean ignoreNullString) {
this.ignoreNullString = ignoreNullString;
}

/**
* 判断构造器条件是否为空
* @return
*/
public boolean isEmpty(){
return condition.isEmpty() && AND_OR.isEmpty();
}

/**
* 判断条件类型,添加到组合中
*
* @param root
* @param cb
* @param option
* @return
*/
@SuppressWarnings("unchecked")
private Predicate getPredicate(Root<T> root, CriteriaBuilder cb, FieldOption option) {
switch (option.getOptionType()) {
case EQ:
if (ignoreCase.contains(option.getName())) {
return cb.equal(cb.lower(root.get(option.getName())), option.getValue()[0].toString().toLowerCase());
}
return cb.equal(root.get(option.getName()), option.getValue()[0]);
case GE:
return cb.ge(root.get(option.getName()), (Number) option.getValue()[0]);
case GT:
return cb.gt(root.get(option.getName()), (Number) option.getValue()[0]);
case IN:
if (ignoreCase.contains(option.getName())) {
CriteriaBuilder.In<Object> in = cb.in(cb.lower(root.get(option.getName())));
for (Object o : option.getValue()) {
in.value(o.toString().toLowerCase());
}
return cb.and(in);
}
return root.get(option.getName()).in(option.getValue());
case LE:
return cb.le(root.get(option.getName()), (Number) option.getValue()[0]);
case LT:
Number o = null;
if (option.getValue()[0] instanceof String) {
o = new BigInteger((String) option.getValue()[0]);
}
if (o == null) {
o = (Number) option.getValue()[0];
}
return cb.lt(root.get(option.getName()), o);
case LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.like(cb.lower(root.get(option.getName())), "%" + option.getValue()[0].toString().toLowerCase() + "%");
}
return cb.like(root.get(option.getName()), "%" + option.getValue()[0] + "%");
case STARTS_LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.like(cb.lower(root.get(option.getName())), "%" + option.getValue()[0].toString().toLowerCase());
}
return cb.like(root.get(option.getName()), "%" + option.getValue()[0]);
case END_LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.like(cb.lower(root.get(option.getName())), option.getValue()[0].toString().toLowerCase() + "%");
}
return cb.like(root.get(option.getName()), option.getValue()[0] + "%");
case IS_NULL:
return root.get(option.getName()).isNull();
case IS_NOT_NULL:
return root.get(option.getName()).isNotNull();
case NOT_LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.notLike(cb.lower(root.get(option.getName())), "%" + option.getValue()[0].toString().toLowerCase() + "%");
}
return cb.notLike(root.get(option.getName()), "%" + option.getValue()[0] + "%");
case NOT_STARTS_LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.notLike(cb.lower(root.get(option.getName())), "%" + option.getValue()[0].toString().toLowerCase());
}
return cb.notLike(root.get(option.getName()), "%" + option.getValue()[0]);
case NOT_END_LIKE:
if (ignoreCase.contains(option.getName())) {
return cb.notLike(cb.lower(root.get(option.getName())), option.getValue()[0].toString().toLowerCase() + "%");
}
return cb.notLike(root.get(option.getName()), option.getValue()[0] + "%");
case NOT_EQ:
if (ignoreCase.contains(option.getName())) {
return cb.notEqual(cb.lower(root.get(option.getName())), option.getValue()[0].toString().toLowerCase());
}
return cb.notEqual(root.get(option.getName()), option.getValue()[0]);
case BETWEEN:
return cb.between(root.get(option.getName()), (Comparable) option.getValue()[0], (Comparable) option.getValue()[1]);
}
return null;
}

private enum OptionType {
AND,
OR,
BETWEEN,    //between x1 and x2
STARTS_LIKE,//like %s
END_LIKE,   //like s%
LIKE,       //like %s%
EQ,         //=
GT,         //>
LT,         //<
GE,         //>=
LE,         //<=
IN,         //in
REGEXP,     //regexp(column,regex)
IS_NULL,    // is null
IS_NOT_NULL,//is not null
NOT_EQ,     //!=
NOT_LIKE,   //not like %s%
NOT_STARTS_LIKE,//not like %s
NOT_END_LIKE,   //not like s%
IGNORE_CASE //忽略大小写
}

private static class FieldOption<T> {
private String name;
private OptionType optionType;
private T[] value;

FieldOption(String name, OptionType optionType, T[] value) {
this.name = name;
this.optionType = optionType;
this.value = value;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public OptionType getOptionType() {
return optionType;
}

public void setOptionType(OptionType optionType) {
this.optionType = optionType;
}

public T[] getValue() {
return value;
}

public void setValue(T[] value) {
this.value = value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldOption option = (FieldOption) o;
return Objects.equals(name, option.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
}

关于以上代码中用到的其他工具代码:

[code]/**
* 获取对象Field属性对象集合ap
*
* @param clazz
* @return
*/
public static Map<String, Field> getFieldMap(Class clazz) {
Map<String, Field> result = new LinkedHashMap<String, Field>(16);
for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
result.put(field.getName(), field);
}
}
return result;
}

这些你都可以复制后直接使用!

具体代码实现原理这里不再累赘,网上很多这方面的资料。

由于时间问题只看了两天的资料,所以有一些疏漏的地方轻喷,请指正。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐