From f439381e04ffcdb26d18caad94c00dbb94f33940 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Wed, 7 Jan 2026 13:02:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor(service):=20=E5=B0=86=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=9D=A1=E4=BB=B6=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=B0=81=E8=A3=85=E5=88=B0=20QueryParser=20=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/controller/QueryController.java | 3 + .../common/controller/RemoveController.java | 2 + .../common/controller/SaveController.java | 2 + .../GlobalResponse.java | 2 +- .../common/{service => entity}/Page.java | 2 +- .../common/{controller => entity}/Query.java | 2 +- .../template/common/service/QueryParser.java | 125 ++++ .../template/common/service/QueryService.java | 3 +- .../controller/SimpleControllerSupport.java | 4 +- .../jpa/service/SimpleServiceSupport.java | 636 ++++++++++-------- 10 files changed, 478 insertions(+), 303 deletions(-) rename spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/{controller => entity}/GlobalResponse.java (99%) rename spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/{service => entity}/Page.java (93%) rename spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/{controller => entity}/Query.java (99%) create mode 100644 spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryParser.java diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/QueryController.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/QueryController.java index 4b6867f..87572aa 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/QueryController.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/QueryController.java @@ -1,5 +1,8 @@ package com.lanyuanxiaoyao.service.template.common.controller; +import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse; +import com.lanyuanxiaoyao.service.template.common.entity.Query; + /** * 查询控制器接口,用于定义统一的查询实体详情和列表的接口规范 *

diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/RemoveController.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/RemoveController.java index 93a43a8..34ebe04 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/RemoveController.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/RemoveController.java @@ -1,5 +1,7 @@ package com.lanyuanxiaoyao.service.template.common.controller; +import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse; + /** * 删除控制器接口,用于定义统一的删除实体对象的接口规范 *

diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/SaveController.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/SaveController.java index 18130e4..c14f421 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/SaveController.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/SaveController.java @@ -1,5 +1,7 @@ package com.lanyuanxiaoyao.service.template.common.controller; +import com.lanyuanxiaoyao.service.template.common.entity.GlobalResponse; + /** * 保存控制器接口,用于定义统一的保存实体对象的接口规范 *

diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/GlobalResponse.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/GlobalResponse.java similarity index 99% rename from spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/GlobalResponse.java rename to spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/GlobalResponse.java index c61f768..7685580 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/GlobalResponse.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/GlobalResponse.java @@ -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.Map; diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/Page.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Page.java similarity index 93% rename from spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/Page.java rename to spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Page.java index d624929..3026cb9 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/Page.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Page.java @@ -1,4 +1,4 @@ -package com.lanyuanxiaoyao.service.template.common.service; +package com.lanyuanxiaoyao.service.template.common.entity; import java.util.stream.Stream; diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/Query.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Query.java similarity index 99% rename from spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/Query.java rename to spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Query.java index c395358..1fd00dd 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/controller/Query.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/entity/Query.java @@ -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.Map; diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryParser.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryParser.java new file mode 100644 index 0000000..e688edb --- /dev/null +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryParser.java @@ -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 { + 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(); + } +} diff --git a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryService.java b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryService.java index efda3d4..a2bec1d 100644 --- a/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryService.java +++ b/spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/service/QueryService.java @@ -1,6 +1,7 @@ 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.Set; diff --git a/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/controller/SimpleControllerSupport.java b/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/controller/SimpleControllerSupport.java index 88e45c5..a790481 100644 --- a/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/controller/SimpleControllerSupport.java +++ b/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/controller/SimpleControllerSupport.java @@ -1,8 +1,8 @@ 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.entity.GlobalResponse; +import com.lanyuanxiaoyao.service.template.common.entity.Query; import com.lanyuanxiaoyao.service.template.common.helper.ObjectHelper; import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity; import com.lanyuanxiaoyao.service.template.jpa.service.SimpleServiceSupport; diff --git a/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/service/SimpleServiceSupport.java b/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/service/SimpleServiceSupport.java index 1c1a71c..bec8969 100644 --- a/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/service/SimpleServiceSupport.java +++ b/spring-boot-service-template-jpa/src/main/java/com/lanyuanxiaoyao/service/template/jpa/service/SimpleServiceSupport.java @@ -1,12 +1,13 @@ 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.NotCollectionException; import com.lanyuanxiaoyao.service.template.common.exception.NotComparableException; import com.lanyuanxiaoyao.service.template.common.exception.NotStringException; 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.jpa.entity.IdOnlyEntity; import com.lanyuanxiaoyao.service.template.jpa.entity.SimpleEntity; @@ -159,300 +160,6 @@ public abstract class SimpleServiceSupport implemen ); } - /** - * 解析字段路径 - *

- * 支持多级字段路径解析,使用"."分隔多级字段。 - * 例如: "user.name" 表示实体的user属性的name字段。 - *

- * - * @param root JPA Criteria查询根节点 - * @param column 字段路径字符串 - * @param 字段类型 - * @return 返回字段路径对象 - * @throws IllegalArgumentException 当字段路径为空时抛出 - */ - private Path column(Root root, String column) { - if (ObjectHelper.isEmpty(column)) { - throw new IllegalArgumentException("Column cannot be blank"); - } - var columns = column.split("\\."); - Path path = root.get(columns[0]); - for (int i = 1; i < columns.length; i++) { - path = path.get(columns[i]); - } - return path; - } - - /** - * 处理字段值 - *

- * 对于枚举类型字段,将字符串值转换为对应的枚举值。 - * 对于LocalDateTime类型字段,将字符串转换为时间对象。 - * 其他类型直接返回原值。 - *

- * - * @param column 字段路径 - * @param value 字段值 - * @param 字段类型 - * @return 处理后的字段值 - * @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出 - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private Object value(Path column, Object value) { - if (ObjectHelper.isNull(value)) { - return null; - } - var javaType = column.getJavaType(); - if (javaType.isEnum()) { - if (value instanceof String enumName) { - var enumType = (Class) 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; - } - - /** - * 构建查询条件谓词列表 - *

- * 根据Query.Queryable对象构建JPA Criteria查询的谓词列表。 - * 支持多种查询条件类型,包括相等、不等、模糊匹配、范围查询等。 - *

- * - * @param queryable 查询条件对象 - * @param root JPA Criteria查询根节点 - * @param query JPA Criteria查询对象 - * @param builder JPA Criteria构建器 - * @return 返回构建的谓词列表 - */ - @SuppressWarnings("unchecked") - private Predicate queryPredicates(Query.Queryable queryable, Root root, CriteriaQuery query, CriteriaBuilder builder) { - var predicates = new ArrayList(); - 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.>column(root, column); - checkCollection(path, column); - predicates.add(builder.isEmpty(path)); - }); - } - if (ObjectHelper.isNotEmpty(queryable.notEmpty())) { - queryable.notEmpty().forEach(column -> { - var path = this.>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.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.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.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.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.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.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.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.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.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.greaterThan(path, (Comparable) value(path, value))); - }); - } - if (ObjectHelper.isNotEmpty(queryable.less())) { - queryable.less().forEach((column, value) -> { - var path = this.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.lessThan(path, (Comparable) value(path, value))); - }); - } - if (ObjectHelper.isNotEmpty(queryable.greatEqual())) { - queryable.greatEqual().forEach((column, value) -> { - var path = this.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.greaterThanOrEqualTo(path, (Comparable) value(path, value))); - }); - } - if (ObjectHelper.isNotEmpty(queryable.lessEqual())) { - queryable.lessEqual().forEach((column, value) -> { - var path = this.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.lessThanOrEqualTo(path, (Comparable) 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.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.between(path, (Comparable) value(path, value.start()), (Comparable) value(path, value.end()))); - }); - } - if (ObjectHelper.isNotEmpty(queryable.notBetween())) { - queryable.notBetween().forEach((column, value) -> { - var path = this.>column(root, column); - checkComparable(path, value, column); - predicates.add(builder.between(path, (Comparable) value(path, value.start()), (Comparable) 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 root, CriteriaQuery query, CriteriaBuilder builder) { return null; } @@ -487,7 +194,7 @@ public abstract class SimpleServiceSupport implemen var result = repository.findAll( (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) ? queryPredicate : builder.and(predicate, queryPredicate); @@ -586,4 +293,339 @@ public abstract class SimpleServiceSupport implemen repository.deleteBatchByIds(ids); } } + + @SuppressWarnings("unchecked") + private static final class JpaQueryParser extends QueryParser { + private final Root root; + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + private final CriteriaQuery query; + private final CriteriaBuilder builder; + private final List predicates = new ArrayList<>(); + + private JpaQueryParser(Root root, CriteriaQuery query, CriteriaBuilder builder) { + this.root = root; + this.query = query; + this.builder = builder; + } + + /** + * 解析字段路径 + *

+ * 支持多级字段路径解析,使用"."分隔多级字段。 + * 例如: "user.name" 表示实体的user属性的name字段。 + *

+ * + * @param root JPA Criteria查询根节点 + * @param column 字段路径字符串 + * @param 字段类型 + * @return 返回字段路径对象 + * @throws IllegalArgumentException 当字段路径为空时抛出 + */ + private Path column(Root root, String column) { + if (ObjectHelper.isEmpty(column)) { + throw new IllegalArgumentException("Column cannot be blank"); + } + var columns = column.split("\\."); + Path path = root.get(columns[0]); + for (int i = 1; i < columns.length; i++) { + path = path.get(columns[i]); + } + return path; + } + + /** + * 处理字段值 + *

+ * 对于枚举类型字段,将字符串值转换为对应的枚举值。 + * 对于LocalDateTime类型字段,将字符串转换为时间对象。 + * 其他类型直接返回原值。 + *

+ * + * @param column 字段路径 + * @param value 字段值 + * @param 字段类型 + * @return 处理后的字段值 + * @throws IllegalArgumentException 当枚举类型字段的值不是字符串时抛出 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private Object value(Path column, Object value) { + if (ObjectHelper.isNull(value)) { + return null; + } + var javaType = column.getJavaType(); + if (javaType.isEnum()) { + if (value instanceof String enumName) { + var enumType = (Class) 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.>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.>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.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.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.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.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.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.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.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.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.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.greaterThan(path, (Comparable) value(path, value))); + }); + } + + @Override + protected void less(Query.Queryable queryable) { + queryable.less().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.lessThan(path, (Comparable) value(path, value))); + }); + } + + @Override + protected void greatEqual(Query.Queryable queryable) { + queryable.greatEqual().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.greaterThanOrEqualTo(path, (Comparable) value(path, value))); + }); + } + + @Override + protected void lessEqual(Query.Queryable queryable) { + queryable.lessEqual().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.lessThanOrEqualTo(path, (Comparable) 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.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.between(path, (Comparable) value(path, value.start()), (Comparable) value(path, value.end()))); + }); + } + + @Override + protected void notBetween(Query.Queryable queryable) { + queryable.notBetween().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, value, column); + predicates.add(builder.between(path, (Comparable) value(path, value.start()), (Comparable) value(path, value.end())).not()); + }); + } + + @Override + protected Predicate build() { + return predicates.size() == 1 + ? predicates.get(0) + : builder.and(predicates.toArray(Predicate[]::new)); + } + } } \ No newline at end of file