diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java
index dd60dd7dd6..c5e1694508 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2021 the original author or authors.
+ * Copyright 2017-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,8 @@
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
+import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
-import org.springframework.transaction.PlatformTransactionManager;
/**
* Annotation to enable JDBC repositories. Will scan the package of the annotated configuration class for Spring Data
@@ -39,6 +39,7 @@
* @author Mark Paluch
* @author Fei Dong
* @author Antoine Sauray
+ * @author Diego Krupitza
* @see AbstractJdbcConfiguration
*/
@Target(ElementType.TYPE)
@@ -124,11 +125,17 @@
*/
String dataAccessStrategyRef() default "";
- /**
+ /**
* Configures the name of the {@link DataSourceTransactionManager} bean definition to be used to create repositories
* discovered through this annotation. Defaults to {@code transactionManager}.
+ *
* @since 2.1
*/
String transactionManagerRef() default "transactionManager";
+ /**
+ * Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
+ * {@link QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}.
+ */
+ QueryLookupStrategy.Key queryLookupStrategy() default QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;
}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
index ab2fc67dd1..4fbdd69f86 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2021 the original author or authors.
+ * Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
@@ -41,7 +40,6 @@
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
-import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
@@ -51,7 +49,7 @@
import org.springframework.util.Assert;
/**
- * {@link QueryLookupStrategy} for JDBC repositories.
+ * Abstract {@link QueryLookupStrategy} for JDBC repositories.
*
* @author Jens Schauder
* @author Kazuki Shimizu
@@ -60,8 +58,9 @@
* @author Maciej Walkowiak
* @author Moises Cisneros
* @author Hebert Coelho
+ * @author Diego Krupitza
*/
-class JdbcQueryLookupStrategy implements QueryLookupStrategy {
+abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private static final Log LOG = LogFactory.getLog(JdbcQueryLookupStrategy.class);
@@ -72,12 +71,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final Dialect dialect;
private final QueryMappingConfiguration queryMappingConfiguration;
private final NamedParameterJdbcOperations operations;
- private final BeanFactory beanfactory;
+ @Nullable private final BeanFactory beanfactory;
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
- BeanFactory beanfactory) {
+ @Nullable BeanFactory beanfactory) {
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
@@ -96,18 +95,52 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
this.beanfactory = beanfactory;
}
- /*
- * (non-Javadoc)
- * @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)
+ /**
+ * {@link QueryLookupStrategy} to create a query from the method name.
+ *
+ * @author Diego Krupitza
*/
- @Override
- public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
- ProjectionFactory projectionFactory, NamedQueries namedQueries) {
+ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
+
+ CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
+ RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
+ @Nullable BeanFactory beanfactory) {
+ super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
+ }
+
+ @Override
+ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
+ ProjectionFactory projectionFactory, NamedQueries namedQueries) {
+
+ JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
+
+ return new PartTreeJdbcQuery(getContext(), queryMethod, getDialect(), getConverter(), getOperations(),
+ this::createMapper);
+ }
+ }
+
+ /**
+ * {@link QueryLookupStrategy} that tries to detect a declared query declared via
+ * {@link org.springframework.data.jdbc.repository.query.Query} annotation followed by a JPA named query lookup.
+ *
+ * @author Diego Krupitza
+ */
+ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
+
+ DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
+ RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
+ @Nullable BeanFactory beanfactory) {
+ super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
+ }
+
+ @Override
+ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
+ ProjectionFactory projectionFactory, NamedQueries namedQueries) {
- JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries,
- context);
+ JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
- try {
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
if (queryMethod.hasAnnotatedQuery() && queryMethod.hasAnnotatedQueryName()) {
@@ -115,24 +148,156 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
"Query method %s is annotated with both, a query and a query name. Using the declared query.", method));
}
- StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, this::createMapper, converter);
- query.setBeanFactory(beanfactory);
+ StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, getOperations(), this::createMapper,
+ getConverter());
+ query.setBeanFactory(getBeanfactory());
return query;
- } else {
- return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, this::createMapper);
}
- } catch (Exception e) {
- throw QueryCreationException.create(queryMethod, e);
+
+ throw new IllegalStateException(
+ String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
+ }
+ }
+
+ /**
+ * {@link QueryLookupStrategy} to try to detect a declared query first (
+ * {@link org.springframework.data.jdbc.repository.query.Query}, JDBC named query). In case none is found we fall back
+ * on query creation.
+ *
+ * Modified based on original source: {@link org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy}
+ *
+ * @author Oliver Gierke
+ * @author Thomas Darimont
+ * @author Diego Krupitza
+ */
+ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy {
+
+ private final DeclaredQueryLookupStrategy lookupStrategy;
+ private final CreateQueryLookupStrategy createStrategy;
+
+ /**
+ * Creates a new {@link CreateIfNotFoundQueryLookupStrategy}.
+ *
+ * @param createStrategy must not be {@literal null}.
+ * @param lookupStrategy must not be {@literal null}.
+ */
+ public CreateIfNotFoundQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
+ RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
+ @Nullable BeanFactory beanfactory, CreateQueryLookupStrategy createStrategy,
+ DeclaredQueryLookupStrategy lookupStrategy) {
+
+ super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
+
+ Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null!");
+ Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null!");
+
+ this.createStrategy = createStrategy;
+ this.lookupStrategy = lookupStrategy;
}
+
+ @Override
+ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata,
+ ProjectionFactory projectionFactory, NamedQueries namedQueries) {
+
+ try {
+ return lookupStrategy.resolveQuery(method, repositoryMetadata, projectionFactory, namedQueries);
+ } catch (IllegalStateException e) {
+ return createStrategy.resolveQuery(method, repositoryMetadata, projectionFactory, namedQueries);
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link JdbcQueryMethod} based on the parameters
+ */
+ JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
+ ProjectionFactory projectionFactory, NamedQueries namedQueries) {
+ return new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, context);
+ }
+
+ /**
+ * Creates a {@link QueryLookupStrategy} based on the provided
+ * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key}.
+ *
+ * @param key the key that decides what {@link QueryLookupStrategy} shozld be used.
+ * @param publisher must not be {@literal null}
+ * @param callbacks may be {@literal null}
+ * @param context must not be {@literal null}
+ * @param converter must not be {@literal null}
+ * @param dialect must not be {@literal null}
+ * @param queryMappingConfiguration must not be {@literal null}
+ * @param operations must not be {@literal null}
+ * @param beanfactory may be {@literal null}
+ */
+ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPublisher publisher,
+ @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
+ QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
+ @Nullable BeanFactory beanfactory) {
+
+ Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
+ Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
+ Assert.notNull(converter, "JdbcConverter must not be null");
+ Assert.notNull(dialect, "Dialect must not be null");
+ Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
+ Assert.notNull(operations, "NamedParameterJdbcOperations must not be null");
+
+ CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context,
+ converter, dialect, queryMappingConfiguration, operations, beanfactory);
+
+ DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks,
+ context, converter, dialect, queryMappingConfiguration, operations, beanfactory);
+
+ Key cleanedKey = key != null ? key : Key.CREATE_IF_NOT_FOUND;
+
+ LOG.debug(String.format("Using the queryLookupStrategy %s", cleanedKey));
+
+ switch (cleanedKey) {
+ case CREATE:
+ return createQueryLookupStrategy;
+ case USE_DECLARED_QUERY:
+ return declaredQueryLookupStrategy;
+ case CREATE_IF_NOT_FOUND:
+ return new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, context, converter, dialect,
+ queryMappingConfiguration, operations, beanfactory, createQueryLookupStrategy, declaredQueryLookupStrategy);
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
+ }
+ }
+
+ protected ApplicationEventPublisher getPublisher() {
+ return publisher;
+ }
+
+ protected RelationalMappingContext getContext() {
+ return context;
+ }
+
+ protected JdbcConverter getConverter() {
+ return converter;
+ }
+
+ protected Dialect getDialect() {
+ return dialect;
+ }
+
+ protected NamedParameterJdbcOperations getOperations() {
+ return operations;
+ }
+
+ @Nullable
+ protected BeanFactory getBeanfactory() {
+ return beanfactory;
}
@SuppressWarnings("unchecked")
- private RowMapper