From 42f1d896af6df60df47480bfa8c0bba45a615900 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Fri, 15 Aug 2025 13:04:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 52 ++++++- .../service/template/controller/Query.java | 12 +- .../service/template/helper/ObjectHelper.java | 68 +++++++++ .../template/repository/SimpleRepository.java | 3 +- .../service/SimpleServiceSupport.java | 132 ++++++++++++++++-- .../service/template/entity/Company.java | 1 - 6 files changed, 246 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index e6fdf7c..6d8be48 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 3.4.3 2024.0.1 - 11.1.0 + 5.1.0 @@ -30,7 +30,21 @@ com.blinkfox fenix-spring-boot-starter - 3.0.0 + 3.1.0 + + + + com.querydsl + querydsl-apt + ${querydsl.version} + provided + jakarta + + + com.querydsl + querydsl-jpa + ${querydsl.version} + jakarta @@ -60,6 +74,40 @@ + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + compile-dsl + compile + + process + + + com.querydsl.apt.jpa.JPAAnnotationProcessor + target/generated-sources/java + + + + test-compile-dsl + test-compile + + test-process + + + com.querydsl.apt.jpa.JPAAnnotationProcessor + target/generated-test-sources/java + + + + + + + ${releases.id} diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java b/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java index d9af1df..1468bd5 100644 --- a/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java +++ b/src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java @@ -210,22 +210,22 @@ public class Query { } public static class Between { - private String start; - private String end; + private Object start; + private Object end; - public String getStart() { + public Object getStart() { return start; } - public void setStart(String start) { + public void setStart(Object start) { this.start = start; } - public String getEnd() { + public Object getEnd() { return end; } - public void setEnd(String end) { + public void setEnd(Object end) { this.end = end; } diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java b/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java index 1f2b29f..8c6a88e 100644 --- a/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java +++ b/src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.java @@ -5,19 +5,44 @@ import java.util.Map; import java.util.Optional; public class ObjectHelper { + /** + * 判断对象是否为null + * + * @param obj 待检查的对象 + * @return 如果对象为null返回true,否则返回false + */ public static boolean isNull(Object obj) { return obj == null; } + + /** + * 判断对象是否不为null + * + * @param obj 待判断的对象 + * @return 如果对象不为null则返回true,否则返回false + */ public static boolean isNotNull(Object obj) { return !isNull(obj); } + + /** + * 判断对象是否为空 + * + * @param obj 待判断的对象 + * @return 如果对象为null或为空则返回true,否则返回false + */ public static boolean isEmpty(Object obj) { + // 首先判断对象是否为null if (isNull(obj)) return true; + // 判断是否为集合类型 else if (obj instanceof Collection collection) return collection.isEmpty(); + // 判断是否为Map类型 else if (obj instanceof Map map) return map.isEmpty(); + // 判断是否为字符序列类型 else if (obj instanceof CharSequence sequence) return sequence.isEmpty(); + // 判断是否为各种基本类型数组 else if (obj instanceof Object[] array) return array.length == 0; else if (obj instanceof byte[] array) return array.length == 0; else if (obj instanceof short[] array) return array.length == 0; @@ -27,10 +52,13 @@ public class ObjectHelper { else if (obj instanceof double[] array) return array.length == 0; else if (obj instanceof char[] array) return array.length == 0; else if (obj instanceof boolean[] array) return array.length == 0; + // 判断是否为Optional类型 else if (obj instanceof Optional optional) return optional.isEmpty(); + // 其他情况认为对象不为空 else return false; } + public static boolean isNotEmpty(Object obj) { return !isEmpty(obj); } @@ -38,4 +66,44 @@ public class ObjectHelper { public static T defaultIfNull(final T object, final T defaultValue) { return isNull(object) ? defaultValue : object; } + + /** + * 判断给定的类是否可比较 + * + * @param clazz 待判断的类对象 + * @return 如果类是枚举、字符序列、可比较接口的实现类或基本数据类型则返回true,否则返回false + */ + public static boolean isComparable(Class clazz) { + if (isNull(clazz)) return false; + // 判断类是否为可比较类型:枚举、字符序列、可比较接口实现类或基本数据类型 + return clazz.isEnum() || + CharSequence.class.isAssignableFrom(clazz) || + Comparable.class.isAssignableFrom(clazz) || + clazz.isPrimitive(); + } + + public static boolean isComparable(Object obj) { + if (isNull(obj)) return false; + return isComparable(obj.getClass()); + } + + public static boolean isCollection(Class clazz) { + if (isNull(clazz)) return false; + return Collection.class.isAssignableFrom(clazz); + } + + public static boolean isCollection(Object obj) { + if (isNull(obj)) return false; + return isCollection(obj.getClass()); + } + + public static boolean isString(Class clazz) { + if (isNull(clazz)) return false; + return String.class.isAssignableFrom(clazz); + } + + public static boolean isString(Object obj) { + if (isNull(obj)) return false; + return isString(obj.getClass()); + } } \ No newline at end of file diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java b/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java index 6c3c2a8..3d5eed2 100644 --- a/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java +++ b/src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java @@ -2,9 +2,10 @@ package com.lanyuanxiaoyao.service.template.repository; import com.blinkfox.fenix.jpa.FenixJpaRepository; import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.query.QueryByExampleExecutor; @NoRepositoryBean -public interface SimpleRepository extends FenixJpaRepository, FenixJpaSpecificationExecutor, QueryByExampleExecutor { +public interface SimpleRepository extends FenixJpaRepository, FenixJpaSpecificationExecutor, QueryByExampleExecutor, QuerydslPredicateExecutor { } diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java index 841f732..0516b2c 100644 --- a/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java +++ b/src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java @@ -11,6 +11,7 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.transaction.Transactional; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -101,7 +102,7 @@ public abstract class SimpleServiceSupport implemen var enumType = (Class) javaType; return Enum.valueOf(enumType, enumName); } else { - throw new IllegalArgumentException("枚举类型字段需要String类型的值"); + throw new IllegalArgumentException("枚举类型字段需要 String 类型的值"); } } return value; @@ -120,10 +121,18 @@ public abstract class SimpleServiceSupport implemen queryable.getNotNullEqual().forEach(column -> predicates.add(builder.isNotNull(column(root, column)))); } if (ObjectHelper.isNotEmpty(queryable.getEmpty())) { - queryable.getEmpty().forEach(column -> predicates.add(builder.isEmpty(column(root, column)))); + queryable.getEmpty().forEach(column -> { + var path = this.>column(root, column); + checkCollection(path, column); + predicates.add(builder.isEmpty(path)); + }); } if (ObjectHelper.isNotEmpty(queryable.getNotEmpty())) { - queryable.getNotEmpty().forEach(column -> predicates.add(builder.isNotEmpty(column(root, column)))); + queryable.getNotEmpty().forEach(column -> { + var path = this.>column(root, column); + checkCollection(path, column); + predicates.add(builder.isNotEmpty(path)); + }); } if (ObjectHelper.isNotEmpty(queryable.getEqual())) { queryable.getEqual().forEach((column, value) -> { @@ -138,22 +147,52 @@ public abstract class SimpleServiceSupport implemen }); } if (ObjectHelper.isNotEmpty(queryable.getLike())) { - queryable.getLike().forEach((column, value) -> predicates.add(builder.like(column(root, column), value))); + queryable.getLike().forEach((column, value) -> { + var path = column(root, column); + checkString(path, column); + checkString(value, column); + predicates.add(builder.like(column(root, column), value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getNotLike())) { - queryable.getNotLike().forEach((column, value) -> predicates.add(builder.notLike(column(root, column), value))); + queryable.getNotLike().forEach((column, value) -> { + var path = column(root, column); + checkString(path, column); + checkString(value, column); + predicates.add(builder.notLike(column(root, column), value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getGreat())) { - queryable.getGreat().forEach((column, value) -> predicates.add(builder.greaterThan(column(root, column), (Comparable) value))); + queryable.getGreat().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.greaterThan(path, (Comparable) value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getLess())) { - queryable.getLess().forEach((column, value) -> predicates.add(builder.lessThan(column(root, column), (Comparable) value))); + queryable.getLess().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.lessThan(path, (Comparable) value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getGreatEqual())) { - queryable.getGreatEqual().forEach((column, value) -> predicates.add(builder.greaterThanOrEqualTo(column(root, column), (Comparable) value))); + queryable.getGreatEqual().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.greaterThanOrEqualTo(path, (Comparable) value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getLessEqual())) { - queryable.getLessEqual().forEach((column, value) -> predicates.add(builder.lessThanOrEqualTo(column(root, column), (Comparable) value))); + queryable.getLessEqual().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.lessThanOrEqualTo(path, (Comparable) value)); + }); } if (ObjectHelper.isNotEmpty(queryable.getIn())) { queryable.getIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value))); @@ -162,14 +201,65 @@ public abstract class SimpleServiceSupport implemen queryable.getNotIn().forEach((column, value) -> predicates.add(builder.in(column(root, column)).value(value).not())); } if (ObjectHelper.isNotEmpty(queryable.getBetween())) { - queryable.getBetween().forEach((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd()))); + queryable.getBetween().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.between(column(root, column), (Comparable) value.getStart(), (Comparable) value.getEnd())); + }); } if (ObjectHelper.isNotEmpty(queryable.getNotBetween())) { - queryable.getNotBetween().forEach((column, value) -> predicates.add(builder.between(column(root, column), value.getStart(), value.getEnd()))); + queryable.getNotBetween().forEach((column, value) -> { + var path = this.>column(root, column); + checkComparable(path, column); + checkComparable(value, column); + predicates.add(builder.between(column(root, column), (Comparable) value.getStart(), (Comparable) value.getEnd()).not()); + }); } return predicates; } + private void checkComparable(Path path, String column) { + if (!ObjectHelper.isComparable(path.getJavaType())) { + throw new NotComparableException(column); + } + } + + private void checkComparable(Object value, String column) { + if (!ObjectHelper.isComparable(value)) { + throw new NotComparableException(column); + } + } + + private void checkComparable(Query.Queryable.Between value, String column) { + checkComparable(value.getStart(), column); + checkComparable(value.getEnd(), column); + } + + private void checkCollection(Path path, String column) { + if (!ObjectHelper.isCollection(path.getJavaType())) { + throw new NotCollectionException(column); + } + } + + private void checkCollection(Object value, String column) { + if (!ObjectHelper.isCollection(value)) { + throw new NotCollectionException(column); + } + } + + private void checkString(Path path, String column) { + if (!ObjectHelper.isString(path.getJavaType())) { + throw new NotStringException(column); + } + } + + private void checkString(Object value, String column) { + if (!ObjectHelper.isString(value)) { + throw new NotStringException(column); + } + } + protected List listPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { return new ArrayList<>(0); } @@ -237,7 +327,25 @@ public abstract class SimpleServiceSupport implemen } public IdNotFoundException(Long id) { - super("ID为%d的资源不存在".formatted(id)); + super("ID为 %d 的资源不存在".formatted(id)); + } + } + + public static final class NotComparableException extends RuntimeException { + public NotComparableException(String variable) { + super("变量 %s 不能比较".formatted(variable)); + } + } + + public static final class NotCollectionException extends RuntimeException { + public NotCollectionException(String variable) { + super("变量 %s 不是集合".formatted(variable)); + } + } + + public static final class NotStringException extends RuntimeException { + public NotStringException(String variable) { + super("变量 %s 不是字符串".formatted(variable)); } } } diff --git a/src/test/java/com/lanyuanxiaoyao/service/template/entity/Company.java b/src/test/java/com/lanyuanxiaoyao/service/template/entity/Company.java index 10c9533..b0ed490 100644 --- a/src/test/java/com/lanyuanxiaoyao/service/template/entity/Company.java +++ b/src/test/java/com/lanyuanxiaoyao/service/template/entity/Company.java @@ -6,7 +6,6 @@ import org.hibernate.annotations.Comment; import org.hibernate.annotations.DynamicUpdate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; - @Entity @DynamicUpdate @EntityListeners(AuditingEntityListener.class)