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 ;
@@ -104,7 +96,8 @@ public abstract class QueryUtils {
10496
10597 private static final Pattern ALIAS_MATCH ;
10698 private static final Pattern COUNT_MATCH ;
107- private static final Pattern PROJECTION_CLAUSE = Pattern .compile ("select\\ s+(?:distinct\\ s+)?(.+)\\ s+from" , Pattern .CASE_INSENSITIVE );
99+ private static final Pattern PROJECTION_CLAUSE = Pattern .compile ("select\\ s+(?:distinct\\ s+)?(.+)\\ s+from" ,
100+ Pattern .CASE_INSENSITIVE );
108101
109102 private static final Pattern NO_DIGITS = Pattern .compile ("\\ D+" );
110103
@@ -114,8 +107,8 @@ public abstract class QueryUtils {
114107 private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s" ;
115108 private static final Pattern ORDER_BY = Pattern .compile (".*order\\ s+by\\ s+.*" , CASE_INSENSITIVE );
116109
117- private static final Pattern NAMED_PARAMETER = Pattern
118- . compile ( COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER , CASE_INSENSITIVE );
110+ private static final Pattern NAMED_PARAMETER = Pattern . compile ( COLON_NO_DOUBLE_COLON + IDENTIFIER + "|#" + IDENTIFIER ,
111+ CASE_INSENSITIVE );
119112
120113 private static final Pattern CONSTRUCTOR_EXPRESSION ;
121114
@@ -490,9 +483,9 @@ public static String createCountQueryFor(String originalQuery, @Nullable String
490483 && !variable .startsWith ("count(" ) //
491484 && !variable .contains ("," ); //
492485
493- String complexCountValue = matcher .matches () &&
494- StringUtils . hasText ( matcher . group ( COMPLEX_COUNT_FIRST_INDEX )) ?
495- COMPLEX_COUNT_VALUE : COMPLEX_COUNT_LAST_VALUE ;
486+ String complexCountValue = matcher .matches () && StringUtils . hasText ( matcher . group ( COMPLEX_COUNT_FIRST_INDEX ))
487+ ? COMPLEX_COUNT_VALUE
488+ : COMPLEX_COUNT_LAST_VALUE ;
496489
497490 String replacement = useVariable ? SIMPLE_COUNT_VALUE : complexCountValue ;
498491 countQuery = matcher .replaceFirst (String .format (COUNT_REPLACEMENT_TEMPLATE , replacement ));
@@ -626,15 +619,16 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
626619 /**
627620 * Creates an expression with proper inner and left joins by recursively navigating the path
628621 *
629- * @param from the {@link From}
630- * @param property the property path
631- * @param isForSelection is the property navigated for the selection or ordering part of the query?
622+ * @param from the {@link From}
623+ * @param property the property path
624+ * @param isForSelection is the property navigated for the selection or ordering part of the query?
632625 * @param hasRequiredOuterJoin has a parent already required an outer join?
633- * @param <T> the type of the expression
626+ * @param <T> the type of the expression
634627 * @return the expression
635628 */
636- @ SuppressWarnings ("unchecked" ) static <T > Expression <T > toExpressionRecursively (From <?, ?> from ,
637- PropertyPath property , boolean isForSelection , boolean hasRequiredOuterJoin ) {
629+ @ SuppressWarnings ("unchecked" )
630+ static <T > Expression <T > toExpressionRecursively (From <?, ?> from , PropertyPath property , boolean isForSelection ,
631+ boolean hasRequiredOuterJoin ) {
638632
639633 String segment = property .getSegment ();
640634
@@ -663,16 +657,15 @@ static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath p
663657 }
664658
665659 /**
666- * Checks if this attribute requires an outer join.
667- * This is the case eg. if it hadn't already been fetched with an inner join and if it's an a optional association,
668- * and if previous paths has already required outer joins.
669- * It also ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999).
660+ * Checks if this attribute requires an outer join. This is the case eg. if it hadn't already been fetched with an
661+ * inner join and if it's an a optional association, and if previous paths has already required outer joins. It also
662+ * ensures outer joins are used even when Hibernate defaults to inner joins (HHH-12712 and HHH-12999).
670663 *
671- * @param from the {@link From} to check for fetches.
672- * @param property the property path
673- * @param isForSelection is the property navigated for the selection or ordering part of the query? if true,
674- * we need to generate an explicit outer join in order to prevent Hibernate to use an
675- * inner join instead. see https://hibernate.atlassian.net/browse/HHH-12999
664+ * @param from the {@link From} to check for fetches.
665+ * @param property the property path
666+ * @param isForSelection is the property navigated for the selection or ordering part of the query? if true, we need
667+ * to generate an explicit outer join in order to prevent Hibernate to use an inner join instead. see
668+ * https://hibernate.atlassian.net/browse/HHH-12999
676669 * @param hasRequiredOuterJoin has a parent already required an outer join?
677670 * @return whether an outer join is to be used for integrating this attribute in a query.
678671 */
@@ -696,7 +689,7 @@ private static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property,
696689 if (model instanceof ManagedType ) {
697690 managedType = (ManagedType <?>) model ;
698691 } else if (model instanceof SingularAttribute
699- && ((SingularAttribute <?, ?>) model ).getType () instanceof ManagedType ) {
692+ && ((SingularAttribute <?, ?>) model ).getType () instanceof ManagedType ) {
700693 managedType = (ManagedType <?>) ((SingularAttribute <?, ?>) model ).getType ();
701694 }
702695 if (managedType != null ) {
@@ -760,34 +753,48 @@ private static <T> T getAnnotationProperty(Attribute<?, ?> attribute, String pro
760753 /**
761754 * Returns an existing join for the given attribute if one already exists or creates a new one if not.
762755 *
763- * @param from the {@link From} to get the current joins from.
756+ * @param from the {@link From} to get the current joins from.
764757 * @param attribute the {@link Attribute} to look for in the current joins.
765- * @param joinType the join type to create if none was found
758+ * @param joinType the join type to create if none was found
766759 * @return will never be {@literal null}.
767760 */
768761 private static Join <?, ?> getOrCreateJoin (From <?, ?> from , String attribute , JoinType joinType ) {
769- return from .getJoins ().stream ()
770- .filter (join -> join .getAttribute ().getName ().equals (attribute ))
771- .findFirst ()
772- .orElseGet (() -> from .join (attribute , joinType ));
762+
763+ for (Join <?, ?> join : from .getJoins ()) {
764+
765+ if (join .getAttribute ().getName ().equals (attribute )) {
766+ return join ;
767+ }
768+ }
769+ return from .join (attribute , joinType );
773770 }
774771
775772 /**
776773 * Return whether the given {@link From} contains an inner join for the attribute with the given name.
777774 *
778- * @param from the {@link From} to check for joins.
775+ * @param from the {@link From} to check for joins.
779776 * @param attribute the attribute name to check.
780777 * @return true if the attribute has already been inner joined
781778 */
782779 private static boolean isAlreadyInnerJoined (From <?, ?> from , String attribute ) {
783780
784- boolean isInnerJoinFetched = from .getFetches ().stream ().anyMatch (
785- fetch -> fetch .getAttribute ().getName ().equals (attribute ) && fetch .getJoinType ().equals (JoinType .INNER ));
781+ for (Fetch <?, ?> fetch : from .getFetches ()) {
786782
787- boolean isSimplyInnerJoined = from .getJoins ().stream ()
788- .anyMatch (join -> join .getAttribute ().getName ().equals (attribute ) && join .getJoinType ().equals (JoinType .INNER ));
783+ if (fetch .getAttribute ().getName ().equals (attribute ) //
784+ && fetch .getJoinType ().equals (JoinType .INNER )) {
785+ return true ;
786+ }
787+ }
788+
789+ for (Join <?, ?> join : from .getJoins ()) {
790+
791+ if (join .getAttribute ().getName ().equals (attribute ) //
792+ && join .getJoinType ().equals (JoinType .INNER )) {
793+ return true ;
794+ }
795+ }
789796
790- return isInnerJoinFetched || isSimplyInnerJoined ;
797+ return false ;
791798 }
792799
793800 /**
0 commit comments