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
781
// 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 );
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,7 +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 );
831
+ Bindable <?> propertyPathModel = getModelForPath (property , managedType , () -> from );
822
832
823
833
// is the attribute of Collection type?
824
834
boolean isPluralAttribute = model instanceof PluralAttribute ;
@@ -843,16 +853,38 @@ static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property, boolean
843
853
boolean isInverseOptionalOneToOne = ONE_TO_ONE == attribute .getPersistentAttributeType ()
844
854
&& StringUtils .hasText (getAnnotationProperty (attribute , "mappedBy" , "" ));
845
855
846
- boolean isLeafProperty = !property .hasNext ();
847
- if (isLeafProperty && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin ) {
856
+ if ((isLeafProperty || isRelationshipId ) && !isForSelection && !isCollection && !isInverseOptionalOneToOne && !hasRequiredOuterJoin ) {
848
857
return false ;
849
858
}
850
859
851
860
return hasRequiredOuterJoin || getAnnotationProperty (attribute , "optional" , true );
852
861
}
853
862
854
- @ SuppressWarnings ("unchecked" )
855
- static <T > T getAnnotationProperty (Attribute <?, ?> attribute , String propertyName , T defaultValue ) {
863
+ /**
864
+ * Checks if this property path is referencing to relationship id.
865
+ *
866
+ * @param from the {@link From} to check for attribute model.
867
+ * @param property the property path
868
+ * @return whether in a query is relationship id.
869
+ */
870
+ static boolean isRelationshipId (From <?, ?> from , PropertyPath property ) {
871
+ if (!property .hasNext ()) {
872
+ return false ;
873
+ }
874
+
875
+ Bindable <?> model = from .getModel ();
876
+ ManagedType <?> managedType = getManagedTypeForModel (model );
877
+ Bindable <?> propertyPathModel = getModelForPath (property , managedType , () -> from );
878
+ ManagedType <?> propertyPathManagedType = getManagedTypeForModel (propertyPathModel );
879
+ Bindable <?> nextPropertyPathModel = getModelForPath (property .next (), propertyPathManagedType , () -> from .get (property .getSegment ()));
880
+ if (nextPropertyPathModel instanceof SingularAttribute <?, ?>) {
881
+ return ((SingularAttribute <?, ?>) nextPropertyPathModel ).isId ();
882
+ }
883
+ return false ;
884
+ }
885
+
886
+
887
+ static @ Nullable <T > T getAnnotationProperty (Attribute <?, ?> attribute , String propertyName , T defaultValue ) {
856
888
857
889
Class <? extends Annotation > associationAnnotation = ASSOCIATION_TYPES .get (attribute .getPersistentAttributeType ());
858
890
@@ -961,7 +993,7 @@ static void checkSortExpression(Order order) {
961
993
* "https://github.com/jakartaee/persistence/issues/562">https://github.com/jakartaee/persistence/issues/562</a>
962
994
*/
963
995
private static @ Nullable Bindable <?> getModelForPath (PropertyPath path , @ Nullable ManagedType <?> managedType ,
964
- Path <?> fallback ) {
996
+ Supplier < Path <?> > fallback ) {
965
997
966
998
String segment = path .getSegment ();
967
999
if (managedType != null ) {
@@ -972,7 +1004,7 @@ static void checkSortExpression(Order order) {
972
1004
}
973
1005
}
974
1006
975
- return fallback .get (segment ).getModel ();
1007
+ return fallback .get (). get ( segment ).getModel ();
976
1008
}
977
1009
978
1010
/**
0 commit comments