11/*
2- * Copyright 2018-2021 the original author or authors.
2+ * Copyright 2018-2022 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2121
2222import org .apache .commons .logging .Log ;
2323import org .apache .commons .logging .LogFactory ;
24-
2524import org .springframework .beans .factory .BeanFactory ;
2625import org .springframework .context .ApplicationEventPublisher ;
2726import org .springframework .data .jdbc .core .convert .EntityRowMapper ;
4140import org .springframework .data .relational .core .mapping .event .AfterLoadEvent ;
4241import org .springframework .data .repository .core .NamedQueries ;
4342import org .springframework .data .repository .core .RepositoryMetadata ;
44- import org .springframework .data .repository .query .QueryCreationException ;
4543import org .springframework .data .repository .query .QueryLookupStrategy ;
4644import org .springframework .data .repository .query .RepositoryQuery ;
4745import org .springframework .jdbc .core .RowMapper ;
5149import org .springframework .util .Assert ;
5250
5351/**
54- * {@link QueryLookupStrategy} for JDBC repositories.
52+ * Abstract {@link QueryLookupStrategy} for JDBC repositories.
5553 *
5654 * @author Jens Schauder
5755 * @author Kazuki Shimizu
6058 * @author Maciej Walkowiak
6159 * @author Moises Cisneros
6260 * @author Hebert Coelho
61+ * @author Diego Krupitza
6362 */
64- class JdbcQueryLookupStrategy implements QueryLookupStrategy {
63+ abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
6564
6665 private static final Log LOG = LogFactory .getLog (JdbcQueryLookupStrategy .class );
6766
@@ -72,12 +71,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
7271 private final Dialect dialect ;
7372 private final QueryMappingConfiguration queryMappingConfiguration ;
7473 private final NamedParameterJdbcOperations operations ;
75- private final BeanFactory beanfactory ;
74+ @ Nullable private final BeanFactory beanfactory ;
7675
7776 JdbcQueryLookupStrategy (ApplicationEventPublisher publisher , @ Nullable EntityCallbacks callbacks ,
7877 RelationalMappingContext context , JdbcConverter converter , Dialect dialect ,
7978 QueryMappingConfiguration queryMappingConfiguration , NamedParameterJdbcOperations operations ,
80- BeanFactory beanfactory ) {
79+ @ Nullable BeanFactory beanfactory ) {
8180
8281 Assert .notNull (publisher , "ApplicationEventPublisher must not be null" );
8382 Assert .notNull (context , "RelationalMappingContextPublisher must not be null" );
@@ -96,43 +95,209 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
9695 this .beanfactory = beanfactory ;
9796 }
9897
99- /*
100- * (non-Javadoc)
101- * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
98+ /**
99+ * {@link QueryLookupStrategy} to create a query from the method name.
100+ *
101+ * @author Diego Krupitza
102102 */
103- @ Override
104- public RepositoryQuery resolveQuery (Method method , RepositoryMetadata repositoryMetadata ,
105- ProjectionFactory projectionFactory , NamedQueries namedQueries ) {
103+ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
104+
105+ CreateQueryLookupStrategy (ApplicationEventPublisher publisher , @ Nullable EntityCallbacks callbacks ,
106+ RelationalMappingContext context , JdbcConverter converter , Dialect dialect ,
107+ QueryMappingConfiguration queryMappingConfiguration , NamedParameterJdbcOperations operations ,
108+ @ Nullable BeanFactory beanfactory ) {
109+ super (publisher , callbacks , context , converter , dialect , queryMappingConfiguration , operations , beanfactory );
110+ }
111+
112+ @ Override
113+ public RepositoryQuery resolveQuery (Method method , RepositoryMetadata repositoryMetadata ,
114+ ProjectionFactory projectionFactory , NamedQueries namedQueries ) {
115+
116+ JdbcQueryMethod queryMethod = getJdbcQueryMethod (method , repositoryMetadata , projectionFactory , namedQueries );
117+
118+ return new PartTreeJdbcQuery (getContext (), queryMethod , getDialect (), getConverter (), getOperations (),
119+ this ::createMapper );
120+ }
121+ }
122+
123+ /**
124+ * {@link QueryLookupStrategy} that tries to detect a declared query declared via
125+ * {@link org.springframework.data.jdbc.repository.query.Query} annotation followed by a JPA named query lookup.
126+ *
127+ * @author Diego Krupitza
128+ */
129+ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
130+
131+ DeclaredQueryLookupStrategy (ApplicationEventPublisher publisher , @ Nullable EntityCallbacks callbacks ,
132+ RelationalMappingContext context , JdbcConverter converter , Dialect dialect ,
133+ QueryMappingConfiguration queryMappingConfiguration , NamedParameterJdbcOperations operations ,
134+ @ Nullable BeanFactory beanfactory ) {
135+ super (publisher , callbacks , context , converter , dialect , queryMappingConfiguration , operations , beanfactory );
136+ }
137+
138+ @ Override
139+ public RepositoryQuery resolveQuery (Method method , RepositoryMetadata repositoryMetadata ,
140+ ProjectionFactory projectionFactory , NamedQueries namedQueries ) {
106141
107- JdbcQueryMethod queryMethod = new JdbcQueryMethod (method , repositoryMetadata , projectionFactory , namedQueries ,
108- context );
142+ JdbcQueryMethod queryMethod = getJdbcQueryMethod (method , repositoryMetadata , projectionFactory , namedQueries );
109143
110- try {
111144 if (namedQueries .hasQuery (queryMethod .getNamedQueryName ()) || queryMethod .hasAnnotatedQuery ()) {
112145
113146 if (queryMethod .hasAnnotatedQuery () && queryMethod .hasAnnotatedQueryName ()) {
114147 LOG .warn (String .format (
115148 "Query method %s is annotated with both, a query and a query name. Using the declared query." , method ));
116149 }
117150
118- StringBasedJdbcQuery query = new StringBasedJdbcQuery (queryMethod , operations , this ::createMapper , converter );
119- query .setBeanFactory (beanfactory );
151+ StringBasedJdbcQuery query = new StringBasedJdbcQuery (queryMethod , getOperations (), this ::createMapper ,
152+ getConverter ());
153+ query .setBeanFactory (getBeanfactory ());
120154 return query ;
121- } else {
122- return new PartTreeJdbcQuery (context , queryMethod , dialect , converter , operations , this ::createMapper );
123155 }
124- } catch (Exception e ) {
125- throw QueryCreationException .create (queryMethod , e );
156+
157+ throw new IllegalStateException (
158+ String .format ("Did neither find a NamedQuery nor an annotated query for method %s!" , method ));
159+ }
160+ }
161+
162+ /**
163+ * {@link QueryLookupStrategy} to try to detect a declared query first (
164+ * {@link org.springframework.data.jdbc.repository.query.Query}, JDBC named query). In case none is found we fall back
165+ * on query creation.
166+ * <p>
167+ * Modified based on original source: {@link org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy}
168+ *
169+ * @author Oliver Gierke
170+ * @author Thomas Darimont
171+ * @author Diego Krupitza
172+ */
173+ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy {
174+
175+ private final DeclaredQueryLookupStrategy lookupStrategy ;
176+ private final CreateQueryLookupStrategy createStrategy ;
177+
178+ /**
179+ * Creates a new {@link CreateIfNotFoundQueryLookupStrategy}.
180+ *
181+ * @param createStrategy must not be {@literal null}.
182+ * @param lookupStrategy must not be {@literal null}.
183+ */
184+ public CreateIfNotFoundQueryLookupStrategy (ApplicationEventPublisher publisher , @ Nullable EntityCallbacks callbacks ,
185+ RelationalMappingContext context , JdbcConverter converter , Dialect dialect ,
186+ QueryMappingConfiguration queryMappingConfiguration , NamedParameterJdbcOperations operations ,
187+ @ Nullable BeanFactory beanfactory , CreateQueryLookupStrategy createStrategy ,
188+ DeclaredQueryLookupStrategy lookupStrategy ) {
189+
190+ super (publisher , callbacks , context , converter , dialect , queryMappingConfiguration , operations , beanfactory );
191+
192+ Assert .notNull (createStrategy , "CreateQueryLookupStrategy must not be null!" );
193+ Assert .notNull (lookupStrategy , "DeclaredQueryLookupStrategy must not be null!" );
194+
195+ this .createStrategy = createStrategy ;
196+ this .lookupStrategy = lookupStrategy ;
126197 }
198+
199+ @ Override
200+ public RepositoryQuery resolveQuery (Method method , RepositoryMetadata repositoryMetadata ,
201+ ProjectionFactory projectionFactory , NamedQueries namedQueries ) {
202+
203+ try {
204+ return lookupStrategy .resolveQuery (method , repositoryMetadata , projectionFactory , namedQueries );
205+ } catch (IllegalStateException e ) {
206+ return createStrategy .resolveQuery (method , repositoryMetadata , projectionFactory , namedQueries );
207+ }
208+ }
209+ }
210+
211+ /**
212+ * Creates a {@link JdbcQueryMethod} based on the parameters
213+ */
214+ JdbcQueryMethod getJdbcQueryMethod (Method method , RepositoryMetadata repositoryMetadata ,
215+ ProjectionFactory projectionFactory , NamedQueries namedQueries ) {
216+ return new JdbcQueryMethod (method , repositoryMetadata , projectionFactory , namedQueries , context );
217+ }
218+
219+ /**
220+ * Creates a {@link QueryLookupStrategy} based on the provided
221+ * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key}.
222+ *
223+ * @param key the key that decides what {@link QueryLookupStrategy} shozld be used.
224+ * @param publisher must not be {@literal null}
225+ * @param callbacks may be {@literal null}
226+ * @param context must not be {@literal null}
227+ * @param converter must not be {@literal null}
228+ * @param dialect must not be {@literal null}
229+ * @param queryMappingConfiguration must not be {@literal null}
230+ * @param operations must not be {@literal null}
231+ * @param beanfactory may be {@literal null}
232+ */
233+ public static QueryLookupStrategy create (@ Nullable Key key , ApplicationEventPublisher publisher ,
234+ @ Nullable EntityCallbacks callbacks , RelationalMappingContext context , JdbcConverter converter , Dialect dialect ,
235+ QueryMappingConfiguration queryMappingConfiguration , NamedParameterJdbcOperations operations ,
236+ @ Nullable BeanFactory beanfactory ) {
237+
238+ Assert .notNull (publisher , "ApplicationEventPublisher must not be null" );
239+ Assert .notNull (context , "RelationalMappingContextPublisher must not be null" );
240+ Assert .notNull (converter , "JdbcConverter must not be null" );
241+ Assert .notNull (dialect , "Dialect must not be null" );
242+ Assert .notNull (queryMappingConfiguration , "QueryMappingConfiguration must not be null" );
243+ Assert .notNull (operations , "NamedParameterJdbcOperations must not be null" );
244+
245+ CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy (publisher , callbacks , context ,
246+ converter , dialect , queryMappingConfiguration , operations , beanfactory );
247+
248+ DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy (publisher , callbacks ,
249+ context , converter , dialect , queryMappingConfiguration , operations , beanfactory );
250+
251+ Key cleanedKey = key != null ? key : Key .CREATE_IF_NOT_FOUND ;
252+
253+ LOG .debug (String .format ("Using the queryLookupStrategy %s" , cleanedKey ));
254+
255+ switch (cleanedKey ) {
256+ case CREATE :
257+ return createQueryLookupStrategy ;
258+ case USE_DECLARED_QUERY :
259+ return declaredQueryLookupStrategy ;
260+ case CREATE_IF_NOT_FOUND :
261+ return new CreateIfNotFoundQueryLookupStrategy (publisher , callbacks , context , converter , dialect ,
262+ queryMappingConfiguration , operations , beanfactory , createQueryLookupStrategy , declaredQueryLookupStrategy );
263+ default :
264+ throw new IllegalArgumentException (String .format ("Unsupported query lookup strategy %s!" , key ));
265+ }
266+ }
267+
268+ protected ApplicationEventPublisher getPublisher () {
269+ return publisher ;
270+ }
271+
272+ protected RelationalMappingContext getContext () {
273+ return context ;
274+ }
275+
276+ protected JdbcConverter getConverter () {
277+ return converter ;
278+ }
279+
280+ protected Dialect getDialect () {
281+ return dialect ;
282+ }
283+
284+ protected NamedParameterJdbcOperations getOperations () {
285+ return operations ;
286+ }
287+
288+ @ Nullable
289+ protected BeanFactory getBeanfactory () {
290+ return beanfactory ;
127291 }
128292
129293 @ SuppressWarnings ("unchecked" )
130- private RowMapper <Object > createMapper (Class <?> returnedObjectType ) {
294+ RowMapper <Object > createMapper (Class <?> returnedObjectType ) {
131295
132296 RelationalPersistentEntity <?> persistentEntity = context .getPersistentEntity (returnedObjectType );
133297
134298 if (persistentEntity == null ) {
135- return (RowMapper <Object >) SingleColumnRowMapper .newInstance (returnedObjectType , converter .getConversionService ());
299+ return (RowMapper <Object >) SingleColumnRowMapper .newInstance (returnedObjectType ,
300+ converter .getConversionService ());
136301 }
137302
138303 return (RowMapper <Object >) determineDefaultMapper (returnedObjectType );
0 commit comments