refactor(service): 将查询条件解析逻辑封装到 QueryParser 中
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.controller;
|
package com.lanyuanxiaoyao.service.template.common.controller;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.Query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询控制器接口,用于定义统一的查询实体详情和列表的接口规范
|
* 查询控制器接口,用于定义统一的查询实体详情和列表的接口规范
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.controller;
|
package com.lanyuanxiaoyao.service.template.common.controller;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除控制器接口,用于定义统一的删除实体对象的接口规范
|
* 删除控制器接口,用于定义统一的删除实体对象的接口规范
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.controller;
|
package com.lanyuanxiaoyao.service.template.common.controller;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存控制器接口,用于定义统一的保存实体对象的接口规范
|
* 保存控制器接口,用于定义统一的保存实体对象的接口规范
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.controller;
|
package com.lanyuanxiaoyao.service.template.common.entity;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.service;
|
package com.lanyuanxiaoyao.service.template.common.entity;
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.controller;
|
package com.lanyuanxiaoyao.service.template.common.entity;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.lanyuanxiaoyao.service.template.common.service;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.Query;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
|
||||||
|
|
||||||
|
public abstract class QueryParser<O> {
|
||||||
|
protected abstract void nullEqual(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notNullEqual(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void empty(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notEmpty(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void equal(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notEqual(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void like(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notLike(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void contain(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notContain(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void startWith(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notStartWith(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void endWith(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notEndWith(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void great(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void less(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void greatEqual(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void lessEqual(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void inside(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notInside(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void between(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract void notBetween(Query.Queryable queryable);
|
||||||
|
|
||||||
|
protected abstract O build();
|
||||||
|
|
||||||
|
public O build(Query.Queryable queryable) {
|
||||||
|
if (ObjectHelper.isNull(queryable)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.nullEqual())) {
|
||||||
|
nullEqual(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notNullEqual())) {
|
||||||
|
notNullEqual(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.empty())) {
|
||||||
|
empty(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notEmpty())) {
|
||||||
|
notEmpty(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.equal())) {
|
||||||
|
equal(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notEqual())) {
|
||||||
|
notEqual(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.like())) {
|
||||||
|
like(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notLike())) {
|
||||||
|
notLike(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.contain())) {
|
||||||
|
contain(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notContain())) {
|
||||||
|
notContain(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.startWith())) {
|
||||||
|
startWith(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notStartWith())) {
|
||||||
|
notStartWith(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.endWith())) {
|
||||||
|
endWith(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notEndWith())) {
|
||||||
|
notEndWith(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.great())) {
|
||||||
|
great(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.less())) {
|
||||||
|
less(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.greatEqual())) {
|
||||||
|
greatEqual(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.lessEqual())) {
|
||||||
|
lessEqual(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.inside())) {
|
||||||
|
inside(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notInside())) {
|
||||||
|
notInside(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.between())) {
|
||||||
|
between(queryable);
|
||||||
|
}
|
||||||
|
if (ObjectHelper.isNotEmpty(queryable.notBetween())) {
|
||||||
|
notBetween(queryable);
|
||||||
|
}
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.common.service;
|
package com.lanyuanxiaoyao.service.template.common.service;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.service.template.common.controller.Query;
|
import com.lanyuanxiaoyao.service.template.common.entity.Page;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.Query;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.jpa.controller;
|
package com.lanyuanxiaoyao.service.template.jpa.controller;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.service.template.common.controller.GlobalResponse;
|
|
||||||
import com.lanyuanxiaoyao.service.template.common.controller.Query;
|
|
||||||
import com.lanyuanxiaoyao.service.template.common.controller.SimpleController;
|
import com.lanyuanxiaoyao.service.template.common.controller.SimpleController;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.Query;
|
||||||
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
|
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
|
||||||
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity;
|
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity;
|
||||||
import com.lanyuanxiaoyao.service.template.jpa.service.SimpleServiceSupport;
|
import com.lanyuanxiaoyao.service.template.jpa.service.SimpleServiceSupport;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.jpa.service;
|
package com.lanyuanxiaoyao.service.template.jpa.service;
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.service.template.common.controller.Query;
|
import com.lanyuanxiaoyao.service.template.common.entity.Page;
|
||||||
|
import com.lanyuanxiaoyao.service.template.common.entity.Query;
|
||||||
import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException;
|
import com.lanyuanxiaoyao.service.template.common.exception.IdNotFoundException;
|
||||||
import com.lanyuanxiaoyao.service.template.common.exception.NotCollectionException;
|
import com.lanyuanxiaoyao.service.template.common.exception.NotCollectionException;
|
||||||
import com.lanyuanxiaoyao.service.template.common.exception.NotComparableException;
|
import com.lanyuanxiaoyao.service.template.common.exception.NotComparableException;
|
||||||
import com.lanyuanxiaoyao.service.template.common.exception.NotStringException;
|
import com.lanyuanxiaoyao.service.template.common.exception.NotStringException;
|
||||||
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
|
import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper;
|
||||||
import com.lanyuanxiaoyao.service.template.common.service.Page;
|
import com.lanyuanxiaoyao.service.template.common.service.QueryParser;
|
||||||
import com.lanyuanxiaoyao.service.template.common.service.SimpleService;
|
import com.lanyuanxiaoyao.service.template.common.service.SimpleService;
|
||||||
import com.lanyuanxiaoyao.service.template.jpa.entity.IdOnlyEntity;
|
import com.lanyuanxiaoyao.service.template.jpa.entity.IdOnlyEntity;
|
||||||
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity;
|
import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity;
|
||||||
@@ -159,300 +160,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析字段路径
|
|
||||||
* <p>
|
|
||||||
* 支持多级字段路径解析,使用"."分隔多级字段。
|
|
||||||
* 例如: "user.name" 表示实体的user属性的name字段。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param root JPA Criteria查询根节点
|
|
||||||
* @param column 字段路径字符串
|
|
||||||
* @param <Y> 字段类型
|
|
||||||
* @return 返回字段路径对象
|
|
||||||
* @throws IllegalArgumentException 当字段路径为空时抛出
|
|
||||||
*/
|
|
||||||
private <Y> Path<Y> column(Root<ENTITY> root, String column) {
|
|
||||||
if (ObjectHelper.isEmpty(column)) {
|
|
||||||
throw new IllegalArgumentException("Column cannot be blank");
|
|
||||||
}
|
|
||||||
var columns = column.split("\\.");
|
|
||||||
Path<Y> path = root.get(columns[0]);
|
|
||||||
for (int i = 1; i < columns.length; i++) {
|
|
||||||
path = path.get(columns[i]);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理字段值
|
|
||||||
* <p>
|
|
||||||
* 对于枚举类型字段,将字符串值转换为对应的枚举值。
|
|
||||||
* 对于LocalDateTime类型字段,将字符串转换为时间对象。
|
|
||||||
* 其他类型直接返回原值。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param column 字段路径
|
|
||||||
* @param value 字段值
|
|
||||||
* @param <Y> 字段类型
|
|
||||||
* @return 处理后的字段值
|
|
||||||
* @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
private <Y> Object value(Path<Y> column, Object value) {
|
|
||||||
if (ObjectHelper.isNull(value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var javaType = column.getJavaType();
|
|
||||||
if (javaType.isEnum()) {
|
|
||||||
if (value instanceof String enumName) {
|
|
||||||
var enumType = (Class<Enum>) javaType;
|
|
||||||
return Enum.valueOf(enumType, enumName);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("枚举类型字段需要 String 类型的值");
|
|
||||||
}
|
|
||||||
} else if (javaType.isAssignableFrom(LocalDateTime.class)) {
|
|
||||||
return LocalDateTime.parse(String.valueOf(value), DATE_TIME_FORMATTER);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建查询条件谓词列表
|
|
||||||
* <p>
|
|
||||||
* 根据Query.Queryable对象构建JPA Criteria查询的谓词列表。
|
|
||||||
* 支持多种查询条件类型,包括相等、不等、模糊匹配、范围查询等。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param queryable 查询条件对象
|
|
||||||
* @param root JPA Criteria查询根节点
|
|
||||||
* @param query JPA Criteria查询对象
|
|
||||||
* @param builder JPA Criteria构建器
|
|
||||||
* @return 返回构建的谓词列表
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Predicate queryPredicates(Query.Queryable queryable, Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
|
||||||
var predicates = new ArrayList<Predicate>();
|
|
||||||
if (ObjectHelper.isNull(queryable)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.nullEqual())) {
|
|
||||||
queryable.nullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column))));
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notNullEqual())) {
|
|
||||||
queryable.notNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column))));
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.empty())) {
|
|
||||||
queryable.empty().forEach(column -> {
|
|
||||||
var path = this.<Collection<Object>>column(root, column);
|
|
||||||
checkCollection(path, column);
|
|
||||||
predicates.add(builder.isEmpty(path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notEmpty())) {
|
|
||||||
queryable.notEmpty().forEach(column -> {
|
|
||||||
var path = this.<Collection<Object>>column(root, column);
|
|
||||||
checkCollection(path, column);
|
|
||||||
predicates.add(builder.isNotEmpty(path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.equal())) {
|
|
||||||
queryable.equal().forEach((column, value) -> {
|
|
||||||
var path = column(root, column);
|
|
||||||
predicates.add(builder.equal(path, value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notEqual())) {
|
|
||||||
queryable.notEqual().forEach((column, value) -> {
|
|
||||||
var path = column(root, column);
|
|
||||||
predicates.add(builder.notEqual(path, value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.like())) {
|
|
||||||
queryable.like().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.like(path, value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notLike())) {
|
|
||||||
queryable.notLike().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.notLike(path, value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.contain())) {
|
|
||||||
queryable.contain().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.like(path, "%" + value + "%"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notContain())) {
|
|
||||||
queryable.notContain().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.notLike(path, "%" + value + "%"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.startWith())) {
|
|
||||||
queryable.startWith().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.like(path, value + "%"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notStartWith())) {
|
|
||||||
queryable.notStartWith().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.notLike(path, value + "%"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.endWith())) {
|
|
||||||
queryable.endWith().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.like(path, "%" + value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notEndWith())) {
|
|
||||||
queryable.notEndWith().forEach((column, value) -> {
|
|
||||||
var path = this.<String>column(root, column);
|
|
||||||
checkString(path, value, column);
|
|
||||||
predicates.add(builder.notLike(path, "%" + value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.great())) {
|
|
||||||
queryable.great().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.greaterThan(path, (Comparable<Object>) value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.less())) {
|
|
||||||
queryable.less().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.lessThan(path, (Comparable<Object>) value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.greatEqual())) {
|
|
||||||
queryable.greatEqual().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.greaterThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.lessEqual())) {
|
|
||||||
queryable.lessEqual().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.lessThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.inside())) {
|
|
||||||
queryable.inside()
|
|
||||||
.entrySet()
|
|
||||||
.stream()
|
|
||||||
.filter(entry -> ObjectHelper.isNotEmpty(entry.getValue()))
|
|
||||||
.forEach(entry -> predicates.add(builder.in(column(root, entry.getKey())).value(entry.getValue())));
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notInside())) {
|
|
||||||
queryable.notInside()
|
|
||||||
.entrySet()
|
|
||||||
.stream()
|
|
||||||
.filter(entry -> ObjectHelper.isNotEmpty(entry.getValue()))
|
|
||||||
.forEach(entry -> predicates.add(builder.in(column(root, entry.getKey())).value(entry.getValue()).not()));
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.between())) {
|
|
||||||
queryable.between().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (ObjectHelper.isNotEmpty(queryable.notBetween())) {
|
|
||||||
queryable.notBetween().forEach((column, value) -> {
|
|
||||||
var path = this.<Comparable<Object>>column(root, column);
|
|
||||||
checkComparable(path, value, column);
|
|
||||||
predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())).not());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return predicates.size() == 1
|
|
||||||
? predicates.get(0)
|
|
||||||
: builder.and(predicates.toArray(Predicate[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查字段类型是否可比较
|
|
||||||
*
|
|
||||||
* @param path 字段路径
|
|
||||||
* @param value 比较值
|
|
||||||
* @param column 字段名称
|
|
||||||
* @throws NotComparableException 当字段类型不可比较时抛出
|
|
||||||
*/
|
|
||||||
private void checkComparable(Path<?> path, Object value, String column) {
|
|
||||||
if (!ObjectHelper.isComparable(path.getJavaType()) || !ObjectHelper.isComparable(value)) {
|
|
||||||
throw new NotComparableException(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查区间值是否可比较
|
|
||||||
*
|
|
||||||
* @param path 字段路径
|
|
||||||
* @param value 区间对象
|
|
||||||
* @param column 字段名称
|
|
||||||
* @throws NotComparableException 当区间值不可比较时抛出
|
|
||||||
*/
|
|
||||||
private void checkComparable(Path<?> path, Query.Queryable.Between value, String column) {
|
|
||||||
checkComparable(path, value.start(), column);
|
|
||||||
checkComparable(path, value.end(), column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查字段类型是否为集合
|
|
||||||
*
|
|
||||||
* @param path 字段路径
|
|
||||||
* @param column 字段名称
|
|
||||||
* @throws NotCollectionException 当字段类型不是集合时抛出
|
|
||||||
*/
|
|
||||||
private void checkCollection(Path<?> path, String column) {
|
|
||||||
if (!ObjectHelper.isCollection(path.getJavaType())) {
|
|
||||||
throw new NotCollectionException(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查值是否为集合
|
|
||||||
*
|
|
||||||
* @param value 值对象
|
|
||||||
* @param column 字段名称
|
|
||||||
* @throws NotCollectionException 当值不是集合时抛出
|
|
||||||
*/
|
|
||||||
private void checkCollection(Object value, String column) {
|
|
||||||
if (!ObjectHelper.isCollection(value)) {
|
|
||||||
throw new NotCollectionException(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查字段类型是否为字符串
|
|
||||||
*
|
|
||||||
* @param path 字段路径
|
|
||||||
* @param value 比较值
|
|
||||||
* @param column 字段名称
|
|
||||||
* @throws NotStringException 当字段类型不是字符串时抛出
|
|
||||||
*/
|
|
||||||
private void checkString(Path<?> path, Object value, String column) {
|
|
||||||
if (!ObjectHelper.isString(path.getJavaType()) || !ObjectHelper.isString(value)) {
|
|
||||||
throw new NotStringException(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Predicate commonPredicates(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
protected Predicate commonPredicates(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -487,7 +194,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
var result = repository.findAll(
|
var result = repository.findAll(
|
||||||
(root, query, builder) -> {
|
(root, query, builder) -> {
|
||||||
var predicate = commonPredicates(root, query, builder);
|
var predicate = commonPredicates(root, query, builder);
|
||||||
var queryPredicate = queryPredicates(listQuery.query(), root, query, builder);
|
var queryPredicate = new JpaQueryParser<>(root, query, builder).build(listQuery.query());
|
||||||
return ObjectHelper.isNull(predicate)
|
return ObjectHelper.isNull(predicate)
|
||||||
? queryPredicate
|
? queryPredicate
|
||||||
: builder.and(predicate, queryPredicate);
|
: builder.and(predicate, queryPredicate);
|
||||||
@@ -586,4 +293,339 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
repository.deleteBatchByIds(ids);
|
repository.deleteBatchByIds(ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static final class JpaQueryParser<ENTITY> extends QueryParser<Predicate> {
|
||||||
|
private final Root<ENTITY> root;
|
||||||
|
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||||
|
private final CriteriaQuery<?> query;
|
||||||
|
private final CriteriaBuilder builder;
|
||||||
|
private final List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
private JpaQueryParser(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||||
|
this.root = root;
|
||||||
|
this.query = query;
|
||||||
|
this.builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析字段路径
|
||||||
|
* <p>
|
||||||
|
* 支持多级字段路径解析,使用"."分隔多级字段。
|
||||||
|
* 例如: "user.name" 表示实体的user属性的name字段。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param root JPA Criteria查询根节点
|
||||||
|
* @param column 字段路径字符串
|
||||||
|
* @param <Y> 字段类型
|
||||||
|
* @return 返回字段路径对象
|
||||||
|
* @throws IllegalArgumentException 当字段路径为空时抛出
|
||||||
|
*/
|
||||||
|
private <Y> Path<Y> column(Root<ENTITY> root, String column) {
|
||||||
|
if (ObjectHelper.isEmpty(column)) {
|
||||||
|
throw new IllegalArgumentException("Column cannot be blank");
|
||||||
|
}
|
||||||
|
var columns = column.split("\\.");
|
||||||
|
Path<Y> path = root.get(columns[0]);
|
||||||
|
for (int i = 1; i < columns.length; i++) {
|
||||||
|
path = path.get(columns[i]);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理字段值
|
||||||
|
* <p>
|
||||||
|
* 对于枚举类型字段,将字符串值转换为对应的枚举值。
|
||||||
|
* 对于LocalDateTime类型字段,将字符串转换为时间对象。
|
||||||
|
* 其他类型直接返回原值。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param column 字段路径
|
||||||
|
* @param value 字段值
|
||||||
|
* @param <Y> 字段类型
|
||||||
|
* @return 处理后的字段值
|
||||||
|
* @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
private <Y> Object value(Path<Y> column, Object value) {
|
||||||
|
if (ObjectHelper.isNull(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var javaType = column.getJavaType();
|
||||||
|
if (javaType.isEnum()) {
|
||||||
|
if (value instanceof String enumName) {
|
||||||
|
var enumType = (Class<Enum>) javaType;
|
||||||
|
return Enum.valueOf(enumType, enumName);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("枚举类型字段需要 String 类型的值");
|
||||||
|
}
|
||||||
|
} else if (javaType.isAssignableFrom(LocalDateTime.class)) {
|
||||||
|
return LocalDateTime.parse(String.valueOf(value), DATE_TIME_FORMATTER);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字段类型是否可比较
|
||||||
|
*
|
||||||
|
* @param path 字段路径
|
||||||
|
* @param value 比较值
|
||||||
|
* @param column 字段名称
|
||||||
|
* @throws NotComparableException 当字段类型不可比较时抛出
|
||||||
|
*/
|
||||||
|
private void checkComparable(Path<?> path, Object value, String column) {
|
||||||
|
if (!ObjectHelper.isComparable(path.getJavaType()) || !ObjectHelper.isComparable(value)) {
|
||||||
|
throw new NotComparableException(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查区间值是否可比较
|
||||||
|
*
|
||||||
|
* @param path 字段路径
|
||||||
|
* @param value 区间对象
|
||||||
|
* @param column 字段名称
|
||||||
|
* @throws NotComparableException 当区间值不可比较时抛出
|
||||||
|
*/
|
||||||
|
private void checkComparable(Path<?> path, Query.Queryable.Between value, String column) {
|
||||||
|
checkComparable(path, value.start(), column);
|
||||||
|
checkComparable(path, value.end(), column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字段类型是否为集合
|
||||||
|
*
|
||||||
|
* @param path 字段路径
|
||||||
|
* @param column 字段名称
|
||||||
|
* @throws NotCollectionException 当字段类型不是集合时抛出
|
||||||
|
*/
|
||||||
|
private void checkCollection(Path<?> path, String column) {
|
||||||
|
if (!ObjectHelper.isCollection(path.getJavaType())) {
|
||||||
|
throw new NotCollectionException(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查值是否为集合
|
||||||
|
*
|
||||||
|
* @param value 值对象
|
||||||
|
* @param column 字段名称
|
||||||
|
* @throws NotCollectionException 当值不是集合时抛出
|
||||||
|
*/
|
||||||
|
private void checkCollection(Object value, String column) {
|
||||||
|
if (!ObjectHelper.isCollection(value)) {
|
||||||
|
throw new NotCollectionException(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字段类型是否为字符串
|
||||||
|
*
|
||||||
|
* @param path 字段路径
|
||||||
|
* @param value 比较值
|
||||||
|
* @param column 字段名称
|
||||||
|
* @throws NotStringException 当字段类型不是字符串时抛出
|
||||||
|
*/
|
||||||
|
private void checkString(Path<?> path, Object value, String column) {
|
||||||
|
if (!ObjectHelper.isString(path.getJavaType()) || !ObjectHelper.isString(value)) {
|
||||||
|
throw new NotStringException(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void nullEqual(Query.Queryable queryable) {
|
||||||
|
queryable.nullEqual().forEach(column -> predicates.add(builder.isNull(column(root, column))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notNullEqual(Query.Queryable queryable) {
|
||||||
|
queryable.notNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void empty(Query.Queryable queryable) {
|
||||||
|
queryable.empty().forEach(column -> {
|
||||||
|
var path = this.<Collection<Object>>column(root, column);
|
||||||
|
checkCollection(path, column);
|
||||||
|
predicates.add(builder.isEmpty(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notEmpty(Query.Queryable queryable) {
|
||||||
|
queryable.notEmpty().forEach(column -> {
|
||||||
|
var path = this.<Collection<Object>>column(root, column);
|
||||||
|
checkCollection(path, column);
|
||||||
|
predicates.add(builder.isNotEmpty(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void equal(Query.Queryable queryable) {
|
||||||
|
queryable.equal().forEach((column, value) -> {
|
||||||
|
var path = column(root, column);
|
||||||
|
predicates.add(builder.equal(path, value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notEqual(Query.Queryable queryable) {
|
||||||
|
queryable.notEqual().forEach((column, value) -> {
|
||||||
|
var path = column(root, column);
|
||||||
|
predicates.add(builder.notEqual(path, value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void like(Query.Queryable queryable) {
|
||||||
|
queryable.like().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.like(path, value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notLike(Query.Queryable queryable) {
|
||||||
|
queryable.notLike().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.notLike(path, value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void contain(Query.Queryable queryable) {
|
||||||
|
queryable.contain().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.like(path, "%" + value + "%"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notContain(Query.Queryable queryable) {
|
||||||
|
queryable.notContain().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.notLike(path, "%" + value + "%"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startWith(Query.Queryable queryable) {
|
||||||
|
queryable.startWith().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.like(path, value + "%"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notStartWith(Query.Queryable queryable) {
|
||||||
|
queryable.notStartWith().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.notLike(path, value + "%"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void endWith(Query.Queryable queryable) {
|
||||||
|
queryable.endWith().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.like(path, "%" + value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notEndWith(Query.Queryable queryable) {
|
||||||
|
queryable.notEndWith().forEach((column, value) -> {
|
||||||
|
var path = this.<String>column(root, column);
|
||||||
|
checkString(path, value, column);
|
||||||
|
predicates.add(builder.notLike(path, "%" + value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void great(Query.Queryable queryable) {
|
||||||
|
queryable.great().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.greaterThan(path, (Comparable<Object>) value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void less(Query.Queryable queryable) {
|
||||||
|
queryable.less().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.lessThan(path, (Comparable<Object>) value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void greatEqual(Query.Queryable queryable) {
|
||||||
|
queryable.greatEqual().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.greaterThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void lessEqual(Query.Queryable queryable) {
|
||||||
|
queryable.lessEqual().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.lessThanOrEqualTo(path, (Comparable<Object>) value(path, value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inside(Query.Queryable queryable) {
|
||||||
|
queryable.inside()
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> ObjectHelper.isNotEmpty(entry.getValue()))
|
||||||
|
.forEach(entry -> predicates.add(builder.in(column(root, entry.getKey())).value(entry.getValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notInside(Query.Queryable queryable) {
|
||||||
|
queryable.notInside()
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> ObjectHelper.isNotEmpty(entry.getValue()))
|
||||||
|
.forEach(entry -> predicates.add(builder.in(column(root, entry.getKey())).value(entry.getValue()).not()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void between(Query.Queryable queryable) {
|
||||||
|
queryable.between().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void notBetween(Query.Queryable queryable) {
|
||||||
|
queryable.notBetween().forEach((column, value) -> {
|
||||||
|
var path = this.<Comparable<Object>>column(root, column);
|
||||||
|
checkComparable(path, value, column);
|
||||||
|
predicates.add(builder.between(path, (Comparable<Object>) value(path, value.start()), (Comparable<Object>) value(path, value.end())).not());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Predicate build() {
|
||||||
|
return predicates.size() == 1
|
||||||
|
? predicates.get(0)
|
||||||
|
: builder.and(predicates.toArray(Predicate[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user