diff --git a/README.adoc b/README.adoc index 42ae7cf7..9882edae 100644 --- a/README.adoc +++ b/README.adoc @@ -124,6 +124,34 @@ List findByNameRange(@Param("lower") String lower, @Param("upper") If you compile your sources with the `-parameters` compiler flag you can omit the `@Param` annotations. +==== Custom RowMapper + +You can configure the `RowMapper` to use using either the `@Query(rowMapperClass = ....)` or you can register a `RowMapperMap` bean and register `RowMapper` per method return type. + +[source,java] +---- + + @Bean + RowMapperMap rowMappers() { + return new ConfigurableRowMapperMap() // + .register(Person.class, new PersonRowMapper()) // + .register(Address.class, new AddressRowMapper()); + } + +---- + +When determining the `RowMapper` to use for a method the following steps are followed based on the return type of the method: + +1. If the type is a simple type no `RowMapper` is used. + Instead the query is expected to return a single row with a single column and a conversion to the return type is applied to that value. + +2. The entity classes in the `RowMapperMap` are iterated until one is found that is a superclass or interface of the return type in question. + The `RowMapper` registered for that class is used. + Iterating happens in the order of registration, so make sure to register more general types after specific ones. + +If applicable wrapper type like collections or `Optional` are unwrapped. +So a return type of `Optional` will use the type `Person` in the steps above. + === Id generation Spring Data JDBC uses the id to identify entities but also to determine if an entity is new or already existing in the database. diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index bbade9dc..af72d133 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -283,7 +283,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { } public EntityRowMapper getEntityRowMapper(Class domainType) { - return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context.getConversions(), context, accessStrategy); + return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, accessStrategy); } private RowMapper getMapEntityRowMapper(JdbcPersistentProperty property) { diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index 73b28b15..808283c8 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -53,11 +53,11 @@ public class EntityRowMapper implements RowMapper { private final DataAccessStrategy accessStrategy; private final JdbcPersistentProperty idProperty; - public EntityRowMapper(JdbcPersistentEntity entity, ConversionService conversions, JdbcMappingContext context, - DataAccessStrategy accessStrategy) { + public EntityRowMapper(JdbcPersistentEntity entity, JdbcMappingContext context, + DataAccessStrategy accessStrategy) { this.entity = entity; - this.conversions = conversions; + this.conversions = context.getConversions(); this.context = context; this.accessStrategy = accessStrategy; diff --git a/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java b/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java new file mode 100644 index 00000000..cbf0110b --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * 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() { + + public RowMapper rowMapperFor(Class type) { + return null; + } + }; + + RowMapper rowMapperFor(Class type); +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java b/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java new file mode 100644 index 00000000..a3544a82 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * 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; + } + + @SuppressWarnings("unchecked") + public RowMapper rowMapperFor(Class type) { + + 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/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index ccc6039f..8df58d89 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -21,6 +21,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.EntityRowMapper; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; @@ -40,13 +41,15 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { private final JdbcMappingContext context; private final DataAccessStrategy accessStrategy; + private final RowMapperMap rowMapperMap; private final ConversionService conversionService; JdbcQueryLookupStrategy(EvaluationContextProvider evaluationContextProvider, JdbcMappingContext context, - DataAccessStrategy accessStrategy) { + DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap) { this.context = context; this.accessStrategy = accessStrategy; + this.rowMapperMap = rowMapperMap; this.conversionService = context.getConversions(); } @@ -55,22 +58,32 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { ProjectionFactory projectionFactory, NamedQueries namedQueries) { JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory); - Class returnedObjectType = queryMethod.getReturnedObjectType(); - RowMapper rowMapper = queryMethod.isModifyingQuery() ? null : createRowMapper(returnedObjectType); + RowMapper rowMapper = queryMethod.isModifyingQuery() ? null : createRowMapper(queryMethod); return new JdbcRepositoryQuery(queryMethod, context, rowMapper); } - private RowMapper createRowMapper(Class returnedObjectType) { + private RowMapper createRowMapper(JdbcQueryMethod queryMethod) { + + Class returnedObjectType = queryMethod.getReturnedObjectType(); return context.getSimpleTypeHolder().isSimpleType(returnedObjectType) ? SingleColumnRowMapper.newInstance(returnedObjectType, conversionService) - : new EntityRowMapper<>( // - context.getRequiredPersistentEntity(returnedObjectType), // - conversionService, // + : determineDefaultRowMapper(queryMethod); + } + + private RowMapper determineDefaultRowMapper(JdbcQueryMethod queryMethod) { + + Class domainType = queryMethod.getReturnedObjectType(); + + RowMapper typeMappedRowMapper = rowMapperMap.rowMapperFor(domainType); + + return typeMappedRowMapper == null // + ? new EntityRowMapper<>( // + context.getRequiredPersistentEntity(domainType), // context, // - accessStrategy // - ); + accessStrategy) // + : typeMappedRowMapper; } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index 49281dc7..288bc452 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -22,6 +22,7 @@ import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.core.JdbcEntityTemplate; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation; +import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.jdbc.repository.SimpleJdbcRepository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; @@ -42,6 +43,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { private final JdbcMappingContext context; private final ApplicationEventPublisher publisher; private final DataAccessStrategy accessStrategy; + private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; public JdbcRepositoryFactory(ApplicationEventPublisher publisher, JdbcMappingContext context, DataAccessStrategy dataAccessStrategy) { @@ -84,6 +86,10 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport { throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key)); } - return Optional.of(new JdbcQueryLookupStrategy(evaluationContextProvider, context, accessStrategy)); + return Optional.of(new JdbcQueryLookupStrategy(evaluationContextProvider, context, accessStrategy, rowMapperMap)); + } + + public void setRowMapperMap(RowMapperMap rowMapperMap) { + this.rowMapperMap = rowMapperMap; } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 53312252..4f53754d 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -22,6 +22,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.data.jdbc.core.DataAccessStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; @@ -41,6 +42,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend private ApplicationEventPublisher publisher; private JdbcMappingContext mappingContext; private DataAccessStrategy dataAccessStrategy; + private RowMapperMap rowMapperMap = RowMapperMap.EMPTY; JdbcRepositoryFactoryBean(Class repositoryInterface) { super(repositoryInterface); @@ -60,7 +62,15 @@ public class JdbcRepositoryFactoryBean, S, ID extend */ @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - return new JdbcRepositoryFactory(publisher, mappingContext, dataAccessStrategy); + + JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(publisher, mappingContext, + dataAccessStrategy); + + if (rowMapperMap != null) { + jdbcRepositoryFactory.setRowMapperMap(rowMapperMap); + } + + return jdbcRepositoryFactory; } @Autowired @@ -75,6 +85,11 @@ public class JdbcRepositoryFactoryBean, S, ID extend this.dataAccessStrategy = dataAccessStrategy; } + @Autowired(required = false) + public void setRowMapperMap(RowMapperMap rowMapperMap) { + this.rowMapperMap = rowMapperMap; + } + @Override public void afterPropertiesSet() { diff --git a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java index 36798f02..b9471ce4 100644 --- a/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/EntityRowMapperUnitTests.java @@ -28,7 +28,6 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.mapping.model.NamingStrategy; -import org.springframework.data.repository.query.Param; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.util.Assert; @@ -204,7 +203,7 @@ public class EntityRowMapperUnitTests { Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter); return new EntityRowMapper<>((JdbcPersistentEntity) context.getRequiredPersistentEntity(type), - conversionService, context, accessStrategy); + context, accessStrategy); } private static ResultSet mockResultSet(List columns, Object... values) { diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java new file mode 100644 index 00000000..6861d9a4 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMapUnitTests.java @@ -0,0 +1,95 @@ +/* + * 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 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.jdbc.core.RowMapper; + +/** + * Unit tests for {@link ConfigurableRowMapperMap}. + * + * @author Jens Schauder + */ +public class ConfigurableRowMapperMapUnitTests { + + @Test + public void freshInstanceReturnsNull() { + + RowMapperMap map = new ConfigurableRowMapperMap(); + + assertThat(map.rowMapperFor(Object.class)).isNull(); + } + + @Test + public void returnsConfiguredInstanceForClass() { + + RowMapper rowMapper = mock(RowMapper.class); + + RowMapperMap map = new ConfigurableRowMapperMap().register(Object.class, rowMapper); + + assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + } + + @Test + public void returnsNullForClassNotConfigured() { + + RowMapper rowMapper = mock(RowMapper.class); + + RowMapperMap map = new ConfigurableRowMapperMap().register(Number.class, rowMapper); + + assertThat(map.rowMapperFor(Integer.class)).isNull(); + assertThat(map.rowMapperFor(String.class)).isNull(); + } + + @Test + public void returnsInstanceRegisteredForSubClass() { + + RowMapper rowMapper = mock(RowMapper.class); + + RowMapperMap map = new ConfigurableRowMapperMap().register(String.class, rowMapper); + + assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + } + + @Test + public void prefersExactTypeMatchClass() { + + 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)); + + assertThat(map.rowMapperFor(Integer.class)).isEqualTo(rowMapper); + } + + @Test + public void prefersLatestRegistrationForSuperTypeMatch() { + + RowMapper rowMapper = mock(RowMapper.class); + + RowMapperMap map = new ConfigurableRowMapperMap() // + .register(Integer.class, mock(RowMapper.class)) // + .register(Number.class, rowMapper); + + assertThat(map.rowMapperFor(Object.class)).isEqualTo(rowMapper); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java index 91529b8d..6cb6d981 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -15,20 +15,28 @@ */ package org.springframework.data.jdbc.repository.config; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import lombok.Data; +import java.lang.reflect.Field; + +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.RowMapperMap; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.RowMapper; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ReflectionUtils; /** * Tests the {@link EnableJdbcRepositories} annotation. @@ -40,8 +48,18 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(classes = TestConfiguration.class) public class EnableJdbcRepositoriesIntegrationTests { + static final Field ROW_MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "rowMapperMap"); + public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class); + public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class); + + @Autowired JdbcRepositoryFactoryBean factoryBean; @Autowired DummyRepository repository; + @BeforeClass + public static void setup() { + ROW_MAPPER_MAP.setAccessible(true); + } + @Test // DATAJDBC-100 public void repositoryGetsPickedUp() { @@ -52,6 +70,15 @@ public class EnableJdbcRepositoriesIntegrationTests { assertNotNull(all); } + @Test // DATAJDBC-166 + public void customRowMapperConfigurationGetsPickedUp() { + + RowMapperMap mapping = (RowMapperMap) ReflectionUtils.getField(ROW_MAPPER_MAP, factoryBean); + + assertThat(mapping.rowMapperFor(String.class)).isEqualTo(STRING_ROW_MAPPER); + assertThat(mapping.rowMapperFor(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER); + } + interface DummyRepository extends CrudRepository { } @@ -69,5 +96,13 @@ public class EnableJdbcRepositoriesIntegrationTests { Class testClass() { return EnableJdbcRepositoriesIntegrationTests.class; } + + @Bean + RowMapperMap rowMappers() { + return new ConfigurableRowMapperMap() // + .register(DummyEntity.class, DUMMY_ENTITY_ROW_MAPPER) // + .register(String.class, STRING_ROW_MAPPER); + } + } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java new file mode 100644 index 00000000..68a953be --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java @@ -0,0 +1,97 @@ +/* + * 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.support; + +import static org.mockito.Mockito.*; + +import java.lang.reflect.Method; +import java.text.NumberFormat; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.jdbc.core.DataAccessStrategy; +import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.repository.RowMapperMap; +import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap; +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; + +/** + * Unit tests for {@link JdbcQueryLookupStrategy}. + * + * @author Jens Schauder + */ +public class JdbcQueryLookupStrategyUnitTests { + + EvaluationContextProvider evaluationContextProvider = mock(EvaluationContextProvider.class); + JdbcMappingContext mappingContext = mock(JdbcMappingContext.class, RETURNS_DEEP_STUBS); + DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class); + ProjectionFactory projectionFactory = mock(ProjectionFactory.class); + RepositoryMetadata metadata; + NamedQueries namedQueries = mock(NamedQueries.class); + + @Before + public void setup() { + + metadata = mock(RepositoryMetadata.class); + when(metadata.getReturnedDomainClass(any(Method.class))).thenReturn((Class) NumberFormat.class); + + } + + private Method getMethod(String name) { + + try { + return this.getClass().getDeclaredMethod(name); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Test // DATAJDBC-166 + public void typeBasedRowMapperGetsUsedForQuery() { + + RowMapper numberFormatMapper = mock(RowMapper.class); + RowMapperMap rowMapperMap = new ConfigurableRowMapperMap().register(NumberFormat.class, numberFormatMapper); + + RepositoryQuery repositoryQuery = getRepositoryQuery("returningNumberFormat", rowMapperMap); + + repositoryQuery.execute(new Object[] {}); + + verify(mappingContext.getTemplate()).queryForObject(anyString(), any(SqlParameterSource.class), + eq(numberFormatMapper)); + } + + private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) { + + JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(evaluationContextProvider, mappingContext, + accessStrategy, rowMapperMap); + + return queryLookupStrategy.resolveQuery(getMethod(name), metadata, projectionFactory, namedQueries); + } + + // NumberFormat is just used as an arbitrary non simple type. + @Query("some SQL") + private NumberFormat returningNumberFormat() { + return null; + } + +}