Skip to content

Commit 246343f

Browse files
DiegoKrupitzaschauder
authored andcommitted
Introduced queryLookupStrategy to EnableJdbcRepositories.
Added the missing functionality that is found in the documentation of spring-data-jdbc, but was not present in the code as functionality. Users can not choose between various QueryLookupStrategies. Closes #1043 Original pull request #1187
1 parent eb197ee commit 246343f

16 files changed

+610
-31
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-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.
@@ -27,8 +27,8 @@
2727
import org.springframework.context.annotation.Import;
2828
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
2929
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
30+
import org.springframework.data.repository.query.QueryLookupStrategy;
3031
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
31-
import org.springframework.transaction.PlatformTransactionManager;
3232

3333
/**
3434
* Annotation to enable JDBC repositories. Will scan the package of the annotated configuration class for Spring Data
@@ -39,6 +39,7 @@
3939
* @author Mark Paluch
4040
* @author Fei Dong
4141
* @author Antoine Sauray
42+
* @author Diego Krupitza
4243
* @see AbstractJdbcConfiguration
4344
*/
4445
@Target(ElementType.TYPE)
@@ -124,11 +125,17 @@
124125
*/
125126
String dataAccessStrategyRef() default "";
126127

127-
/**
128+
/**
128129
* Configures the name of the {@link DataSourceTransactionManager} bean definition to be used to create repositories
129130
* discovered through this annotation. Defaults to {@code transactionManager}.
131+
*
130132
* @since 2.1
131133
*/
132134
String transactionManagerRef() default "transactionManager";
133135

136+
/**
137+
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
138+
* {@link QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}.
139+
*/
140+
QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;
134141
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

+190-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -21,7 +21,6 @@
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
24-
2524
import org.springframework.beans.factory.BeanFactory;
2625
import org.springframework.context.ApplicationEventPublisher;
2726
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
@@ -41,7 +40,6 @@
4140
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
4241
import org.springframework.data.repository.core.NamedQueries;
4342
import org.springframework.data.repository.core.RepositoryMetadata;
44-
import org.springframework.data.repository.query.QueryCreationException;
4543
import org.springframework.data.repository.query.QueryLookupStrategy;
4644
import org.springframework.data.repository.query.RepositoryQuery;
4745
import org.springframework.jdbc.core.RowMapper;
@@ -51,7 +49,7 @@
5149
import 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
@@ -60,8 +58,9 @@
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,39 +95,209 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
9695
this.beanfactory = beanfactory;
9796
}
9897

99-
@Override
100-
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
101-
ProjectionFactory projectionFactory, NamedQueries namedQueries) {
98+
/**
99+
* {@link QueryLookupStrategy} to create a query from the method name.
100+
*
101+
* @author Diego Krupitza
102+
*/
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+
}
102122

103-
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries,
104-
context);
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) {
141+
142+
JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
105143

106-
try {
107144
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
108145

109146
if (queryMethod.hasAnnotatedQuery() && queryMethod.hasAnnotatedQueryName()) {
110147
LOG.warn(String.format(
111148
"Query method %s is annotated with both, a query and a query name. Using the declared query.", method));
112149
}
113150

114-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, this::createMapper, converter);
115-
query.setBeanFactory(beanfactory);
151+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, getOperations(), this::createMapper,
152+
getConverter());
153+
query.setBeanFactory(getBeanfactory());
116154
return query;
117-
} else {
118-
return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, this::createMapper);
119155
}
120-
} catch (Exception e) {
121-
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));
122159
}
123160
}
124161

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;
197+
}
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;
291+
}
292+
125293
@SuppressWarnings("unchecked")
126-
private RowMapper<Object> createMapper(Class<?> returnedObjectType) {
294+
RowMapper<Object> createMapper(Class<?> returnedObjectType) {
127295

128296
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
129297

130298
if (persistentEntity == null) {
131-
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService());
299+
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
300+
converter.getConversionService());
132301
}
133302

134303
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-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.
@@ -46,6 +46,7 @@
4646
* @author Christoph Strobl
4747
* @author Mark Paluch
4848
* @author Hebert Coelho
49+
* @author Diego Krupitza
4950
*/
5051
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
5152

@@ -133,7 +134,7 @@ protected Class<?> getRepositoryBaseClass(RepositoryMetadata repositoryMetadata)
133134
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
134135
QueryMethodEvaluationContextProvider evaluationContextProvider) {
135136

136-
return Optional.of(new JdbcQueryLookupStrategy(publisher, entityCallbacks, context, converter, dialect,
137+
return Optional.of(JdbcQueryLookupStrategy.create(key, publisher, entityCallbacks, context, converter, dialect,
137138
queryMappingConfiguration, operations, beanFactory));
138139
}
139140

0 commit comments

Comments
 (0)