diff --git a/pom.xml b/pom.xml index f0b2a42b23..429fd43bc3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index 84eeb178e0..2c032f6242 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 266af71b96..84e71e08b1 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java index 8e9db74cd6..e66aed271a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java @@ -76,7 +76,7 @@ public JdbcQueryMethod getQueryMethod() { * Creates a {@link JdbcQueryExecution} given a {@link ResultSetExtractor} or a {@link RowMapper}. Prefers the given * {@link ResultSetExtractor} over {@link RowMapper}. * - * @param extractor must not be {@literal null}. + * @param extractor may be {@literal null}. * @param rowMapper must not be {@literal null}. * @return a JdbcQueryExecution appropriate for {@literal queryMethod}. Guaranteed to be not {@literal null}. */ @@ -155,7 +155,34 @@ private JdbcQueryExecution createSingleReadingQueryExecution(ResultSetExt * @since 2.3 */ public interface RowMapperFactory { + + /** + * Create a {@link RowMapper} based on the expected return type passed in as an argument. + * + * @param result must not be {@code null}. + * @return a {@code RowMapper} producing instances of {@code result}. + */ RowMapper create(Class result); + + /** + * Obtain a {@code RowMapper} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}. + * + * @param reference must not be {@code null}. + * @since 3.4 + */ + default RowMapper getRowMapper(String reference) { + throw new UnsupportedOperationException("getRowMapper is not supported"); + } + + /** + * Obtain a {@code ResultSetExtractor} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}. + * + * @param reference must not be {@code null}. + * @since 3.4 + */ + default ResultSetExtractor getResultSetExtractor(String reference) { + throw new UnsupportedOperationException("getResultSetExtractor is not supported"); + } } /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java index 7e8da647ee..0876d099a3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java @@ -77,7 +77,6 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery { private final SpelEvaluator spelEvaluator; private final boolean containsSpelExpressions; private final String query; - private BeanFactory beanFactory; private final CachedRowMapperFactory cachedRowMapperFactory; private final CachedResultSetExtractorFactory cachedResultSetExtractorFactory; @@ -353,9 +352,8 @@ private static boolean isUnconfigured(@Nullable Class configuredClass, Class< return configuredClass == null || configuredClass == defaultClass; } - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } + @Deprecated(since = "3.4") + public void setBeanFactory(BeanFactory beanFactory) {} class CachedRowMapperFactory { @@ -380,10 +378,7 @@ public CachedRowMapperFactory(Supplier> defaultMapper) { this.cachedRowMapper = Lazy.of(() -> { if (!ObjectUtils.isEmpty(rowMapperRef)) { - - Assert.notNull(beanFactory, "When a RowMapperRef is specified the BeanFactory must not be null"); - - return (RowMapper) beanFactory.getBean(rowMapperRef); + return rowMapperFactory.getRowMapper(rowMapperRef); } if (isUnconfigured(rowMapperClass, RowMapper.class)) { @@ -434,10 +429,7 @@ public CachedResultSetExtractorFactory(Supplier> resultSetExtractor this.resultSetExtractorFactory = rowMapper -> { if (!ObjectUtils.isEmpty(resultSetExtractorRef)) { - - Assert.notNull(beanFactory, "When a ResultSetExtractorRef is specified the BeanFactory must not be null"); - - return (ResultSetExtractor) beanFactory.getBean(resultSetExtractorRef); + return rowMapperFactory.getResultSetExtractor(resultSetExtractorRef); } if (isUnconfigured(resultSetExtractorClass, ResultSetExtractor.class)) { 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 b1f3f5bd9f..d265b1becd 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 @@ -26,6 +26,7 @@ import org.springframework.data.jdbc.core.convert.EntityRowMapper; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.jdbc.repository.query.AbstractJdbcQuery; import org.springframework.data.jdbc.repository.query.JdbcQueryMethod; import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery; import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery; @@ -42,6 +43,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SingleColumnRowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -71,13 +73,12 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy { private final JdbcConverter converter; private final QueryMappingConfiguration queryMappingConfiguration; private final NamedParameterJdbcOperations operations; - @Nullable private final BeanFactory beanfactory; protected final QueryMethodEvaluationContextProvider evaluationContextProvider; JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - @Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) { + QueryMethodEvaluationContextProvider evaluationContextProvider) { super(context, dialect); @@ -85,7 +86,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy { Assert.notNull(converter, "JdbcConverter must not be null"); Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null"); Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); - Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvier must not be null"); + Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null"); this.context = context; this.publisher = publisher; @@ -93,7 +94,6 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy { this.converter = converter; this.queryMappingConfiguration = queryMappingConfiguration; this.operations = operations; - this.beanfactory = beanfactory; this.evaluationContextProvider = evaluationContextProvider; } @@ -112,9 +112,9 @@ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy { CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - @Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) { + QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory, + super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider); } @@ -138,12 +138,16 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository */ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy { + private final AbstractJdbcQuery.RowMapperFactory rowMapperFactory; + DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, @Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory, + super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider); + + this.rowMapperFactory = new BeanFactoryRowMapperFactory(beanfactory); } @Override @@ -161,15 +165,51 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery()); - StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryString, queryMethod, getOperations(), - this::createMapper, getConverter(), evaluationContextProvider); - query.setBeanFactory(getBeanFactory()); - return query; + return new StringBasedJdbcQuery(queryString, queryMethod, getOperations(), rowMapperFactory, getConverter(), + evaluationContextProvider); } throw new IllegalStateException( String.format("Did neither find a NamedQuery nor an annotated query for method %s", method)); } + + @SuppressWarnings("unchecked") + private class BeanFactoryRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory { + + private final @Nullable BeanFactory beanFactory; + + BeanFactoryRowMapperFactory(@Nullable BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public RowMapper create(Class result) { + return createMapper(result); + } + + @Override + public RowMapper getRowMapper(String reference) { + + if (beanFactory == null) { + throw new IllegalStateException( + "Cannot resolve RowMapper bean reference '" + reference + "'; BeanFactory is not configured."); + } + + return beanFactory.getBean(reference, RowMapper.class); + } + + @Override + public ResultSetExtractor getResultSetExtractor(String reference) { + + if (beanFactory == null) { + throw new IllegalStateException( + "Cannot resolve ResultSetExtractor bean reference '" + reference + "'; BeanFactory is not configured."); + } + + return beanFactory.getBean(reference, ResultSetExtractor.class); + } + } + } /** @@ -194,10 +234,10 @@ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy CreateIfNotFoundQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, RelationalMappingContext context, JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - @Nullable BeanFactory beanfactory, CreateQueryLookupStrategy createStrategy, + CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy, QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanfactory, + super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider); Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null"); @@ -254,23 +294,23 @@ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPubl Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, context, - converter, dialect, queryMappingConfiguration, operations, beanFactory, evaluationContextProvider); + converter, dialect, queryMappingConfiguration, operations, evaluationContextProvider); DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, beanFactory, evaluationContextProvider); - Key cleanedKey = key != null ? key : Key.CREATE_IF_NOT_FOUND; + Key keyToUse = key != null ? key : Key.CREATE_IF_NOT_FOUND; - LOG.debug(String.format("Using the queryLookupStrategy %s", cleanedKey)); + LOG.debug(String.format("Using the queryLookupStrategy %s", keyToUse)); - switch (cleanedKey) { + switch (keyToUse) { 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, + queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy, evaluationContextProvider); default: throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key)); @@ -285,11 +325,6 @@ NamedParameterJdbcOperations getOperations() { return operations; } - @Nullable - BeanFactory getBeanFactory() { - return beanfactory; - } - @SuppressWarnings("unchecked") RowMapper createMapper(Class returnedObjectType) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java index d5fabc8f7c..d9d0a0cc3e 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.springframework.beans.factory.BeanFactory; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; import org.springframework.data.convert.ReadingConverter; @@ -163,14 +162,9 @@ void cachesCustomMapperAndExtractorInstances() { @Test // GH-1721 void obtainsCustomRowMapperRef() { - BeanFactory beanFactory = mock(BeanFactory.class); - JdbcQueryMethod queryMethod = createMethod("findAllCustomRowMapperRef"); - StringBasedJdbcQuery query = createQuery(queryMethod); - query.setBeanFactory(beanFactory); - CustomRowMapper customRowMapper = new CustomRowMapper(); - - when(beanFactory.getBean("CustomRowMapper")).thenReturn(customRowMapper); + JdbcQueryMethod queryMethod = createMethod("findAllCustomRowMapperRef"); + StringBasedJdbcQuery query = createQuery(queryMethod, "CustomRowMapper", customRowMapper); RowMapper rowMapper = query.determineRowMapper(queryMethod.getResultProcessor(), false); ResultSetExtractor resultSetExtractor = query.determineResultSetExtractor(() -> { @@ -184,14 +178,9 @@ void obtainsCustomRowMapperRef() { @Test // GH-1721 void obtainsCustomResultSetExtractorRef() { - BeanFactory beanFactory = mock(BeanFactory.class); - JdbcQueryMethod queryMethod = createMethod("findAllCustomResultSetExtractorRef"); - StringBasedJdbcQuery query = createQuery(queryMethod); - query.setBeanFactory(beanFactory); - CustomResultSetExtractor cre = new CustomResultSetExtractor(); - - when(beanFactory.getBean("CustomResultSetExtractor")).thenReturn(cre); + JdbcQueryMethod queryMethod = createMethod("findAllCustomResultSetExtractorRef"); + StringBasedJdbcQuery query = createQuery(queryMethod, "CustomResultSetExtractor", cre); RowMapper rowMapper = query.determineRowMapper(queryMethod.getResultProcessor(), false); ResultSetExtractor resultSetExtractor = query.determineResultSetExtractor(() -> { @@ -332,10 +321,10 @@ void queryByListOfTuples() { String[][] tuples = { new String[] { "Albert", "Einstein" }, new String[] { "Richard", "Feynman" } }; SqlParameterSource parameterSource = forMethod("findByListOfTuples", List.class) // - .withArguments(Arrays.asList(tuples)) // + .withArguments(Arrays.asList(tuples))// .extractParameterSource(); - assertThat(parameterSource.getValue("tuples")).asInstanceOf(LIST) // + assertThat(parameterSource.getValue("tuples")).asInstanceOf(LIST)// .containsExactly(tuples); assertThat(parameterSource.getSqlType("tuples")).isEqualTo(JdbcUtil.TYPE_UNKNOWN.getVendorTypeNumber()); @@ -441,7 +430,11 @@ private JdbcQueryMethod createMethod(String methodName, Class... paramTypes) } private StringBasedJdbcQuery createQuery(JdbcQueryMethod queryMethod) { - return new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, evaluationContextProvider); + return createQuery(queryMethod, null, null); + } + + private StringBasedJdbcQuery createQuery(JdbcQueryMethod queryMethod, String preparedReference, Object value) { + return new StringBasedJdbcQuery(queryMethod, operations, new StubRowMapperFactory(preparedReference, value), converter, evaluationContextProvider); } interface MyRepository extends Repository { @@ -636,4 +629,37 @@ public Object getRootObject() { } } + private class StubRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory { + + private final String preparedReference; + private final Object value; + + StubRowMapperFactory(String preparedReference, Object value) { + this.preparedReference = preparedReference; + this.value = value; + } + + @Override + public RowMapper create(Class result) { + return defaultRowMapper; + } + + @Override + public RowMapper getRowMapper(String reference) { + + if (preparedReference.equals(reference)) { + return (RowMapper) value; + } + return AbstractJdbcQuery.RowMapperFactory.super.getRowMapper(reference); + } + + @Override + public ResultSetExtractor getResultSetExtractor(String reference) { + + if (preparedReference.equals(reference)) { + return (ResultSetExtractor) value; + } + return AbstractJdbcQuery.RowMapperFactory.super.getResultSetExtractor(reference); + } + } } diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 3168f9d4f6..cc366a6efc 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 0287ece743..bbb9d4f4e0 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.4.0-SNAPSHOT + 3.4.0-1872-refactor-SBJQ-SNAPSHOT