Skip to content

Commit 4238d73

Browse files
committed
Allow injection of "row mapper," and "result set extractor" beans.
Until now, only classes without injection had support. This commit will add two new params at the `@Query` annotation. In these new params will be possible to add Spring Beans allowing the user to use Spring Beans.
1 parent 109adbc commit 4238d73

File tree

12 files changed

+194
-35
lines changed

12 files changed

+194
-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+
Class<? extends RowMapper> 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+
Class<? extends ResultSetExtractor> 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+
Class<? extends RowMapper> rowMapperBean() default RowMapper.class;
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+
Class<? extends ResultSetExtractor> resultSetExtractorBean() default ResultSetExtractor.class;
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+
Class<? extends ResultSetExtractor> resultSetExtractorBean = queryMethod.getResultSetExtractorBean();
144+
145+
if (!isUnconfigured(resultSetExtractorBean, ResultSetExtractor.class)) {
146+
return 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+
Class<? extends RowMapper> rowMapperBean = queryMethod.getRowMapperBean();
169+
170+
if (!isUnconfigured(rowMapperBean, RowMapper.class)) {
171+
return 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: 28 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,12 @@
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;
38+
import org.springframework.data.jdbc.testing.SingleBaseMappingTestConfiguration.CarResultSetExtractorBean;
39+
import org.springframework.data.jdbc.testing.SingleBaseMappingTestConfiguration.CustomRowMapperBean;
4040
import org.springframework.data.jdbc.testing.TestConfiguration;
4141
import org.springframework.data.repository.CrudRepository;
4242
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -55,7 +55,7 @@
5555
@Transactional
5656
public class StringBasedJdbcQueryMappingConfigurationIntegrationTests {
5757

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

6060
@Configuration
6161
@Import(TestConfiguration.class)
@@ -89,18 +89,22 @@ public void customFindAllCarsUsesConfiguredResultSetExtractor() {
8989
assertThat(cars).allMatch(car -> CAR_MODEL.equals(car.getModel()));
9090
}
9191

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

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

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

102-
@Id private Long id;
103-
private String model;
106+
assertThat(cars).hasSize(1);
107+
assertThat(cars).allMatch(car -> VALUE_PROCESSED_BY_SERVICE.equals(car.getModel()));
104108
}
105109

106110
static class CarResultSetExtractor implements ResultSetExtractor<List<Car>> {
@@ -109,6 +113,16 @@ static class CarResultSetExtractor implements ResultSetExtractor<List<Car>> {
109113
public List<Car> extractData(ResultSet rs) throws SQLException, DataAccessException {
110114
return Arrays.asList(new Car(1L, CAR_MODEL));
111115
}
116+
}
117+
118+
private interface CarRepository extends CrudRepository<Car, Long> {
119+
@Query(value = "select * from car", resultSetExtractorClass = CarResultSetExtractor.class)
120+
List<Car> customFindAll();
121+
122+
@Query(value = "select * from car", resultSetExtractorBean = CarResultSetExtractorBean.class)
123+
List<Car> findByNameWithResultSetExtractor();
112124

125+
@Query(value = "select model from car", rowMapperBean = CustomRowMapperBean.class)
126+
List<String> findByNameWithRowMapperBean();
113127
}
114128
}

0 commit comments

Comments
 (0)