2929import jakarta .persistence .criteria .From ;
3030import jakarta .persistence .criteria .Join ;
3131import jakarta .persistence .criteria .JoinType ;
32+ import jakarta .persistence .criteria .Path ;
3233import jakarta .persistence .metamodel .Attribute ;
3334import jakarta .persistence .metamodel .Attribute .PersistentAttributeType ;
3435import jakarta .persistence .metamodel .Bindable ;
@@ -824,45 +825,18 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
824825 private static boolean requiresOuterJoin (From <?, ?> from , PropertyPath property , boolean isForSelection ,
825826 boolean hasRequiredOuterJoin ) {
826827
827- String segment = property .getSegment ();
828-
829828 // already inner joined so outer join is useless
830- if (isAlreadyInnerJoined (from , segment ))
829+ if (isAlreadyInnerJoined (from , property . getSegment ())) {
831830 return false ;
831+ }
832832
833- Bindable <?> propertyPathModel ;
834833 Bindable <?> model = from .getModel ();
835-
836- // required for EclipseLink: we try to avoid using from.get as EclipseLink produces an inner join
837- // regardless of which join operation is specified next
838- // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892
839- // still occurs as of 2.7
840- ManagedType <?> managedType = null ;
841- if (model instanceof ManagedType ) {
842- managedType = (ManagedType <?>) model ;
843- } else if (model instanceof SingularAttribute
844- && ((SingularAttribute <?, ?>) model ).getType () instanceof ManagedType ) {
845- managedType = (ManagedType <?>) ((SingularAttribute <?, ?>) model ).getType ();
846- }
847- if (managedType != null ) {
848- try {
849- propertyPathModel = (Bindable <?>) managedType .getAttribute (segment );
850- } catch (IllegalArgumentException ex ) {
851- // ManagedType may be erased type for some vendor if the attribute is declared as generic
852- // see: https://hibernate.atlassian.net/browse/HHH-16144
853- // see: https://github.com/hibernate/hibernate-orm/pull/7630
854- // see: https://github.com/jakartaee/persistence/issues/562
855- propertyPathModel = from .get (segment ).getModel ();
856- }
857- } else {
858- propertyPathModel = from .get (segment ).getModel ();
859- }
834+ ManagedType <?> managedType = getManagedTypeForModel (model );
835+ Bindable <?> propertyPathModel = getModelForPath (property , managedType , from );
860836
861837 // is the attribute of Collection type?
862838 boolean isPluralAttribute = model instanceof PluralAttribute ;
863839
864- boolean isLeafProperty = !property .hasNext ();
865-
866840 if (propertyPathModel == null && isPluralAttribute ) {
867841 return true ;
868842 }
@@ -883,6 +857,7 @@ private static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property,
883857 boolean isInverseOptionalOneToOne = PersistentAttributeType .ONE_TO_ONE == attribute .getPersistentAttributeType ()
884858 && StringUtils .hasText (getAnnotationProperty (attribute , "mappedBy" , "" ));
885859
860+ boolean isLeafProperty = !property .hasNext ();
886861 if (isLeafProperty && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin ) {
887862 return false ;
888863 }
@@ -972,4 +947,57 @@ static void checkSortExpression(Order order) {
972947 throw new InvalidDataAccessApiUsageException (String .format (UNSAFE_PROPERTY_REFERENCE , order ));
973948 }
974949 }
950+
951+ /**
952+ * Get the {@link Bindable model} that corresponds to the given path utilizing the given {@link ManagedType} if
953+ * present or resolving the model from the {@link Path#getModel() path} by creating it via {@link From#get(String)} in
954+ * case where the type signature may be erased by some vendors if the attribute contains generics.
955+ *
956+ * @param path the current {@link PropertyPath} segment.
957+ * @param managedType primary source for the resulting {@link Bindable}. Can be {@literal null}.
958+ * @param fallback must not be {@literal null}.
959+ * @return the corresponding {@link Bindable} of {@literal null}.
960+ * @see <a href=
961+ * "https://hibernate.atlassian.net/browse/HHH-16144">https://hibernate.atlassian.net/browse/HHH-16144</a>
962+ * @see <a href=
963+ * "https://github.com/jakartaee/persistence/issues/562">https://github.com/jakartaee/persistence/issues/562</a>
964+ */
965+ @ Nullable
966+ private static Bindable <?> getModelForPath (PropertyPath path , @ Nullable ManagedType <?> managedType ,
967+ Path <?> fallback ) {
968+
969+ String segment = path .getSegment ();
970+ if (managedType != null ) {
971+ try {
972+ return (Bindable <?>) managedType .getAttribute (segment );
973+ } catch (IllegalArgumentException ex ) {
974+ // ManagedType may be erased for some vendor if the attribute is declared as generic
975+ }
976+ }
977+
978+ return fallback .get (segment ).getModel ();
979+ }
980+
981+ /**
982+ * Required for EclipseLink: we try to avoid using from.get as EclipseLink produces an inner join regardless of which
983+ * join operation is specified next
984+ *
985+ * @see <a href=
986+ * "https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892">https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892</a>
987+ * @param model
988+ * @return
989+ */
990+ @ Nullable
991+ private static ManagedType <?> getManagedTypeForModel (Bindable <?> model ) {
992+
993+ if (model instanceof ManagedType <?> managedType ) {
994+ return managedType ;
995+ }
996+
997+ if (!(model instanceof SingularAttribute <?, ?> singularAttribute )) {
998+ return null ;
999+ }
1000+
1001+ return singularAttribute .getType () instanceof ManagedType <?> managedType ? managedType : null ;
1002+ }
9751003}
0 commit comments