diff --git a/querydsl-criteria-builder/pom.xml b/querydsl-criteria-builder/pom.xml index 1ccc508..d24ce93 100644 --- a/querydsl-criteria-builder/pom.xml +++ b/querydsl-criteria-builder/pom.xml @@ -13,9 +13,12 @@ 0.0.1-SNAPSHOT backend backend + 21 + 2.43.0 + com.querydsl @@ -39,4 +42,40 @@ jakarta.persistence-api + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + apply + + compile + + + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + + + + + + true + 4 + + + + + + + diff --git a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/CriteriaQueryBuilder.java b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/CriteriaQueryBuilder.java index bb539c9..f0b5a87 100644 --- a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/CriteriaQueryBuilder.java +++ b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/CriteriaQueryBuilder.java @@ -6,12 +6,10 @@ import com.querydsl.jpa.impl.JPAQuery; import dev.pablolec.querybuilder.model.SearchCriterion; import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; - import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.List; - +import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class CriteriaQueryBuilder { @@ -34,7 +32,8 @@ public JPAQuery buildQuery(List criteria, String entityPathS return addCriteriaToQuery(criteria, rootEntityPath, query); } - private JPAQuery addCriteriaToQuery(List criteria, EntityPathBase rootEntityPath, JPAQuery query) { + private JPAQuery addCriteriaToQuery( + List criteria, EntityPathBase rootEntityPath, JPAQuery query) { criteria.stream() .map(criterion -> getBooleanExpression(criterion, rootEntityPath)) .forEach(query::where); @@ -47,7 +46,8 @@ private BooleanExpression getBooleanExpression(SearchCriterion criterion, Entity validateSubQueryCriteria(criterion.getSubCriteria()); JPAQuery subQuery = buildSubQuery(rootEntityPath, criterion.getField()); - EntityPathBase childEntityPath = entityPathResolver.getEntityPathBase(getFieldPathParts(criterion.getField()).getLast()); + EntityPathBase childEntityPath = entityPathResolver.getEntityPathBase( + getFieldPathParts(criterion.getField()).getLast()); addCriteriaToQuery(criterion.getSubCriteria(), childEntityPath, subQuery); return ExpressionBuilder.buildSubQueryExpression(criterion, subQuery); @@ -99,26 +99,21 @@ private EntityPathBase applyJoin(JPAQuery subQuery, EntityPathBase curr } private BooleanExpression getJoinCondition(EntityPathBase parentEntityPath, EntityPathBase childEntityPath) { - try { - Class parentEntityClass = parentEntityPath.getType(); - for (Field field : parentEntityClass.getDeclaredFields()) { - Class fieldType = field.getType(); - - if (fieldType.equals(childEntityPath.getType())) { - return getDirectJoinCondition(parentEntityClass, parentEntityPath, childEntityPath, field); - } - - if (Iterable.class.isAssignableFrom(fieldType)) { - ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); - Class collectionType = (Class) parameterizedType.getActualTypeArguments()[0]; - - if (collectionType.equals(childEntityPath.getType())) { - return getCollectionJoinCondition(parentEntityClass, parentEntityPath, childEntityPath, field); - } - } + Class parentEntityClass = parentEntityPath.getType(); + + for (Field field : parentEntityClass.getDeclaredFields()) { + PathBuilder parentEntityBuilder = new PathBuilder<>( + parentEntityPath.getType(), parentEntityPath.getMetadata().getName()); + PathBuilder childEntityBuilder = new PathBuilder<>( + childEntityPath.getType(), childEntityPath.getMetadata().getName()); + + if (isDirectJoinApplicable(field, childEntityPath)) { + return buildDirectJoinCondition(parentEntityBuilder, childEntityBuilder, field); + } + + if (isCollectionJoinApplicable(field, childEntityPath)) { + return buildCollectionJoinCondition(parentEntityBuilder, childEntityBuilder, field); } - } catch (Exception e) { - throw new IllegalStateException("Error finding join condition between entities", e); } throw new IllegalStateException("No valid join condition found between entities " @@ -127,14 +122,30 @@ private BooleanExpression getJoinCondition(EntityPathBase parentEntityPath, E + childEntityPath.getType().getSimpleName()); } - private BooleanExpression getDirectJoinCondition(Class parentEntityClass, EntityPathBase parentEntityPath, EntityPathBase childEntityPath, Field field) { - PathBuilder parentEntityBuilder = new PathBuilder<>(parentEntityClass, parentEntityPath.getMetadata().getName()); - return parentEntityBuilder.get(field.getName()).get("id").eq(new PathBuilder<>(childEntityPath.getType(), childEntityPath.getMetadata().getName()).get("id")); + private boolean isDirectJoinApplicable(Field field, EntityPathBase childEntityPath) { + return field.getType().equals(childEntityPath.getType()); } - private BooleanExpression getCollectionJoinCondition(Class parentEntityClass, EntityPathBase parentEntityPath, EntityPathBase childEntityPath, Field field) { - PathBuilder parentEntityBuilder = new PathBuilder<>(parentEntityClass, parentEntityPath.getMetadata().getName()); - return parentEntityBuilder.getCollection(field.getName(), parentEntityClass).any().get("id").eq(new PathBuilder<>(childEntityPath.getType(), childEntityPath.getMetadata().getName()).get("id")); + private boolean isCollectionJoinApplicable(Field field, EntityPathBase childEntityPath) { + if (Iterable.class.isAssignableFrom(field.getType())) { + ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); + Class collectionType = (Class) parameterizedType.getActualTypeArguments()[0]; + return collectionType.equals(childEntityPath.getType()); + } + return false; } -} + private BooleanExpression buildDirectJoinCondition( + PathBuilder parentEntityBuilder, PathBuilder childEntityBuilder, Field field) { + return parentEntityBuilder.get(field.getName()).get("id").eq(childEntityBuilder.get("id")); + } + + private BooleanExpression buildCollectionJoinCondition( + PathBuilder parentEntityBuilder, PathBuilder childEntityBuilder, Field field) { + return parentEntityBuilder + .getCollection(field.getName(), field.getType()) + .any() + .get("id") + .eq(childEntityBuilder.get("id")); + } +} diff --git a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/DynamicFieldCaster.java b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/DynamicFieldCaster.java index a22883c..8d2a8d1 100644 --- a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/DynamicFieldCaster.java +++ b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/DynamicFieldCaster.java @@ -1,13 +1,12 @@ package dev.pablolec.querybuilder; -import lombok.experimental.UtilityClass; - import java.lang.reflect.Field; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.List; +import lombok.experimental.UtilityClass; @UtilityClass class DynamicFieldCaster { @@ -53,7 +52,6 @@ private static Field getField(Class type, String fieldName) throws NoSuchFiel throw new NoSuchFieldException("Field not found: " + fieldName); } - private static Object castSingleValue(Class fieldType, String value) { String typeName = fieldType.getCanonicalName(); return switch (typeName) { @@ -62,13 +60,16 @@ private static Object castSingleValue(Class fieldType, String value) { case "java.time.LocalDate" -> LocalDate.parse(value); case "java.time.LocalDateTime" -> LocalDateTime.parse(value); case "java.lang.String" -> value; - default -> - throw new IllegalArgumentException("Unsupported field type for dynamic casting: " + fieldType.getSimpleName()); + default -> throw new IllegalArgumentException( + "Unsupported field type for dynamic casting: " + fieldType.getSimpleName()); }; } private static List castCollection(Class fieldType, String value) { String[] elements = value.replace("[", "").replace("]", "").split(","); - return Arrays.stream(elements).map(element -> castSingleValue(fieldType, element.trim())).toList(); + + return Arrays.stream(elements) + .map(element -> castSingleValue(fieldType, element.trim())) + .toList(); } -} \ No newline at end of file +} diff --git a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/EntityPathResolver.java b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/EntityPathResolver.java index a51496b..ec6c64e 100644 --- a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/EntityPathResolver.java +++ b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/EntityPathResolver.java @@ -1,7 +1,6 @@ package dev.pablolec.querybuilder; import com.querydsl.core.types.dsl.EntityPathBase; - import java.util.Collections; import java.util.Map; @@ -39,5 +38,4 @@ public EntityPathBase getEntityPathBase(Class targetClass) { throw new IllegalStateException("No matching EntityPathBase found for '" + targetClass.getSimpleName() + "'"); } - } diff --git a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/ExpressionBuilder.java b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/ExpressionBuilder.java index f6700d4..ccade61 100644 --- a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/ExpressionBuilder.java +++ b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/ExpressionBuilder.java @@ -4,17 +4,18 @@ import com.querydsl.jpa.impl.JPAQuery; import dev.pablolec.querybuilder.model.Operator; import dev.pablolec.querybuilder.model.SearchCriterion; -import lombok.experimental.UtilityClass; - import java.time.temporal.TemporalAccessor; import java.util.List; +import lombok.experimental.UtilityClass; @UtilityClass public class ExpressionBuilder { public static BooleanExpression buildExpression(SearchCriterion criterion, EntityPathBase rootEntityPath) { - PathBuilder pathBuilder = new PathBuilder<>(rootEntityPath.getType(), rootEntityPath.getMetadata().getName()); + PathBuilder pathBuilder = new PathBuilder<>( + rootEntityPath.getType(), rootEntityPath.getMetadata().getName()); Operator operator = Operator.fromString(criterion.getOp()); - Object castedValue = DynamicFieldCaster.castValue(pathBuilder.getType(), criterion.getField(), criterion.getValue(), operator.isCollectionOperator()); + Object castedValue = DynamicFieldCaster.castValue( + pathBuilder.getType(), criterion.getField(), criterion.getValue(), operator.isCollectionOperator()); return switch (operator) { case EQ, NE -> handleBasicComparisons(pathBuilder, criterion.getField(), castedValue, operator); @@ -22,14 +23,14 @@ public static BooleanExpression buildExpression(SearchCriterion criterion, Entit if (!(castedValue instanceof String)) { throw new IllegalArgumentException(operator + " operator is only valid for String types."); } - yield operator == Operator.LIKE ? - pathBuilder.getString(criterion.getField()).like((String) castedValue) : - pathBuilder.getString(criterion.getField()).notLike((String) castedValue); + yield operator == Operator.LIKE + ? pathBuilder.getString(criterion.getField()).like((String) castedValue) + : pathBuilder.getString(criterion.getField()).notLike((String) castedValue); } - case GT, LT, GTE, LTE -> - handleComparisonOperators(pathBuilder, criterion.getField(), castedValue, operator); - case IN, NOT_IN -> - handleCollectionExpression(pathBuilder, criterion.getField(), (List) castedValue, operator); + case GT, LT, GTE, LTE -> handleComparisonOperators( + pathBuilder, criterion.getField(), castedValue, operator); + case IN, NOT_IN -> handleCollectionExpression( + pathBuilder, criterion.getField(), (List) castedValue, operator); default -> throw new IllegalArgumentException("Unsupported operator: " + operator); }; } @@ -43,7 +44,8 @@ public static BooleanExpression buildSubQueryExpression(SearchCriterion criterio }; } - private static BooleanExpression handleBasicComparisons(PathBuilder entityPath, String fieldName, Object value, Operator operator) { + private static BooleanExpression handleBasicComparisons( + PathBuilder entityPath, String fieldName, Object value, Operator operator) { return switch (operator) { case EQ -> entityPath.get(fieldName).eq(value); case NE -> entityPath.get(fieldName).ne(value); @@ -51,21 +53,24 @@ private static BooleanExpression handleBasicComparisons(PathBuilder entityPat }; } - private static BooleanExpression handleComparisonOperators(PathBuilder entityPath, String fieldName, Object value, Operator operator) { + private static BooleanExpression handleComparisonOperators( + PathBuilder entityPath, String fieldName, Object value, Operator operator) { if (!(value instanceof Comparable)) { - throw new IllegalArgumentException("Comparison operators are only supported for Comparable types. Field: " + fieldName + ", Value type: " + value.getClass().getSimpleName()); + throw new IllegalArgumentException("Comparison operators are only supported for Comparable types. Field: " + + fieldName + ", Value type: " + value.getClass().getSimpleName()); } return switch (value) { case Number numberValue -> compareUsingGenericNumberPath(entityPath, fieldName, numberValue, operator); - case TemporalAccessor temporalValue -> - compareUsingGenericTemporalPath(entityPath, fieldName, temporalValue, operator); - default -> - throw new IllegalArgumentException("Comparison operators are not supported for the type of " + fieldName + ": " + value.getClass().getSimpleName()); + case TemporalAccessor temporalValue -> compareUsingGenericTemporalPath( + entityPath, fieldName, temporalValue, operator); + default -> throw new IllegalArgumentException("Comparison operators are not supported for the type of " + + fieldName + ": " + value.getClass().getSimpleName()); }; } - private static BooleanExpression handleCollectionExpression(PathBuilder entityPath, String fieldName, List value, Operator operator) { + private static BooleanExpression handleCollectionExpression( + PathBuilder entityPath, String fieldName, List value, Operator operator) { return switch (operator) { case IN -> entityPath.get(fieldName).in(value); case NOT_IN -> entityPath.get(fieldName).notIn(value); @@ -73,8 +78,10 @@ private static BooleanExpression handleCollectionExpression(PathBuilder entit }; } - private static > BooleanExpression compareUsingGenericNumberPath(PathBuilder entityPath, String fieldName, Number numberValue, Operator operator) { - @SuppressWarnings("unchecked") Class type = (Class) numberValue.getClass(); + private static > BooleanExpression compareUsingGenericNumberPath( + PathBuilder entityPath, String fieldName, Number numberValue, Operator operator) { + @SuppressWarnings("unchecked") + Class type = (Class) numberValue.getClass(); NumberPath path = entityPath.getNumber(fieldName, type); N castedValue = type.cast(numberValue); @@ -87,8 +94,10 @@ private static > BooleanExpression compareUsing }; } - private static > BooleanExpression compareUsingGenericTemporalPath(PathBuilder entityPath, String fieldName, TemporalAccessor temporalValue, Operator operator) { - @SuppressWarnings("unchecked") Class type = (Class) temporalValue.getClass(); + private static > BooleanExpression compareUsingGenericTemporalPath( + PathBuilder entityPath, String fieldName, TemporalAccessor temporalValue, Operator operator) { + @SuppressWarnings("unchecked") + Class type = (Class) temporalValue.getClass(); TemporalExpression path = entityPath.getDateTime(fieldName, type); T castedValue = type.cast(temporalValue); diff --git a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/model/SearchCriterion.java b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/model/SearchCriterion.java index ca00581..7e70a5a 100644 --- a/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/model/SearchCriterion.java +++ b/querydsl-criteria-builder/src/main/java/dev/pablolec/querybuilder/model/SearchCriterion.java @@ -1,10 +1,9 @@ package dev.pablolec.querybuilder.model; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; -import java.util.List; - @Data @AllArgsConstructor public class SearchCriterion { @@ -29,4 +28,4 @@ public SearchCriterion(String field, String op, List subCriteri this.subQuery = true; this.subCriteria = subCriteria; } -} \ No newline at end of file +}