diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java
index e29ef4888..0265b5549 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java
@@ -29,6 +29,7 @@ import jakarta.persistence.criteria.Fetch;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
+import jakarta.persistence.criteria.Path;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
import jakarta.persistence.metamodel.Bindable;
@@ -824,45 +825,18 @@ public abstract class QueryUtils {
private static boolean requiresOuterJoin(From, ?> from, PropertyPath property, boolean isForSelection,
boolean hasRequiredOuterJoin) {
- String segment = property.getSegment();
-
// already inner joined so outer join is useless
- if (isAlreadyInnerJoined(from, segment))
+ if (isAlreadyInnerJoined(from, property.getSegment())) {
return false;
+ }
- Bindable> propertyPathModel;
Bindable> model = from.getModel();
-
- // required for EclipseLink: we try to avoid using from.get as EclipseLink produces an inner join
- // regardless of which join operation is specified next
- // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892
- // still occurs as of 2.7
- ManagedType> managedType = null;
- if (model instanceof ManagedType) {
- managedType = (ManagedType>) model;
- } else if (model instanceof SingularAttribute
- && ((SingularAttribute, ?>) model).getType() instanceof ManagedType) {
- managedType = (ManagedType>) ((SingularAttribute, ?>) model).getType();
- }
- if (managedType != null) {
- try {
- propertyPathModel = (Bindable>) managedType.getAttribute(segment);
- } catch (IllegalArgumentException ex) {
- // ManagedType may be erased type for some vendor if the attribute is declared as generic
- // see: https://hibernate.atlassian.net/browse/HHH-16144
- // see: https://github.com/hibernate/hibernate-orm/pull/7630
- // see: https://github.com/jakartaee/persistence/issues/562
- propertyPathModel = from.get(segment).getModel();
- }
- } else {
- propertyPathModel = from.get(segment).getModel();
- }
+ ManagedType> managedType = getManagedTypeForModel(model);
+ Bindable> propertyPathModel = getModelForPath(property, managedType, from);
// is the attribute of Collection type?
boolean isPluralAttribute = model instanceof PluralAttribute;
- boolean isLeafProperty = !property.hasNext();
-
if (propertyPathModel == null && isPluralAttribute) {
return true;
}
@@ -883,6 +857,7 @@ public abstract class QueryUtils {
boolean isInverseOptionalOneToOne = PersistentAttributeType.ONE_TO_ONE == attribute.getPersistentAttributeType()
&& StringUtils.hasText(getAnnotationProperty(attribute, "mappedBy", ""));
+ boolean isLeafProperty = !property.hasNext();
if (isLeafProperty && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin) {
return false;
}
@@ -972,4 +947,57 @@ public abstract class QueryUtils {
throw new InvalidDataAccessApiUsageException(String.format(UNSAFE_PROPERTY_REFERENCE, order));
}
}
+
+ /**
+ * Get the {@link Bindable model} that corresponds to the given path utilizing the given {@link ManagedType} if
+ * present or resolving the model from the {@link Path#getModel() path} by creating it via {@link From#get(String)} in
+ * case where the type signature may be erased by some vendors if the attribute contains generics.
+ *
+ * @param path the current {@link PropertyPath} segment.
+ * @param managedType primary source for the resulting {@link Bindable}. Can be {@literal null}.
+ * @param fallback must not be {@literal null}.
+ * @return the corresponding {@link Bindable} of {@literal null}.
+ * @see https://hibernate.atlassian.net/browse/HHH-16144
+ * @see https://github.com/jakartaee/persistence/issues/562
+ */
+ @Nullable
+ private static Bindable> getModelForPath(PropertyPath path, @Nullable ManagedType> managedType,
+ Path> fallback) {
+
+ String segment = path.getSegment();
+ if (managedType != null) {
+ try {
+ return (Bindable>) managedType.getAttribute(segment);
+ } catch (IllegalArgumentException ex) {
+ // ManagedType may be erased for some vendor if the attribute is declared as generic
+ }
+ }
+
+ return fallback.get(segment).getModel();
+ }
+
+ /**
+ * Required for EclipseLink: we try to avoid using from.get as EclipseLink produces an inner join regardless of which
+ * join operation is specified next
+ *
+ * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892
+ * @param model
+ * @return
+ */
+ @Nullable
+ private static ManagedType> getManagedTypeForModel(Bindable> model) {
+
+ if (model instanceof ManagedType> managedType) {
+ return managedType;
+ }
+
+ if (!(model instanceof SingularAttribute, ?> singularAttribute)) {
+ return null;
+ }
+
+ return singularAttribute.getType() instanceof ManagedType> managedType ? managedType : null;
+ }
}