From 4e7a10939385b0909d2fc7b9035fe0c79b43a84d Mon Sep 17 00:00:00 2001 From: Evgeni Dimitrov Date: Tue, 6 Nov 2018 23:20:18 +0200 Subject: [PATCH] DATAJDBC-290 - Allow specification of resultsetExtractorClass. A @Query annotation may now specify a class implementing ResultSetExtractor to be useed for extracting objects from ResultSets. Original pull request: #101. --- .../repository/QueryMappingConfiguration.java | 27 +++ .../data/jdbc/repository/RowMapperMap.java | 44 ----- .../config/ConfigurableRowMapperMap.java | 75 -------- .../DefaultQueryMappingConfiguration.java | 57 ++++++ .../data/jdbc/repository/query/Query.java | 8 + .../support/InvalidQueryConfiguration.java | 19 ++ .../support/JdbcQueryLookupStrategy.java | 45 ++--- .../repository/support/JdbcQueryMethod.java | 10 + .../support/JdbcRepositoryFactory.java | 10 +- .../support/JdbcRepositoryFactoryBean.java | 14 +- .../support/JdbcRepositoryQuery.java | 52 ++++-- .../RowMapperResultsetExtractorEither.java | 76 ++++++++ ...MapResultSetExtractorIntegrationTests.java | 110 +++++++++++ ...oryResultSetExtractorIntegrationTests.java | 171 ++++++++++++++++++ .../ConfigurableRowMapperMapUnitTests.java | 96 ++++++++-- ...nableJdbcRepositoriesIntegrationTests.java | 36 +++- .../JdbcQueryLookupStrategyUnitTests.java | 23 ++- .../JdbcRepositoryFactoryBeanUnitTests.java | 4 +- .../support/JdbcRepositoryQueryUnitTests.java | 34 +++- ...esultSetExtractorIntegrationTests-hsql.sql | 1 + ...ltSetExtractorIntegrationTests-mariadb.sql | 1 + ...sultSetExtractorIntegrationTests-mssql.sql | 2 + ...sultSetExtractorIntegrationTests-mysql.sql | 1 + ...tSetExtractorIntegrationTests-postgres.sql | 2 + ...esultSetExtractorIntegrationTests-hsql.sql | 3 + ...ltSetExtractorIntegrationTests-mariadb.sql | 3 + ...sultSetExtractorIntegrationTests-mssql.sql | 5 + ...sultSetExtractorIntegrationTests-mysql.sql | 3 + ...tSetExtractorIntegrationTests-postgres.sql | 5 + 29 files changed, 732 insertions(+), 205 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/QueryMappingConfiguration.java delete mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java delete mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/InvalidQueryConfiguration.java create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/RowMapperResultsetExtractorEither.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryResultSetExtractorIntegrationTests.java create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-postgres.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-hsql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mariadb.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mssql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mysql.sql create mode 100644 spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-postgres.sql diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/QueryMappingConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/QueryMappingConfiguration.java new file mode 100644 index 00000000..f0ed2f83 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/QueryMappingConfiguration.java @@ -0,0 +1,27 @@ +package org.springframework.data.jdbc.repository; + +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; +import org.springframework.jdbc.core.ResultSetExtractor; + +/** + * A map from a type to a {@link ResultSetExtractor} to be used for extracting that type from {@link java.sql.ResultSet}s. + * + * @author Jens Schauder + * @author Evgeni Dimitrov + */ +public interface QueryMappingConfiguration { + RowMapperResultsetExtractorEither getMapper(Class type); + + /** + * An immutable empty instance that will return {@literal null} for all arguments. + */ + QueryMappingConfiguration EMPTY = new QueryMappingConfiguration() { + + @Override + public RowMapperResultsetExtractorEither getMapper(Class type) { + return null; + } + + }; + +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java deleted file mode 100644 index 085324d7..00000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2018 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.repository; - -import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; - -/** - * A map from a type to a {@link RowMapper} to be used for extracting that type from {@link java.sql.ResultSet}s. - * - * @author Jens Schauder - */ -public interface RowMapperMap { - - /** - * An immutable empty instance that will return {@literal null} for all arguments. - */ - RowMapperMap EMPTY = new RowMapperMap() { - - /* - * (non-Javadoc) - * @see org.springframework.data.jdbc.repository.RowMapperMap#rowMapperFor(java.lang.Class) - */ - public RowMapper rowMapperFor(Class type) { - return null; - } - }; - - @Nullable - RowMapper rowMapperFor(Class type); -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java deleted file mode 100644 index 592b7612..00000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.repository.config; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.data.jdbc.repository.RowMapperMap; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * A {@link RowMapperMap} that allows for registration of {@link RowMapper}s via a fluent Api. - * - * @author Jens Schauder - */ -public class ConfigurableRowMapperMap implements RowMapperMap { - - private Map, RowMapper> rowMappers = new LinkedHashMap<>(); - - /** - * Registers a the given {@link RowMapper} as to be used for the given type. - * - * @return this instance, so this can be used as a fluent interface. - */ - public ConfigurableRowMapperMap register(Class type, RowMapper rowMapper) { - - rowMappers.put(type, rowMapper); - return this; - } - - /** - * Returs a {@link RowMapper} for the given type if such a {@link RowMapper} is present. If an exact match is found - * that is returned. If not a {@link RowMapper} is returned that produces subtypes of the requested type. If no such - * {@link RowMapper} is found the method returns {@code null}. - * - * @param type the type to be produced by the returned {@link RowMapper}. Must not be {@code null}. - * @param the type to be produced by the returned {@link RowMapper}. - * @return Guaranteed to be not {@code null}. - */ - @SuppressWarnings("unchecked") - @Nullable - public RowMapper rowMapperFor(Class type) { - - Assert.notNull(type, "Type must not be null"); - - RowMapper candidate = (RowMapper) rowMappers.get(type); - - if (candidate == null) { - - for (Map.Entry, RowMapper> entry : rowMappers.entrySet()) { - - if (type.isAssignableFrom(entry.getKey())) { - candidate = (RowMapper) entry.getValue(); - } - } - } - - return candidate; - } -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java new file mode 100644 index 00000000..2d1b0810 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java @@ -0,0 +1,57 @@ +package org.springframework.data.jdbc.repository.config; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.Assert; + +/** + * A {@link QueryMappingConfiguration} that allows for registration of {@link RowMapper}s and {@link ResultSetExtractor}s via a fluent Api. + * + * @author Jens Schauder + * @author Evgeni Dimitrov + */ +public class DefaultQueryMappingConfiguration implements QueryMappingConfiguration{ + private Map, RowMapperResultsetExtractorEither> mappers = new LinkedHashMap<>(); + + public RowMapperResultsetExtractorEither getMapper(Class type) { + Assert.notNull(type, "Type must not be null"); + + RowMapperResultsetExtractorEither candidate = mappers.get(type); + + if (candidate == null) { + + for (Map.Entry, RowMapperResultsetExtractorEither> entry : mappers.entrySet()) { + + if (type.isAssignableFrom(entry.getKey())) { + candidate = entry.getValue(); + } + } + } + return candidate; + } + + /** + * Registers a the given {@link RowMapper} as to be used for the given type. + * + * @return this instance, so this can be used as a fluent interface. + */ + public DefaultQueryMappingConfiguration registerRowMapper(Class type, RowMapper rowMapper) { + mappers.put(type, RowMapperResultsetExtractorEither.of(rowMapper)); + return this; + } + + /** + * Registers a the given {@link ResultSetExtractor} as to be used for the given type. + * + * @return this instance, so this can be used as a fluent interface. + */ + public DefaultQueryMappingConfiguration registerResultSetExtractor(Class type, ResultSetExtractor resultSetExtractor) { + mappers.put(type, RowMapperResultsetExtractorEither.of(resultSetExtractor)); + return this; + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java index ce9c3731..796da650 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.data.annotation.QueryAnnotation; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; /** @@ -44,6 +45,13 @@ public @interface Query { /** * Optional {@link RowMapper} to use to convert the result of the query to domain class instances. + * Cannot be used along with {@link #resultSetExtractorClass()} only one of the two can be set. */ Class rowMapperClass() default RowMapper.class; + + /** + * Optional {@link ResultSetExtractor} to use to convert the result of the query to domain class instances. + * Cannot be used along with {@link #rowMapperClass()} only one of the two can be set. + */ + Class resultSetExtractorClass() default ResultSetExtractor.class; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/InvalidQueryConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/InvalidQueryConfiguration.java new file mode 100644 index 00000000..bd059ad8 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/InvalidQueryConfiguration.java @@ -0,0 +1,19 @@ +package org.springframework.data.jdbc.repository.support; + +import org.springframework.data.jdbc.repository.query.Query; + +/** + * Exception thrown when both {@link Query#resultSetExtractorClass()} and {@link Query#rowMapperClass()} are used in one {@link Query}. + * + * @author Evgeni Dimitrov + */ +public class InvalidQueryConfiguration extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = 6604189906427682546L; + + public InvalidQueryConfiguration(String message) { + super(message); + } +} 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 113b1da7..3c3d27a9 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 @@ -20,7 +20,8 @@ import java.lang.reflect.Method; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.EntityRowMapper; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -29,6 +30,7 @@ import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryLookupStrategy; 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; @@ -49,12 +51,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { private final RelationalMappingContext context; private final RelationalConverter converter; private final DataAccessStrategy accessStrategy; - private final RowMapperMap rowMapperMap; + private final QueryMappingConfiguration mapperMap; private final NamedParameterJdbcOperations operations; /** * Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext}, - * {@link DataAccessStrategy} and {@link RowMapperMap}. + * {@link DataAccessStrategy} and {@link QueryMappingConfiguration}. * * @param publisher must not be {@literal null}. * @param context must not be {@literal null}. @@ -63,7 +65,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { * @param rowMapperMap must not be {@literal null}. */ JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context, RelationalConverter converter, - DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap, NamedParameterJdbcOperations operations) { + DataAccessStrategy accessStrategy, QueryMappingConfiguration rowMapperMap, NamedParameterJdbcOperations operations) { Assert.notNull(publisher, "Publisher must not be null!"); Assert.notNull(context, "RelationalMappingContext must not be null!"); @@ -75,7 +77,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { this.context = context; this.converter = converter; this.accessStrategy = accessStrategy; - this.rowMapperMap = rowMapperMap; + this.mapperMap = rowMapperMap; this.operations = operations; } @@ -89,36 +91,35 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory); - RowMapper rowMapper = queryMethod.isModifyingQuery() ? null : createRowMapper(queryMethod); + RowMapperResultsetExtractorEither mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod); - return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, rowMapper); + return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, mapper); } - private RowMapper createRowMapper(JdbcQueryMethod queryMethod) { + private RowMapperResultsetExtractorEither createMapper(JdbcQueryMethod queryMethod) { Class returnedObjectType = queryMethod.getReturnedObjectType(); RelationalPersistentEntity persistentEntity = context.getPersistentEntity(returnedObjectType); if (persistentEntity == null) { - return SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService()); + return RowMapperResultsetExtractorEither.of( + SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService())); } - return determineDefaultRowMapper(queryMethod); + return determineDefaultMapper(queryMethod); } - private RowMapper determineDefaultRowMapper(JdbcQueryMethod queryMethod) { - + private RowMapperResultsetExtractorEither determineDefaultMapper(JdbcQueryMethod queryMethod) { Class domainType = queryMethod.getReturnedObjectType(); - - RowMapper typeMappedRowMapper = rowMapperMap.rowMapperFor(domainType); - - return typeMappedRowMapper == null // - ? new EntityRowMapper<>( // - context.getRequiredPersistentEntity(domainType), // - context, // - converter, // - accessStrategy) // - : typeMappedRowMapper; + RowMapperResultsetExtractorEither configuredQueryMapper = mapperMap.getMapper(domainType); + if(configuredQueryMapper != null) return configuredQueryMapper; + + EntityRowMapper defaultEntityRowMapper = new EntityRowMapper<>( // + context.getRequiredPersistentEntity(domainType), // + context, // + converter, // + accessStrategy); + return RowMapperResultsetExtractorEither.of(defaultEntityRowMapper); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java index 1cbc40ce..6eb61740 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java @@ -63,6 +63,16 @@ public class JdbcQueryMethod extends QueryMethod { public Class getRowMapperClass() { return getMergedAnnotationAttribute("rowMapperClass"); } + + /** + * Returns the class to be used as {@link org.springframework.jdbc.core.ResultSetExtractor} + * + * @return May be {@code null}. + */ + @Nullable + public Class getResultSetExtractorClass() { + return getMergedAnnotationAttribute("resultSetExtractorClass"); + } /** * Returns whether the query method is a modifying one. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index 30c11454..95be4d57 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -20,7 +20,7 @@ import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.JdbcAggregateTemplate; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -51,7 +51,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { private final DataAccessStrategy accessStrategy; private final NamedParameterJdbcOperations operations; - private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; + private QueryMappingConfiguration mapperMap = QueryMappingConfiguration.EMPTY; /** * Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy}, @@ -81,11 +81,11 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { /** * @param rowMapperMap must not be {@literal null} consider {@link RowMapperMap#EMPTY} instead. */ - public void setRowMapperMap(RowMapperMap rowMapperMap) { + public void setRowMapperMap(QueryMappingConfiguration rowMapperMap) { Assert.notNull(rowMapperMap, "RowMapperMap must not be null!"); - this.rowMapperMap = rowMapperMap; + this.mapperMap = rowMapperMap; } @SuppressWarnings("unchecked") @@ -133,6 +133,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } - return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, rowMapperMap, operations)); + return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, mapperMap, operations)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 927afcc8..aa63d89f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -23,7 +23,7 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.SqlGeneratorSource; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.Repository; @@ -49,7 +49,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend private RelationalMappingContext mappingContext; private RelationalConverter converter; private DataAccessStrategy dataAccessStrategy; - private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; + private QueryMappingConfiguration mapperMap = QueryMappingConfiguration.EMPTY; private NamedParameterJdbcOperations operations; /** @@ -81,7 +81,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext, converter, publisher, operations); - jdbcRepositoryFactory.setRowMapperMap(rowMapperMap); + jdbcRepositoryFactory.setRowMapperMap(mapperMap); return jdbcRepositoryFactory; } @@ -106,8 +106,8 @@ public class JdbcRepositoryFactoryBean, S, ID extend * {@literal null}. */ @Autowired(required = false) - public void setRowMapperMap(RowMapperMap rowMapperMap) { - this.rowMapperMap = rowMapperMap; + public void setRowMapperMap(QueryMappingConfiguration rowMapperMap) { + this.mapperMap = rowMapperMap; } @Autowired @@ -137,8 +137,8 @@ public class JdbcRepositoryFactoryBean, S, ID extend operations); } - if (rowMapperMap == null) { - this.rowMapperMap = RowMapperMap.EMPTY; + if (mapperMap == null) { + this.mapperMap = QueryMappingConfiguration.EMPTY; } super.afterPropertiesSet(); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java index ec36e9a9..cc9caf6b 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java @@ -18,11 +18,13 @@ package org.springframework.data.jdbc.repository.support; import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.relational.core.mapping.event.Identifier; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -49,7 +51,7 @@ class JdbcRepositoryQuery implements RepositoryQuery { private final RelationalMappingContext context; private final JdbcQueryMethod queryMethod; private final NamedParameterJdbcOperations operations; - private final RowMapper rowMapper; + private final RowMapperResultsetExtractorEither mapper; /** * Creates a new {@link JdbcRepositoryQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext} @@ -62,7 +64,7 @@ class JdbcRepositoryQuery implements RepositoryQuery { * @param defaultRowMapper can be {@literal null} (only in case of a modifying query). */ JdbcRepositoryQuery(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations, - @Nullable RowMapper defaultRowMapper) { + @Nullable RowMapperResultsetExtractorEither defaultMapper) { Assert.notNull(publisher, "Publisher must not be null!"); Assert.notNull(context, "Context must not be null!"); @@ -70,14 +72,14 @@ class JdbcRepositoryQuery implements RepositoryQuery { Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!"); if (!queryMethod.isModifyingQuery()) { - Assert.notNull(defaultRowMapper, "RowMapper must not be null!"); + Assert.notNull(defaultMapper, "Mapper must not be null!"); } this.publisher = publisher; this.context = context; this.queryMethod = queryMethod; this.operations = operations; - this.rowMapper = createRowMapper(queryMethod, defaultRowMapper); + this.mapper = determineMapper(defaultMapper); } /* @@ -99,15 +101,23 @@ class JdbcRepositoryQuery implements RepositoryQuery { } if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) { - - List result = operations.query(query, parameters, rowMapper); + List result = null; + if(this.mapper.isResultSetExtractor()) { + result = (List) operations.query(query, parameters, this.mapper.resultSetExtractor()); + } else { + result = operations.query(query, parameters, this.mapper.rowMapper()); + } publishAfterLoad(result); return result; } try { - - Object result = operations.queryForObject(query, parameters, rowMapper); + Object result = null; + if(this.mapper.isResultSetExtractor()) { + result = operations.query(query,parameters, this.mapper.resultSetExtractor()); + } else { + result = operations.queryForObject(query, parameters, this.mapper.rowMapper()); + } publishAfterLoad(result); return result; } catch (EmptyResultDataAccessException e) { @@ -148,14 +158,30 @@ class JdbcRepositoryQuery implements RepositoryQuery { return parameters; } + private RowMapperResultsetExtractorEither determineMapper(RowMapperResultsetExtractorEither defaultMapper) { + RowMapperResultsetExtractorEither configuredMapper = getConfiguredMapper(queryMethod); + if(configuredMapper != null) return configuredMapper; + return defaultMapper; + } + @Nullable - private static RowMapper createRowMapper(JdbcQueryMethod queryMethod, @Nullable RowMapper defaultRowMapper) { - + private static RowMapperResultsetExtractorEither getConfiguredMapper(JdbcQueryMethod queryMethod) { Class rowMapperClass = queryMethod.getRowMapperClass(); + Class resultSetExtractorClass = queryMethod.getResultSetExtractorClass(); + if(isConfigured(rowMapperClass, RowMapper.class) && isConfigured(resultSetExtractorClass, ResultSetExtractor.class)) + throw new InvalidQueryConfiguration("Cannot use both rowMapperClass and resultSetExtractorClass on @Query annotation. Query method: [" + queryMethod.getName() + "] query: [" + queryMethod.getAnnotatedQuery() + "]"); + + if(!isConfigured(rowMapperClass, RowMapper.class) && !isConfigured(resultSetExtractorClass, ResultSetExtractor.class)) + return null; + if(isConfigured(rowMapperClass, RowMapper.class)) { + return RowMapperResultsetExtractorEither.of((RowMapper) BeanUtils.instantiateClass(rowMapperClass)); + } else { + return RowMapperResultsetExtractorEither.of((ResultSetExtractor) BeanUtils.instantiateClass(resultSetExtractorClass)); + } + } - return rowMapperClass == null || rowMapperClass == RowMapper.class // - ? defaultRowMapper // - : (RowMapper) BeanUtils.instantiateClass(rowMapperClass); + private static boolean isConfigured(Class rowMapperClass, Class defaultClass) { + return rowMapperClass != null && rowMapperClass != defaultClass; } private void publishAfterLoad(Iterable all) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/RowMapperResultsetExtractorEither.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/RowMapperResultsetExtractorEither.java new file mode 100644 index 00000000..b24b651c --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/RowMapperResultsetExtractorEither.java @@ -0,0 +1,76 @@ +package org.springframework.data.jdbc.support; + +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +/** + * Represents either a RowMapper or a ResultSetExtractor + * + * @author Evgeni Dimitrov + */ +public class RowMapperResultsetExtractorEither { + private final RowMapper rowMapper; + private final ResultSetExtractor resultSetExtractor; + + private RowMapperResultsetExtractorEither(RowMapper rowMapper, ResultSetExtractor resultSetExtractor) { + this.rowMapper = rowMapper; + this.resultSetExtractor = resultSetExtractor; + } + + public static RowMapperResultsetExtractorEither of(RowMapper rowMapper) { + return new RowMapperResultsetExtractorEither<>(rowMapper, null); + } + + public boolean isRowMapper() { + return this.rowMapper != null; + } + + public RowMapper rowMapper() { + return this.rowMapper; + } + + public static RowMapperResultsetExtractorEither of(ResultSetExtractor resultSetExtractor) { + return new RowMapperResultsetExtractorEither<>(null, resultSetExtractor); + } + + public boolean isResultSetExtractor() { + return this.resultSetExtractor != null; + } + + public ResultSetExtractor resultSetExtractor() { + return this.resultSetExtractor; + } + + @Override + public String toString() { + return String.format("RowMapperResultsetExtractorEither[%s]", this.rowMapper != null ? this.rowMapper : this.resultSetExtractor); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((resultSetExtractor == null) ? 0 : resultSetExtractor.hashCode()); + result = prime * result + ((rowMapper == null) ? 0 : rowMapper.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + RowMapperResultsetExtractorEither other = (RowMapperResultsetExtractorEither) obj; + if (resultSetExtractor == null) { + if (other.resultSetExtractor != null) return false; + } else { + if (!resultSetExtractor.equals(other.resultSetExtractor)) return false; + } + if (rowMapper == null) { + if (other.rowMapper != null) return false; + } else { + if (!rowMapper.equals(other.rowMapper)) return false; + } + return true; + } + +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.java new file mode 100644 index 00000000..0c2cf97e --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.dao.DataAccessException; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.transaction.annotation.Transactional; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Very simple use cases for creation and usage of {@link ResultSetExtractor}s in JdbcRepository. + * + * @author Evgeni Dimitrov + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryMapperMapResultSetExtractorIntegrationTests { + private static String CAR_MODEL = "ResultSetExtracotr Car"; + @Configuration + @Import(TestConfiguration.class) + @EnableJdbcRepositories(considerNestedRepositories = true) + static class Config { + + @Bean + Class testClass() { + return JdbcRepositoryMapperMapResultSetExtractorIntegrationTests.class; + } + + @Bean + QueryMappingConfiguration mappers() { + return new DefaultQueryMappingConfiguration() + .registerResultSetExtractor(Car.class, new CarResultSetExtractor()); + } + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired CarRepository carRepository; + + @Test // DATAJDBC-290 + public void customFindAllCarsPicksResultSetExtractorFromMapperMap() { + carRepository.save(new Car(null, "Some model")); + Iterable cars = carRepository.customFindAll(); + assertThat(cars).hasSize(1); + assertThat(cars).allMatch(car -> CAR_MODEL.equals(car.getModel())); + } + + interface CarRepository extends CrudRepository { + @Query("select * from car") + public List customFindAll(); + } + + @Data + @AllArgsConstructor + static class Car { + @Id + private Long id; + private String model; + } + + static class CarResultSetExtractor implements ResultSetExtractor> { + + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + return Arrays.asList(new Car(1L, CAR_MODEL)); + } + + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryResultSetExtractorIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryResultSetExtractorIntegrationTests.java new file mode 100644 index 00000000..b474d9de --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryResultSetExtractorIntegrationTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.RecoverableDataAccessException; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; +import org.springframework.transaction.annotation.Transactional; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Very simple use cases for creation and usage of {@link ResultSetExtractor}s in JdbcRepository. + * + * @author Evgeni Dimitrov + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryResultSetExtractorIntegrationTests { + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryResultSetExtractorIntegrationTests.class; + } + + @Bean + PersonRepository personEntityRepository() { + return factory.getRepository(PersonRepository.class); + } + + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired PersonRepository personRepository; + + @Test // DATAJDBC-290 + public void findAllPeopleWithAdressesReturnsEmptyWhenNoneFound() { + // NOT saving anything, so DB is empty + assertThat(personRepository.findAllPeopleWithAdresses()).isEmpty(); + } + + @Test // DATAJDBC-290 + public void findAllPeopleWithAdressesReturnsOnePersonWithoutAdresses() { + personRepository.save(new Person(null, "Joe", null)); + assertThat(personRepository.findAllPeopleWithAdresses()).hasSize(1); + } + + @Test // DATAJDBC-290 + public void findAllPeopleWithAdressesReturnsOnePersonWithAdresses() { + final String personName = "Joe"; + Person savedPerson = personRepository.save(new Person(null, personName, null)); + String street1 = "Klokotnitsa"; + MapSqlParameterSource paramsAddress1 = buildAddressParameters(savedPerson.getId(), street1); + template.update("insert into address (street, person_id) values (:street, :personId)",paramsAddress1); + String street2 = "bul. Hristo Botev"; + MapSqlParameterSource paramsAddress2 = buildAddressParameters(savedPerson.getId(), street2); + template.update("insert into address (street, person_id) values (:street, :personId)",paramsAddress2); + + List people = personRepository.findAllPeopleWithAdresses(); + assertThat(people).hasSize(1); + Person person = people.get(0); + assertThat(person.getName()).isEqualTo(personName); + assertThat(person.getAdresses()).hasSize(2); + assertThat(person.getAdresses()).extracting(a -> a.getStreet()).containsExactlyInAnyOrder(street1, street2); + } + private MapSqlParameterSource buildAddressParameters(Long id, String streetName) { + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("street", streetName, Types.VARCHAR); + params.addValue("personId", id, Types.NUMERIC); + return params; + } + + interface PersonRepository extends CrudRepository { + @Query(value="select p.id, p.name, a.id addrId, a.street from person p left join address a on(p.id = a.person_id)", + resultSetExtractorClass=PersonResultSetExtractor.class) + public List findAllPeopleWithAdresses(); + } + + @Data + @AllArgsConstructor + static class Person { + @Id + private Long id; + private String name; + private List
adresses; + } + + @Data + @AllArgsConstructor + static class Address { + @Id + private Long id; + private String street; + } + + static class PersonResultSetExtractor implements ResultSetExtractor> { + + @Override + public List extractData(ResultSet rs) throws SQLException, DataAccessException { + Map peopleById = new HashMap<>(); + while(rs.next()) { + long personId = rs.getLong("id"); + Person currentPerson = peopleById.computeIfAbsent(personId, t -> { + try { + return new Person(personId, rs.getString("name"), new ArrayList<>()); + } catch (SQLException e) { + throw new RecoverableDataAccessException("Error mapping Person", e); + } + }); + + if(currentPerson.getAdresses() == null) currentPerson.setAdresses(new ArrayList<>()); + long addrId = rs.getLong("addrId"); + if(!rs.wasNull()) { + currentPerson.getAdresses().add(new Address(addrId, rs.getString("street"))); + } + } + + return new ArrayList(peopleById.values()); + } + + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java index 6861d9a4..c994adde 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java @@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import org.junit.Test; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; /** @@ -32,9 +34,9 @@ public class ConfigurableRowMapperMapUnitTests { @Test public void freshInstanceReturnsNull() { - RowMapperMap map = new ConfigurableRowMapperMap(); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration(); - assertThat(map.rowMapperFor(Object.class)).isNull(); + assertThat(map.getMapper(Object.class)).isNull(); } @Test @@ -42,9 +44,19 @@ public class ConfigurableRowMapperMapUnitTests { RowMapper rowMapper = mock(RowMapper.class); - RowMapperMap map = new ConfigurableRowMapperMap().register(Object.class, rowMapper); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(Object.class, rowMapper); - assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper)); + } + + @Test + public void returnsConfiguredInstanceResultSetExtractorForClass() { + + ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class); + + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(Object.class, resultSetExtractor); + + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor)); } @Test @@ -52,10 +64,21 @@ public class ConfigurableRowMapperMapUnitTests { RowMapper rowMapper = mock(RowMapper.class); - RowMapperMap map = new ConfigurableRowMapperMap().register(Number.class, rowMapper); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(Number.class, rowMapper); - assertThat(map.rowMapperFor(Integer.class)).isNull(); - assertThat(map.rowMapperFor(String.class)).isNull(); + assertThat(map.getMapper(Integer.class)).isNull(); + assertThat(map.getMapper(String.class)).isNull(); + } + + @Test + public void returnsNullResultSetExtractorForClassNotConfigured() { + + ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class); + + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(Number.class, resultSetExtractor); + + assertThat(map.getMapper(Integer.class)).isNull(); + assertThat(map.getMapper(String.class)).isNull(); } @Test @@ -63,9 +86,19 @@ public class ConfigurableRowMapperMapUnitTests { RowMapper rowMapper = mock(RowMapper.class); - RowMapperMap map = new ConfigurableRowMapperMap().register(String.class, rowMapper); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerRowMapper(String.class, rowMapper); - assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper)); + } + + @Test + public void returnsInstanceOfResultSetExtractorRegisteredForSubClass() { + + ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class); + + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration().registerResultSetExtractor(String.class, resultSetExtractor); + + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor)); } @Test @@ -73,12 +106,25 @@ public class ConfigurableRowMapperMapUnitTests { RowMapper rowMapper = mock(RowMapper.class); - RowMapperMap map = new ConfigurableRowMapperMap() // - .register(Object.class, mock(RowMapper.class)) // - .register(Integer.class, rowMapper) // - .register(Number.class, mock(RowMapper.class)); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() // + .registerRowMapper(Object.class, mock(RowMapper.class)) // + .registerRowMapper(Integer.class, rowMapper) // + .registerRowMapper(Number.class, mock(RowMapper.class)); - assertThat(map.rowMapperFor(Integer.class)).isEqualTo(rowMapper); + assertThat(map.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper)); + } + + @Test + public void prefersExactResultSetExtractorTypeMatchClass() { + + ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class); + + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() // + .registerResultSetExtractor(Object.class, mock(ResultSetExtractor.class)) // + .registerResultSetExtractor(Integer.class, resultSetExtractor) // + .registerResultSetExtractor(Number.class, mock(ResultSetExtractor.class)); + + assertThat(map.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor)); } @Test @@ -86,10 +132,22 @@ public class ConfigurableRowMapperMapUnitTests { RowMapper rowMapper = mock(RowMapper.class); - RowMapperMap map = new ConfigurableRowMapperMap() // - .register(Integer.class, mock(RowMapper.class)) // - .register(Number.class, rowMapper); + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() // + .registerRowMapper(Integer.class, mock(RowMapper.class)) // + .registerRowMapper(Number.class, rowMapper); - assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(rowMapper)); + } + + @Test + public void prefersLatestRegistrationOfResultSetExtractorForSuperTypeMatch() { + + ResultSetExtractor resultSetExtractor = mock(ResultSetExtractor.class); + + QueryMappingConfiguration map = new DefaultQueryMappingConfiguration() // + .registerResultSetExtractor(Integer.class, mock(ResultSetExtractor.class)) // + .registerResultSetExtractor(Number.class, resultSetExtractor); + + assertThat(map.getMapper(Object.class)).isEqualTo(RowMapperResultsetExtractorEither.of(resultSetExtractor)); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index 32e7e2af..9f82d975 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -30,10 +30,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.data.annotation.Id; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -49,16 +51,17 @@ import org.springframework.util.ReflectionUtils; @ContextConfiguration(classes = TestConfiguration.class) public class EnableJdbcRepositoriesIntegrationTests { - static final Field ROW_MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "rowMapperMap"); + static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "mapperMap"); public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class); public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class); + public static final ResultSetExtractor INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class); @Autowired JdbcRepositoryFactoryBean factoryBean; @Autowired DummyRepository repository; @BeforeClass public static void setup() { - ROW_MAPPER_MAP.setAccessible(true); + MAPPER_MAP.setAccessible(true); } @Test // DATAJDBC-100 @@ -71,13 +74,25 @@ public class EnableJdbcRepositoriesIntegrationTests { assertThat(all).isNotNull(); } + @Test // DATAJDBC-290 + public void customResultSetExtractorConfigurationGetsPickedUp() { + QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean); + assertThat(mapping.getMapper(Integer.class)).isEqualTo(RowMapperResultsetExtractorEither.of(INTEGER_RESULT_SET_EXTRACTOR)); + } + + @Test // DATAJDBC-290 + public void customResultSetExtractorConfigurationIsNotPickedUpIfRowMapperIsRegisteredForTheSameType() { + QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean); + assertThat(mapping.getMapper(String.class).isResultSetExtractor()).isFalse(); + } + @Test // DATAJDBC-166 public void customRowMapperConfigurationGetsPickedUp() { - RowMapperMap mapping = (RowMapperMap) ReflectionUtils.getField(ROW_MAPPER_MAP, factoryBean); + QueryMappingConfiguration mapping = (QueryMappingConfiguration) ReflectionUtils.getField(MAPPER_MAP, factoryBean); - assertThat(mapping.rowMapperFor(String.class)).isEqualTo(STRING_ROW_MAPPER); - assertThat(mapping.rowMapperFor(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER); + assertThat(mapping.getMapper(String.class)).isEqualTo(RowMapperResultsetExtractorEither.of(STRING_ROW_MAPPER)); + assertThat(mapping.getMapper(DummyEntity.class)).isEqualTo(RowMapperResultsetExtractorEither.of(DUMMY_ENTITY_ROW_MAPPER)); } interface DummyRepository extends CrudRepository { @@ -100,10 +115,11 @@ public class EnableJdbcRepositoriesIntegrationTests { } @Bean - RowMapperMap rowMappers() { - return new ConfigurableRowMapperMap() // - .register(DummyEntity.class, DUMMY_ENTITY_ROW_MAPPER) // - .register(String.class, STRING_ROW_MAPPER); + QueryMappingConfiguration rowMappers() { + return new DefaultQueryMappingConfiguration() // + .registerRowMapper(DummyEntity.class, DUMMY_ENTITY_ROW_MAPPER) // + .registerRowMapper(String.class, STRING_ROW_MAPPER) + .registerResultSetExtractor(Integer.class, INTEGER_RESULT_SET_EXTRACTOR); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java index c6dba937..eb5be6a9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java @@ -27,8 +27,8 @@ import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.DataAccessStrategy; -import org.springframework.data.jdbc.repository.RowMapperMap; -import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; +import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; @@ -37,6 +37,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -74,7 +75,7 @@ public class JdbcQueryLookupStrategyUnitTests { public void typeBasedRowMapperGetsUsedForQuery() { RowMapper numberFormatMapper = mock(RowMapper.class); - RowMapperMap rowMapperMap = new ConfigurableRowMapperMap().register(NumberFormat.class, numberFormatMapper); + QueryMappingConfiguration rowMapperMap = new DefaultQueryMappingConfiguration().registerRowMapper(NumberFormat.class, numberFormatMapper); RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", rowMapperMap); @@ -82,8 +83,22 @@ public class JdbcQueryLookupStrategyUnitTests { verify(operations).queryForObject(anyString(), any(SqlParameterSource.class), eq(numberFormatMapper)); } + + @Test // DATAJDBC-290 + @SuppressWarnings("unchecked") + public void typeBasedResultSetExtractorGetsUsedForQuery() { - private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) { + ResultSetExtractor numberFormatMapper = mock(ResultSetExtractor.class); + QueryMappingConfiguration rowMapperMap = new DefaultQueryMappingConfiguration().registerResultSetExtractor(NumberFormat.class, numberFormatMapper); + + RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", rowMapperMap); + + repositoryQuery.execute(new Object[] {}); + + verify(operations).query(anyString(), any(SqlParameterSource.class), eq(numberFormatMapper)); + } + + private RepositoryQuery getRepositoryQuery(String name, QueryMappingConfiguration rowMapperMap) { JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(publisher, mappingContext, converter, accessStrategy, rowMapperMap, operations); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java index d8026901..bdf19a88 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -29,7 +29,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.DefaultDataAccessStrategy; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.QueryMappingConfiguration; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.repository.CrudRepository; @@ -100,7 +100,7 @@ public class JdbcRepositoryFactoryBeanUnitTests { assertThat(factoryBean.getObject()).isNotNull(); assertThat(ReflectionTestUtils.getField(factoryBean, "dataAccessStrategy")) .isInstanceOf(DefaultDataAccessStrategy.class); - assertThat(ReflectionTestUtils.getField(factoryBean, "rowMapperMap")).isEqualTo(RowMapperMap.EMPTY); + assertThat(ReflectionTestUtils.getField(factoryBean, "mapperMap")).isEqualTo(QueryMappingConfiguration.EMPTY); } private static class DummyEntity { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java index 2b70a0b5..22a2b0c0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java @@ -22,16 +22,20 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.Arrays; import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.DataAccessException; +import org.springframework.data.jdbc.support.RowMapperResultsetExtractorEither; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.repository.query.DefaultParameters; import org.springframework.data.repository.query.Parameters; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -48,6 +52,7 @@ public class JdbcRepositoryQueryUnitTests { JdbcQueryMethod queryMethod; RowMapper defaultRowMapper; + ResultSetExtractor defaultResultSetExtractor; JdbcRepositoryQuery query; NamedParameterJdbcOperations operations; ApplicationEventPublisher publisher; @@ -67,7 +72,7 @@ public class JdbcRepositoryQueryUnitTests { this.publisher = mock(ApplicationEventPublisher.class); this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS); - this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper); + this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)); } @Test // DATAJDBC-165 @@ -106,11 +111,24 @@ public class JdbcRepositoryQueryUnitTests { doReturn("some sql statement").when(queryMethod).getAnnotatedQuery(); doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass(); - new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {}); + new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {}); verify(operations) // .queryForObject(anyString(), any(SqlParameterSource.class), isA(CustomRowMapper.class)); } + + @Test // DATAJDBC-290 + public void customResultSetExtractorIsUsedWhenSpecified() { + + doReturn("some sql statement").when(queryMethod).getAnnotatedQuery(); + doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass(); + + new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {}); + + verify(operations) // + .query(anyString(), any(SqlParameterSource.class), isA(CustomResultSetExtractor.class)); + } + @Test // DATAJDBC-263 public void publishesSingleEventWhenQueryReturnsSingleAggregate() { @@ -121,7 +139,7 @@ public class JdbcRepositoryQueryUnitTests { doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class); when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier"); - new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {}); + new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {}); verify(publisher).publishEvent(any(AfterLoadEvent.class)); } @@ -135,7 +153,7 @@ public class JdbcRepositoryQueryUnitTests { doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class); when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier"); - new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {}); + new JdbcRepositoryQuery(publisher, context, queryMethod, operations, RowMapperResultsetExtractorEither.of(defaultRowMapper)).execute(new Object[] {}); verify(publisher, times(2)).publishEvent(any(AfterLoadEvent.class)); } @@ -153,6 +171,14 @@ public class JdbcRepositoryQueryUnitTests { return null; } } + + private static class CustomResultSetExtractor implements ResultSetExtractor { + + @Override + public Object extractData(ResultSet rs) throws SQLException, DataAccessException { + return null; + } + } private static class DummyEntity { private Long id; diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-hsql.sql new file mode 100644 index 00000000..9d5026bc --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-hsql.sql @@ -0,0 +1 @@ +CREATE TABLE car ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, model VARCHAR(100)); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mariadb.sql new file mode 100644 index 00000000..41797233 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mariadb.sql @@ -0,0 +1 @@ +CREATE TABLE car ( id INT NOT NULL AUTO_INCREMENT, model VARCHAR(100), PRIMARY KEY (id)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mssql.sql new file mode 100644 index 00000000..60acad12 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mssql.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS car; +CREATE TABLE car ( id int IDENTITY(1,1) PRIMARY KEY, model VARCHAR(100)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mysql.sql new file mode 100644 index 00000000..41797233 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-mysql.sql @@ -0,0 +1 @@ +CREATE TABLE car ( id INT NOT NULL AUTO_INCREMENT, model VARCHAR(100), PRIMARY KEY (id)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-postgres.sql new file mode 100644 index 00000000..0118aeda --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryMapperMapResultSetExtractorIntegrationTests-postgres.sql @@ -0,0 +1,2 @@ +DROP TABLE car; +CREATE TABLE car ( id SERIAL PRIMARY KEY, model VARCHAR(100)); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-hsql.sql new file mode 100644 index 00000000..a33c466a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-hsql.sql @@ -0,0 +1,3 @@ +CREATE TABLE person ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, name VARCHAR(100)); +CREATE TABLE address ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, street VARCHAR(100), person_id BIGINT); +ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mariadb.sql new file mode 100644 index 00000000..40c0f819 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mariadb.sql @@ -0,0 +1,3 @@ +CREATE TABLE person ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id)); +CREATE TABLE address ( id INT NOT NULL AUTO_INCREMENT, street VARCHAR(100), person_id INT, PRIMARY KEY (id)); +ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mssql.sql new file mode 100644 index 00000000..33556559 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mssql.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS person; +DROP TABLE IF EXISTS address; +CREATE TABLE person ( id int IDENTITY(1,1) PRIMARY KEY, name VARCHAR(100)); +CREATE TABLE address ( id int IDENTITY(1,1) PRIMARY KEY, street VARCHAR(100), person_id INT); +ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mysql.sql new file mode 100644 index 00000000..40c0f819 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-mysql.sql @@ -0,0 +1,3 @@ +CREATE TABLE person ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id)); +CREATE TABLE address ( id INT NOT NULL AUTO_INCREMENT, street VARCHAR(100), person_id INT, PRIMARY KEY (id)); +ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-postgres.sql new file mode 100644 index 00000000..36843410 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-postgres.sql @@ -0,0 +1,5 @@ +DROP TABLE person; +DROP TABLE address; +CREATE TABLE person ( id SERIAL PRIMARY KEY, name VARCHAR(100)); +CREATE TABLE address ( id SERIAL PRIMARY KEY, street VARCHAR(100), person_id INT); +ALTER TABLE address ADD FOREIGN KEY (person_id) REFERENCES person(id); \ No newline at end of file