Skip to content

Commit c356a8c

Browse files
uaihebertschauder
authored andcommitted
DATAJDBC-430 Allow specification of rowMapperRef or resultSetExtractorRef as bean references.
This allows lookup of such beans by name, thereby allowing for full dependency injection support. Original pull request: #249.
1 parent 68c8a8a commit c356a8c

File tree

12 files changed

+192
-35
lines changed

12 files changed

+192
-35
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ private String getQueryName() {
163163
return StringUtils.hasText(annotatedName) ? annotatedName : getNamedQueryName();
164164
}
165165

166-
/*
166+
/**
167167
* Returns the class to be used as {@link org.springframework.jdbc.core.RowMapper}
168168
*
169169
* @return May be {@code null}.
@@ -173,6 +173,17 @@ Class<? extends RowMapper> getRowMapperClass() {
173173
return getMergedAnnotationAttribute("rowMapperClass");
174174
}
175175

176+
177+
/**
178+
* Returns the bean to be used as {@link org.springframework.jdbc.core.RowMapper}
179+
*
180+
* @return May be {@code null}.
181+
*/
182+
@Nullable
183+
String getRowMapperBean() {
184+
return getMergedAnnotationAttribute("rowMapperBean");
185+
}
186+
176187
/**
177188
* Returns the class to be used as {@link org.springframework.jdbc.core.ResultSetExtractor}
178189
*
@@ -183,6 +194,16 @@ Class<? extends ResultSetExtractor> getResultSetExtractorClass() {
183194
return getMergedAnnotationAttribute("resultSetExtractorClass");
184195
}
185196

197+
/**
198+
* Returns the bean to be used as {@link org.springframework.jdbc.core.ResultSetExtractor}
199+
*
200+
* @return May be {@code null}.
201+
*/
202+
@Nullable
203+
String getResultSetExtractorBean() {
204+
return getMergedAnnotationAttribute("resultSetExtractorBean");
205+
}
206+
186207
/**
187208
* Returns whether the query method is a modifying one.
188209
*

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/Query.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,21 @@
5656
*/
5757
Class<? extends RowMapper> rowMapperClass() default RowMapper.class;
5858

59+
/**
60+
* Optional bean of type {@link RowMapper} to use to convert the result of the query to domain class instances. Cannot be used
61+
* along with {@link #resultSetExtractorClass()} only one of the two can be set.
62+
*/
63+
String rowMapperBean() default "RowMapper";
64+
5965
/**
6066
* Optional {@link ResultSetExtractor} to use to convert the result of the query to domain class instances. Cannot be
6167
* used along with {@link #rowMapperClass()} only one of the two can be set.
6268
*/
6369
Class<? extends ResultSetExtractor> resultSetExtractorClass() default ResultSetExtractor.class;
70+
71+
/**
72+
* Optional bean of type {@link ResultSetExtractor} to use to convert the result of the query to domain class instances. Cannot be
73+
* used along with {@link #rowMapperClass()} only one of the two can be set.
74+
*/
75+
String resultSetExtractorBean() default "ResultSetExtractor";
6476
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.sql.JDBCType;
2020

2121
import org.springframework.beans.BeanUtils;
22+
import org.springframework.beans.factory.BeanFactory;
2223
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
2324
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2425
import org.springframework.data.jdbc.core.convert.JdbcValue;
@@ -51,6 +52,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
5152
private final JdbcQueryMethod queryMethod;
5253
private final JdbcQueryExecution<?> executor;
5354
private final JdbcConverter converter;
55+
private BeanFactory beanfactory;
5456

5557
/**
5658
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
@@ -61,12 +63,13 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
6163
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
6264
*/
6365
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
64-
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter) {
66+
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter, BeanFactory beanfactory) {
6567

6668
super(queryMethod, operations, defaultRowMapper);
6769

6870
this.queryMethod = queryMethod;
6971
this.converter = converter;
72+
this.beanfactory = beanfactory;
7073

7174
RowMapper<Object> rowMapper = determineRowMapper(defaultRowMapper);
7275
executor = getQueryExecution( //
@@ -137,6 +140,11 @@ private String determineQuery() {
137140
@Nullable
138141
@SuppressWarnings({ "rawtypes", "unchecked" })
139142
ResultSetExtractor<Object> determineResultSetExtractor(@Nullable RowMapper<Object> rowMapper) {
143+
String resultSetExtractorBean = queryMethod.getResultSetExtractorBean();
144+
145+
if (resultSetExtractorBean != null && !"ResultSetExtractor".equals(resultSetExtractorBean)) {
146+
return (ResultSetExtractor<Object>) beanfactory.getBean(resultSetExtractorBean);
147+
}
140148

141149
Class<? extends ResultSetExtractor> resultSetExtractorClass = queryMethod.getResultSetExtractorClass();
142150

@@ -157,6 +165,12 @@ ResultSetExtractor<Object> determineResultSetExtractor(@Nullable RowMapper<Objec
157165
@SuppressWarnings("unchecked")
158166
RowMapper<Object> determineRowMapper(@Nullable RowMapper<?> defaultMapper) {
159167

168+
String rowMapperBean = queryMethod.getRowMapperBean();
169+
170+
if (rowMapperBean != null && !"RowMapper".equals(rowMapperBean)) {
171+
return (RowMapper<Object>) beanfactory.getBean(rowMapperBean);
172+
}
173+
160174
Class<?> rowMapperClass = queryMethod.getRowMapperClass();
161175

162176
if (isUnconfigured(rowMapperClass, RowMapper.class)) {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.sql.ResultSet;
2020
import java.sql.SQLException;
2121

22+
import org.springframework.beans.factory.BeanFactory;
2223
import org.springframework.context.ApplicationEventPublisher;
2324
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
2425
import org.springframework.data.jdbc.core.convert.JdbcConverter;
@@ -63,10 +64,12 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
6364
private final Dialect dialect;
6465
private final QueryMappingConfiguration queryMappingConfiguration;
6566
private final NamedParameterJdbcOperations operations;
67+
private BeanFactory beanfactory;
6668

6769
public JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
6870
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
69-
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations) {
71+
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
72+
BeanFactory beanfactory) {
7073

7174
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
7275
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
@@ -82,6 +85,7 @@ public JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable En
8285
this.dialect = dialect;
8386
this.queryMappingConfiguration = queryMappingConfiguration;
8487
this.operations = operations;
88+
this.beanfactory = beanfactory;
8589
}
8690

8791
/*
@@ -99,7 +103,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
99103
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
100104

101105
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
102-
return new StringBasedJdbcQuery(queryMethod, operations, mapper, converter);
106+
return new StringBasedJdbcQuery(queryMethod, operations, mapper, converter, beanfactory);
103107
} else {
104108
return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, createMapper(queryMethod));
105109
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Optional;
1919

20+
import org.springframework.beans.factory.BeanFactory;
2021
import org.springframework.context.ApplicationEventPublisher;
2122
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
2223
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
@@ -53,6 +54,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
5354
private final DataAccessStrategy accessStrategy;
5455
private final NamedParameterJdbcOperations operations;
5556
private final Dialect dialect;
57+
private BeanFactory beanfactory;
5658

5759
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
5860
private EntityCallbacks entityCallbacks;
@@ -70,7 +72,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
7072
*/
7173
public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
7274
JdbcConverter converter, Dialect dialect, ApplicationEventPublisher publisher,
73-
NamedParameterJdbcOperations operations) {
75+
NamedParameterJdbcOperations operations, BeanFactory beanfactory) {
7476

7577
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!");
7678
Assert.notNull(context, "RelationalMappingContext must not be null!");
@@ -84,6 +86,7 @@ public JdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMa
8486
this.dialect = dialect;
8587
this.accessStrategy = dataAccessStrategy;
8688
this.operations = operations;
89+
this.beanfactory = beanfactory;
8790
}
8891

8992
/**
@@ -142,7 +145,7 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLo
142145
QueryMethodEvaluationContextProvider evaluationContextProvider) {
143146

144147
return Optional.of(new JdbcQueryLookupStrategy(publisher, entityCallbacks, context, converter, dialect,
145-
queryMappingConfiguration, operations));
148+
queryMappingConfiguration, operations, beanfactory));
146149
}
147150

148151
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
8686
protected RepositoryFactorySupport doCreateRepositoryFactory() {
8787

8888
JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(dataAccessStrategy, mappingContext,
89-
converter, dialect, publisher, operations);
89+
converter, dialect, publisher, operations, beanFactory);
9090
jdbcRepositoryFactory.setQueryMappingConfiguration(queryMappingConfiguration);
9191
jdbcRepositoryFactory.setEntityCallbacks(entityCallbacks);
9292

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.junit.Before;
3333
import org.junit.Test;
3434
import org.mockito.stubbing.Answer;
35+
import org.springframework.beans.factory.BeanFactory;
3536
import org.springframework.context.ApplicationEventPublisher;
3637
import org.springframework.data.annotation.Id;
3738
import org.springframework.data.domain.PageRequest;
@@ -82,6 +83,7 @@ public class SimpleJdbcRepositoryEventsUnitTests {
8283

8384
DummyEntityRepository repository;
8485
DefaultDataAccessStrategy dataAccessStrategy;
86+
BeanFactory beanFactory = mock(BeanFactory.class);
8587

8688
@Before
8789
public void before() {
@@ -99,7 +101,7 @@ public void before() {
99101
doReturn(true).when(dataAccessStrategy).update(any(), any());
100102

101103
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter,
102-
H2Dialect.INSTANCE, publisher, operations);
104+
H2Dialect.INSTANCE, publisher, operations, beanFactory);
103105

104106
this.repository = factory.getRepository(DummyEntityRepository.class);
105107
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/StringBasedJdbcQueryMappingConfigurationIntegrationTests.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
*/
1616
package org.springframework.data.jdbc.repository;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
20-
import lombok.AllArgsConstructor;
21-
import lombok.Data;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.springframework.data.jdbc.testing.SingleBaseMappingTestConfiguration.VALUE_PROCESSED_BY_SERVICE;
2220

2321
import java.sql.ResultSet;
2422
import java.sql.SQLException;
@@ -33,10 +31,10 @@
3331
import org.springframework.context.annotation.Configuration;
3432
import org.springframework.context.annotation.Import;
3533
import org.springframework.dao.DataAccessException;
36-
import org.springframework.data.annotation.Id;
3734
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
3835
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
3936
import org.springframework.data.jdbc.repository.query.Query;
37+
import org.springframework.data.jdbc.testing.SingleBaseMappingTestConfiguration.Car;
4038
import org.springframework.data.jdbc.testing.TestConfiguration;
4139
import org.springframework.data.repository.CrudRepository;
4240
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -55,7 +53,7 @@
5553
@Transactional
5654
public class StringBasedJdbcQueryMappingConfigurationIntegrationTests {
5755

58-
private static String CAR_MODEL = "ResultSetExtractor Car";
56+
private final static String CAR_MODEL = "ResultSetExtractor Car";
5957

6058
@Configuration
6159
@Import(TestConfiguration.class)
@@ -89,18 +87,22 @@ public void customFindAllCarsUsesConfiguredResultSetExtractor() {
8987
assertThat(cars).allMatch(car -> CAR_MODEL.equals(car.getModel()));
9088
}
9189

92-
interface CarRepository extends CrudRepository<Car, Long> {
90+
@Test // DATAJDBC-430
91+
public void customFindWithRowMapperSupportingInjection() {
92+
carRepository.save(new Car(null, "Some model"));
93+
List<String> names = carRepository.findByNameWithRowMapperBean();
9394

94-
@Query(value = "select * from car", resultSetExtractorClass = CarResultSetExtractor.class)
95-
List<Car> customFindAll();
95+
assertThat(names).hasSize(1);
96+
assertThat(names).allMatch(name -> VALUE_PROCESSED_BY_SERVICE.equals(name));
9697
}
9798

98-
@Data
99-
@AllArgsConstructor
100-
static class Car {
99+
@Test // DATAJDBC-430
100+
public void customFindWithResultSetExtractorSupportingInjection() {
101+
carRepository.save(new Car(null, "Some model"));
102+
Iterable<Car> cars = carRepository.findByNameWithResultSetExtractor();
101103

102-
@Id private Long id;
103-
private String model;
104+
assertThat(cars).hasSize(1);
105+
assertThat(cars).allMatch(car -> VALUE_PROCESSED_BY_SERVICE.equals(car.getModel()));
104106
}
105107

106108
static class CarResultSetExtractor implements ResultSetExtractor<List<Car>> {
@@ -109,6 +111,16 @@ static class CarResultSetExtractor implements ResultSetExtractor<List<Car>> {
109111
public List<Car> extractData(ResultSet rs) throws SQLException, DataAccessException {
110112
return Arrays.asList(new Car(1L, CAR_MODEL));
111113
}
114+
}
115+
116+
private interface CarRepository extends CrudRepository<Car, Long> {
117+
@Query(value = "select * from car", resultSetExtractorClass = CarResultSetExtractor.class)
118+
List<Car> customFindAll();
119+
120+
@Query(value = "select * from car", resultSetExtractorBean = "CarResultSetExtractorBean")
121+
List<Car> findByNameWithResultSetExtractor();
112122

123+
@Query(value = "select model from car", rowMapperBean = "CustomRowMapperBean")
124+
List<String> findByNameWithRowMapperBean();
113125
}
114126
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.junit.Before;
2525
import org.junit.Test;
2626

27+
import org.springframework.beans.factory.BeanFactory;
2728
import org.springframework.dao.DataAccessException;
2829
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
2930
import org.springframework.data.jdbc.core.convert.JdbcConverter;
@@ -53,6 +54,7 @@ public class StringBasedJdbcQueryUnitTests {
5354
NamedParameterJdbcOperations operations;
5455
RelationalMappingContext context;
5556
JdbcConverter converter;
57+
BeanFactory beanFactory;
5658

5759
@Before
5860
public void setup() throws NoSuchMethodException {
@@ -67,6 +69,7 @@ public void setup() throws NoSuchMethodException {
6769
this.operations = mock(NamedParameterJdbcOperations.class);
6870
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
6971
this.converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
72+
this.beanFactory = mock(BeanFactory.class);
7073
}
7174

7275
@Test // DATAJDBC-165
@@ -75,7 +78,7 @@ public void emptyQueryThrowsException() {
7578
doReturn(null).when(queryMethod).getDeclaredQuery();
7679

7780
Assertions.assertThatExceptionOfType(IllegalStateException.class) //
78-
.isThrownBy(() -> new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter)
81+
.isThrownBy(() -> new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory)
7982
.execute(new Object[] {}));
8083
}
8184

@@ -84,7 +87,7 @@ public void defaultRowMapperIsUsedByDefault() {
8487

8588
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
8689
doReturn(RowMapper.class).when(queryMethod).getRowMapperClass();
87-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter);
90+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory);
8891

8992
assertThat(query.determineRowMapper(defaultRowMapper)).isEqualTo(defaultRowMapper);
9093
}
@@ -93,7 +96,7 @@ public void defaultRowMapperIsUsedByDefault() {
9396
public void defaultRowMapperIsUsedForNull() {
9497

9598
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
96-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter);
99+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory);
97100

98101
assertThat(query.determineRowMapper(defaultRowMapper)).isEqualTo(defaultRowMapper);
99102
}
@@ -104,7 +107,7 @@ public void customRowMapperIsUsedWhenSpecified() {
104107
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
105108
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();
106109

107-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter);
110+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory);
108111

109112
assertThat(query.determineRowMapper(defaultRowMapper)).isInstanceOf(CustomRowMapper.class);
110113
}
@@ -115,9 +118,9 @@ public void customResultSetExtractorIsUsedWhenSpecified() {
115118
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
116119
doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass();
117120

118-
new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter).execute(new Object[] {});
121+
new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory).execute(new Object[] {});
119122

120-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter);
123+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory);
121124

122125
ResultSetExtractor<Object> resultSetExtractor = query.determineResultSetExtractor(defaultRowMapper);
123126

@@ -134,7 +137,7 @@ public void customResultSetExtractorAndRowMapperGetCombined() {
134137
doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass();
135138
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();
136139

137-
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter);
140+
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, defaultRowMapper, converter, beanFactory);
138141

139142
ResultSetExtractor<Object> resultSetExtractor = query
140143
.determineResultSetExtractor(query.determineRowMapper(defaultRowMapper));

0 commit comments

Comments
 (0)