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
+}