42
42
import java .lang .reflect .AnnotatedElement ;
43
43
import java .lang .reflect .Member ;
44
44
import java .util .*;
45
+ import java .util .function .Supplier ;
45
46
import java .util .regex .Matcher ;
46
47
import java .util .regex .Pattern ;
47
48
import java .util .stream .Collectors ;
88
89
* @author Eduard Dudar
89
90
* @author Yanming Zhou
90
91
* @author Alim Naizabek
92
+ * @author Jakub Soltys
91
93
*/
92
94
public abstract class QueryUtils {
93
95
@@ -773,11 +775,17 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
773
775
774
776
boolean isLeafProperty = !property .hasNext ();
775
777
776
- boolean requiresOuterJoin = requiresOuterJoin (from , property , isForSelection , hasRequiredOuterJoin );
778
+ boolean isRelationshipId = isRelationshipId (from , property );
779
+ boolean requiresOuterJoin = requiresOuterJoin (from , property , isForSelection , hasRequiredOuterJoin , isLeafProperty , isRelationshipId );
777
780
778
- // if it does not require an outer join and is a leaf, simply get the segment
779
- if (!requiresOuterJoin && isLeafProperty ) {
780
- return from .get (segment );
781
+ // if it does not require an outer join and is a leaf or relationship id, simply get rest of the segment path
782
+ if (!requiresOuterJoin && (isLeafProperty || isRelationshipId )) {
783
+ Path <T > trailingPath = from .get (segment );
784
+ while (property .hasNext ()) {
785
+ property = property .next ();
786
+ trailingPath = trailingPath .get (property .getSegment ());
787
+ }
788
+ return trailingPath ;
781
789
}
782
790
783
791
// get or create the join
@@ -806,10 +814,12 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
806
814
* to generate an explicit outer join in order to prevent Hibernate to use an inner join instead. see
807
815
* https://hibernate.atlassian.net/browse/HHH-12999
808
816
* @param hasRequiredOuterJoin has a parent already required an outer join?
817
+ * @param isLeafProperty is leaf property
818
+ * @param isRelationshipId whether property path refers to relationship id
809
819
* @return whether an outer join is to be used for integrating this attribute in a query.
810
820
*/
811
821
static boolean requiresOuterJoin (From <?, ?> from , PropertyPath property , boolean isForSelection ,
812
- boolean hasRequiredOuterJoin ) {
822
+ boolean hasRequiredOuterJoin , boolean isLeafProperty , boolean isRelationshipId ) {
813
823
814
824
// already inner joined so outer join is useless
815
825
if (isAlreadyInnerJoined (from , property .getSegment ())) {
@@ -818,14 +828,7 @@ static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property, boolean
818
828
819
829
Bindable <?> model = from .getModel ();
820
830
ManagedType <?> managedType = getManagedTypeForModel (model );
821
- Bindable <?> propertyPathModel = getModelForPath (property , managedType , from );
822
-
823
- // is the attribute of Collection type?
824
- boolean isPluralAttribute = model instanceof PluralAttribute ;
825
-
826
- if (propertyPathModel == null && isPluralAttribute ) {
827
- return true ;
828
- }
831
+ Bindable <?> propertyPathModel = getModelForPath (property , managedType , () -> from );
829
832
830
833
if (!(propertyPathModel instanceof Attribute <?, ?> attribute )) {
831
834
return false ;
@@ -843,14 +846,36 @@ static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property, boolean
843
846
boolean isInverseOptionalOneToOne = ONE_TO_ONE == attribute .getPersistentAttributeType ()
844
847
&& StringUtils .hasText (getAnnotationProperty (attribute , "mappedBy" , "" ));
845
848
846
- boolean isLeafProperty = !property .hasNext ();
847
- if (isLeafProperty && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin ) {
849
+ if ((isLeafProperty || isRelationshipId ) && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin ) {
848
850
return false ;
849
851
}
850
852
851
853
return hasRequiredOuterJoin || getAnnotationProperty (attribute , "optional" , true );
852
854
}
853
855
856
+ /**
857
+ * Checks if this property path is referencing to relationship id.
858
+ *
859
+ * @param from the {@link From} to check for attribute model.
860
+ * @param property the property path
861
+ * @return whether in a query is relationship id.
862
+ */
863
+ static boolean isRelationshipId (From <?, ?> from , PropertyPath property ) {
864
+ if (!property .hasNext ()) {
865
+ return false ;
866
+ }
867
+
868
+ Bindable <?> model = from .getModel ();
869
+ ManagedType <?> managedType = getManagedTypeForModel (model );
870
+ Bindable <?> propertyPathModel = getModelForPath (property , managedType , () -> from );
871
+ ManagedType <?> propertyPathManagedType = getManagedTypeForModel (propertyPathModel );
872
+ Bindable <?> nextPropertyPathModel = getModelForPath (property .next (), propertyPathManagedType , () -> from .get (property .getSegment ()));
873
+ if (nextPropertyPathModel instanceof SingularAttribute <?, ?>) {
874
+ return ((SingularAttribute <?, ?>) nextPropertyPathModel ).isId ();
875
+ }
876
+ return false ;
877
+ }
878
+
854
879
@ SuppressWarnings ("unchecked" )
855
880
static <T > T getAnnotationProperty (Attribute <?, ?> attribute , String propertyName , T defaultValue ) {
856
881
@@ -954,14 +979,14 @@ static void checkSortExpression(Order order) {
954
979
* @param path the current {@link PropertyPath} segment.
955
980
* @param managedType primary source for the resulting {@link Bindable}. Can be {@literal null}.
956
981
* @param fallback must not be {@literal null}.
957
- * @return the corresponding {@link Bindable} of {@literal null} .
982
+ * @return the corresponding {@link Bindable}.
958
983
* @see <a href=
959
984
* "https://hibernate.atlassian.net/browse/HHH-16144">https://hibernate.atlassian.net/browse/HHH-16144</a>
960
985
* @see <a href=
961
986
* "https://github.com/jakartaee/persistence/issues/562">https://github.com/jakartaee/persistence/issues/562</a>
962
987
*/
963
- private static @ Nullable Bindable <?> getModelForPath (PropertyPath path , @ Nullable ManagedType <?> managedType ,
964
- Path <?> fallback ) {
988
+ private static Bindable <?> getModelForPath (PropertyPath path , @ Nullable ManagedType <?> managedType ,
989
+ Supplier < Path <?> > fallback ) {
965
990
966
991
String segment = path .getSegment ();
967
992
if (managedType != null ) {
@@ -972,7 +997,7 @@ static void checkSortExpression(Order order) {
972
997
}
973
998
}
974
999
975
- return fallback .get (segment ).getModel ();
1000
+ return fallback .get (). get ( segment ).getModel ();
976
1001
}
977
1002
978
1003
/**
0 commit comments