2121import java .lang .annotation .Annotation ;
2222import java .lang .reflect .AnnotatedElement ;
2323import java .lang .reflect .Member ;
24- import java .util .ArrayList ;
25- import java .util .Collections ;
26- import java .util .HashMap ;
27- import java .util .HashSet ;
28- import java .util .Iterator ;
29- import java .util .List ;
30- import java .util .Locale ;
31- import java .util .Map ;
32- import java .util .Objects ;
33- import java .util .Set ;
24+ import java .util .*;
3425import java .util .regex .Matcher ;
3526import java .util .regex .Pattern ;
3627import java .util .stream .Collectors ;
4233import javax .persistence .Query ;
4334import javax .persistence .criteria .CriteriaBuilder ;
4435import javax .persistence .criteria .Expression ;
36+ import javax .persistence .criteria .Fetch ;
4537import javax .persistence .criteria .From ;
4638import javax .persistence .criteria .Join ;
4739import javax .persistence .criteria .JoinType ;
4840import javax .persistence .metamodel .Attribute ;
49- import javax .persistence .metamodel .Attribute .PersistentAttributeType ;
5041import javax .persistence .metamodel .Bindable ;
5142import javax .persistence .metamodel .ManagedType ;
5243import javax .persistence .metamodel .PluralAttribute ;
5344import javax .persistence .metamodel .SingularAttribute ;
45+ import javax .persistence .metamodel .Attribute .PersistentAttributeType ;
5446
5547import org .springframework .core .annotation .AnnotationUtils ;
5648import org .springframework .dao .InvalidDataAccessApiUsageException ;
@@ -105,7 +97,8 @@ public abstract class QueryUtils {
10597
10698 private static final Pattern ALIAS_MATCH ;
10799 private static final Pattern COUNT_MATCH ;
108- private static final Pattern PROJECTION_CLAUSE = Pattern .compile ("select\\ s+(?:distinct\\ s+)?(.+)\\ s+from" , Pattern .CASE_INSENSITIVE );
100+ private static final Pattern PROJECTION_CLAUSE = Pattern .compile ("select\\ s+(?:distinct\\ s+)?(.+)\\ s+from" ,
101+ Pattern .CASE_INSENSITIVE );
109102
110103 private static final Pattern NO_DIGITS = Pattern .compile ("\\ D+" );
111104
@@ -115,8 +108,8 @@ public abstract class QueryUtils {
115108 private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s" ;
116109 private static final Pattern ORDER_BY = Pattern .compile (".*order\\ s+by\\ s+.*" , CASE_INSENSITIVE );
117110
118- private static final Pattern NAMED_PARAMETER = Pattern
119- . compile ( COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER , CASE_INSENSITIVE );
111+ private static final Pattern NAMED_PARAMETER = Pattern . compile ( COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER ,
112+ CASE_INSENSITIVE );
120113
121114 private static final Pattern CONSTRUCTOR_EXPRESSION ;
122115
@@ -491,9 +484,9 @@ public static String createCountQueryFor(String originalQuery, @Nullable String
491484 && !variable .startsWith ("count(" ) //
492485 && !variable .contains ("," ); //
493486
494- String complexCountValue = matcher .matches () &&
495- StringUtils . hasText ( matcher . group ( COMPLEX_COUNT_FIRST_INDEX )) ?
496- COMPLEX_COUNT_VALUE : COMPLEX_COUNT_LAST_VALUE ;
487+ String complexCountValue = matcher .matches () && StringUtils . hasText ( matcher . group ( COMPLEX_COUNT_FIRST_INDEX ))
488+ ? COMPLEX_COUNT_VALUE
489+ : COMPLEX_COUNT_LAST_VALUE ;
497490
498491 String replacement = useVariable ? SIMPLE_COUNT_VALUE : complexCountValue ;
499492 countQuery = matcher .replaceFirst (String .format (COUNT_REPLACEMENT_TEMPLATE , replacement ));
@@ -627,15 +620,16 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
627620 /**
628621 * Creates an expression with proper inner and left joins by recursively navigating the path
629622 *
630- * @param from the {@link From}
631- * @param property the property path
632- * @param isForSelection is the property navigated for the selection or ordering part of the query?
623+ * @param from the {@link From}
624+ * @param property the property path
625+ * @param isForSelection is the property navigated for the selection or ordering part of the query?
633626 * @param hasRequiredOuterJoin has a parent already required an outer join?
634- * @param <T> the type of the expression
627+ * @param <T> the type of the expression
635628 * @return the expression
636629 */
637- @ SuppressWarnings ("unchecked" ) static <T > Expression <T > toExpressionRecursively (From <?, ?> from ,
638- PropertyPath property , boolean isForSelection , boolean hasRequiredOuterJoin ) {
630+ @ SuppressWarnings ("unchecked" )
631+ static <T > Expression <T > toExpressionRecursively (From <?, ?> from , PropertyPath property , boolean isForSelection ,
632+ boolean hasRequiredOuterJoin ) {
639633
640634 String segment = property .getSegment ();
641635
@@ -664,16 +658,15 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
664658 }
665659
666660 /**
667- * Checks if this attribute requires an outer join.
668- * This is the case eg. if it hadn't already been fetched with an inner join and if it's an a optional association,
669- * and if previous paths has already required outer joins.
670- * It also ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999).
661+ * Checks if this attribute requires an outer join. This is the case eg. if it hadn't already been fetched with an
662+ * inner join and if it's an a optional association, and if previous paths has already required outer joins. It also
663+ * ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999).
671664 *
672- * @param from the {@link From} to check for fetches.
673- * @param property the property path
674- * @param isForSelection is the property navigated for the selection or ordering part of the query? if true,
675- * we need to generate an explicit outer join in order to prevent Hibernate to use an
676- * inner join instead. see https://hibernate.atlassian.net/browse/HHH-12999
665+ * @param from the {@link From} to check for fetches.
666+ * @param property the property path
667+ * @param isForSelection is the property navigated for the selection or ordering part of the query? if true, we need
668+ * to generate an explicit outer join in order to prevent Hibernate to use an inner join instead. see
669+ * https://hibernate.atlassian.net/browse/HHH-12999
677670 * @param hasRequiredOuterJoin has a parent already required an outer join?
678671 * @return whether an outer join is to be used for integrating this attribute in a query.
679672 */
@@ -697,7 +690,7 @@ private static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property,
697690 if (model instanceof ManagedType ) {
698691 managedType = (ManagedType <?>) model ;
699692 } else if (model instanceof SingularAttribute
700- && ((SingularAttribute <?, ?>) model ).getType () instanceof ManagedType ) {
693+ && ((SingularAttribute <?, ?>) model ).getType () instanceof ManagedType ) {
701694 managedType = (ManagedType <?>) ((SingularAttribute <?, ?>) model ).getType ();
702695 }
703696 if (managedType != null ) {
@@ -761,34 +754,48 @@ private static <T> T getAnnotationProperty(Attribute<?, ?> attribute, String pro
761754 /**
762755 * Returns an existing join for the given attribute if one already exists or creates a new one if not.
763756 *
764- * @param from the {@link From} to get the current joins from.
757+ * @param from the {@link From} to get the current joins from.
765758 * @param attribute the {@link Attribute} to look for in the current joins.
766- * @param joinType the join type to create if none was found
759+ * @param joinType the join type to create if none was found
767760 * @return will never be {@literal null}.
768761 */
769762 private static Join <?, ?> getOrCreateJoin (From <?, ?> from , String attribute , JoinType joinType ) {
770- return from .getJoins ().stream ()
771- .filter (join -> join .getAttribute ().getName ().equals (attribute ))
772- .findFirst ()
773- .orElseGet (() -> from .join (attribute , joinType ));
763+
764+ for (Join <?, ?> join : from .getJoins ()) {
765+
766+ if (join .getAttribute ().getName ().equals (attribute )) {
767+ return join ;
768+ }
769+ }
770+ return from .join (attribute , joinType );
774771 }
775772
776773 /**
777774 * Return whether the given {@link From} contains an inner join for the attribute with the given name.
778775 *
779- * @param from the {@link From} to check for joins.
776+ * @param from the {@link From} to check for joins.
780777 * @param attribute the attribute name to check.
781778 * @return true if the attribute has already been inner joined
782779 */
783780 private static boolean isAlreadyInnerJoined (From <?, ?> from , String attribute ) {
784781
785- boolean isInnerJoinFetched = from .getFetches ().stream ().anyMatch (
786- fetch -> fetch .getAttribute ().getName ().equals (attribute ) && fetch .getJoinType ().equals (JoinType .INNER ));
782+ for (Fetch <?, ?> fetch : from .getFetches ()) {
787783
788- boolean isSimplyInnerJoined = from .getJoins ().stream ()
789- .anyMatch (join -> join .getAttribute ().getName ().equals (attribute ) && join .getJoinType ().equals (JoinType .INNER ));
784+ if (fetch .getAttribute ().getName ().equals (attribute ) //
785+ && fetch .getJoinType ().equals (JoinType .INNER )) {
786+ return true ;
787+ }
788+ }
789+
790+ for (Join <?, ?> join : from .getJoins ()) {
791+
792+ if (join .getAttribute ().getName ().equals (attribute ) //
793+ && join .getJoinType ().equals (JoinType .INNER )) {
794+ return true ;
795+ }
796+ }
790797
791- return isInnerJoinFetched || isSimplyInnerJoined ;
798+ return false ;
792799 }
793800
794801 /**
0 commit comments