Skip to content

Commit 55886fd

Browse files
committed
Refine RowMapperFactory design.
Retrieve a RowMapper for general usage from JdbcAggregateOperations instead of passing several independent dependencies across various components.
1 parent 5cbe590 commit 55886fd

20 files changed

+262
-340
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
2828
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2929
import org.springframework.data.relational.core.query.Query;
30+
import org.springframework.jdbc.core.RowMapper;
3031
import org.springframework.lang.Nullable;
3132

3233
/**
@@ -342,4 +343,16 @@ public interface JdbcAggregateOperations {
342343
*/
343344
DataAccessStrategy getDataAccessStrategy();
344345

346+
/**
347+
* Return a {@link RowMapper} that can map rows of a {@link java.sql.ResultSet} to instances of the specified
348+
* {@link Class type}. The row mapper supports entity callbacks and lifecycle events if enabled and configured on the
349+
* underlying template instance.
350+
*
351+
* @param type type of the entity to map.
352+
* @return a row mapper for the given type.
353+
* @param <T>
354+
* @since 4.0
355+
*/
356+
<T> RowMapper<? extends T> getRowMapper(Class<T> type);
357+
345358
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.jdbc.core;
1717

18+
import java.sql.ResultSet;
19+
import java.sql.SQLException;
1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.HashMap;
@@ -36,7 +38,9 @@
3638
import org.springframework.data.domain.Pageable;
3739
import org.springframework.data.domain.Sort;
3840
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
41+
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
3942
import org.springframework.data.jdbc.core.convert.JdbcConverter;
43+
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
4044
import org.springframework.data.mapping.IdentifierAccessor;
4145
import org.springframework.data.mapping.callback.EntityCallbacks;
4246
import org.springframework.data.relational.core.EntityLifecycleEventDelegate;
@@ -55,6 +59,7 @@
5559
import org.springframework.data.relational.core.mapping.event.*;
5660
import org.springframework.data.relational.core.query.Query;
5761
import org.springframework.data.support.PageableExecutionUtils;
62+
import org.springframework.jdbc.core.RowMapper;
5863
import org.springframework.lang.Nullable;
5964
import org.springframework.util.Assert;
6065
import org.springframework.util.ClassUtils;
@@ -83,6 +88,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations, Applicati
8388
private final JdbcConverter converter;
8489

8590
private @Nullable EntityCallbacks entityCallbacks;
91+
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
8692

8793
/**
8894
* Creates a new {@link JdbcAggregateTemplate} given {@link RelationalMappingContext} and {@link DataAccessStrategy}.
@@ -186,6 +192,23 @@ public void setEntityLifecycleEventsEnabled(boolean enabled) {
186192
this.eventDelegate.setEventsEnabled(enabled);
187193
}
188194

195+
/**
196+
* Return a {@link RowMapper} to map results for {@link Class type}.
197+
*
198+
* @param type must not be {@literal null}.
199+
* @return a row mapper to map results for {@link Class type}.
200+
* @param <T> return type.
201+
* @since 4.0
202+
*/
203+
@Override
204+
@SuppressWarnings("unchecked")
205+
public <T> RowMapper<? extends T> getRowMapper(Class<T> type) {
206+
207+
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(type);
208+
209+
return new LifecycleEntityRowMapper<T>((RelationalPersistentEntity<T>) entity);
210+
}
211+
189212
@Override
190213
public <T> T save(T instance) {
191214

@@ -741,4 +764,29 @@ private interface AggregateChangeCreator<T> {
741764
RootAggregateChange<T> createAggregateChange(T instance);
742765
}
743766

767+
class LifecycleEntityRowMapper<T> extends EntityRowMapper<T> {
768+
769+
public LifecycleEntityRowMapper(RelationalPersistentEntity<T> entity) {
770+
super(entity, converter);
771+
}
772+
773+
@Override
774+
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
775+
776+
T object = super.mapRow(resultSet, rowNumber);
777+
778+
if (object != null) {
779+
780+
eventDelegate.publishEvent(() -> new AfterConvertEvent<>(object));
781+
782+
if (entityCallbacks != null) {
783+
return entityCallbacks.callback(AfterConvertCallback.class, object);
784+
}
785+
}
786+
787+
return object;
788+
}
789+
790+
}
791+
744792
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ public class EntityRowMapper<T> implements RowMapper<T> {
4141
private final JdbcConverter converter;
4242
private final Identifier identifier;
4343

44-
private EntityRowMapper(TypeInformation<T> typeInformation, JdbcConverter converter, Identifier identifier) {
45-
46-
this.typeInformation = typeInformation;
47-
this.converter = converter;
48-
this.identifier = identifier;
49-
}
50-
5144
@SuppressWarnings("unchecked")
5245
public EntityRowMapper(AggregatePath path, JdbcConverter converter, Identifier identifier) {
5346
this(((RelationalPersistentEntity<T>) path.getRequiredLeafEntity()).getTypeInformation(), converter, identifier);
@@ -57,6 +50,13 @@ public EntityRowMapper(RelationalPersistentEntity<T> entity, JdbcConverter conve
5750
this(entity.getTypeInformation(), converter, Identifier.empty());
5851
}
5952

53+
private EntityRowMapper(TypeInformation<T> typeInformation, JdbcConverter converter, Identifier identifier) {
54+
55+
this.typeInformation = typeInformation;
56+
this.converter = converter;
57+
this.identifier = identifier;
58+
}
59+
6060
@Override
6161
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
6262

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMappingConfiguration.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,22 @@
2828
*/
2929
public interface QueryMappingConfiguration {
3030

31-
@Nullable
32-
default <T> RowMapper<? extends T> getRowMapper(Class<T> type) {
33-
return null;
34-
}
31+
/**
32+
* Returns the {@link RowMapper} to be used for the given type or {@literal null} if no specific mapper is configured.
33+
*
34+
* @param type
35+
* @return
36+
* @param <T>
37+
*/
38+
<T> @Nullable RowMapper<? extends T> getRowMapper(Class<T> type);
3539

3640
/**
3741
* An immutable empty instance that will return {@literal null} for all arguments.
3842
*/
3943
QueryMappingConfiguration EMPTY = new QueryMappingConfiguration() {
4044

4145
@Override
42-
public <T> RowMapper<? extends T> getRowMapper(Class<T> type) {
46+
public <T> @Nullable RowMapper<? extends T> getRowMapper(Class<T> type) {
4347
return null;
4448
}
4549

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/AotRepositoryFragmentSupport.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class AotRepositoryFragmentSupport {
8080

8181
private final RowMapperFactory rowMapperFactory;
8282

83-
private final JdbcAggregateOperations aggregateOperations;
83+
private final JdbcAggregateOperations operations;
8484

8585
private final StatementFactory statementFactory;
8686

@@ -92,20 +92,20 @@ public class AotRepositoryFragmentSupport {
9292

9393
private final Lazy<ConcurrentLruCache<Method, ValueEvaluationContextProvider>> contextProviders;
9494

95-
protected AotRepositoryFragmentSupport(RowMapperFactory rowMapperFactory, JdbcAggregateOperations aggregateOperations,
95+
protected AotRepositoryFragmentSupport(RowMapperFactory rowMapperFactory, JdbcAggregateOperations operations,
9696
RepositoryFactoryBeanSupport.FragmentCreationContext context) {
97-
this(rowMapperFactory, aggregateOperations, context.getRepositoryMetadata(), context.getValueExpressionDelegate(),
97+
this(rowMapperFactory, operations, context.getRepositoryMetadata(), context.getValueExpressionDelegate(),
9898
context.getProjectionFactory());
9999
}
100100

101-
protected AotRepositoryFragmentSupport(RowMapperFactory rowMapperFactory, JdbcAggregateOperations aggregateOperations,
101+
protected AotRepositoryFragmentSupport(RowMapperFactory rowMapperFactory, JdbcAggregateOperations operations,
102102
RepositoryMetadata repositoryMetadata, ValueExpressionDelegate valueExpressions,
103103
ProjectionFactory projectionFactory) {
104104

105105
this.rowMapperFactory = rowMapperFactory;
106-
this.aggregateOperations = aggregateOperations;
107-
this.statementFactory = new StatementFactory(aggregateOperations.getConverter(),
108-
aggregateOperations.getDataAccessStrategy().getDialect());
106+
this.operations = operations;
107+
this.statementFactory = new StatementFactory(operations.getConverter(),
108+
operations.getDataAccessStrategy().getDialect());
109109
this.projectionFactory = projectionFactory;
110110
this.expressions = Lazy.of(() -> new ConcurrentLruCache<>(32, valueExpressions::parse));
111111
this.parameters = Lazy
@@ -123,11 +123,15 @@ protected StatementFactory getStatementFactory() {
123123
}
124124

125125
protected Dialect getDialect() {
126-
return aggregateOperations.getDataAccessStrategy().getDialect();
126+
return operations.getDataAccessStrategy().getDialect();
127+
}
128+
129+
protected JdbcAggregateOperations getOperations() {
130+
return operations;
127131
}
128132

129133
protected NamedParameterJdbcOperations getJdbcOperations() {
130-
return this.aggregateOperations.getDataAccessStrategy().getJdbcOperations();
134+
return this.operations.getDataAccessStrategy().getJdbcOperations();
131135
}
132136

133137
protected <T extends @Nullable Object> T queryForObject(String sql, SqlParameterSource paramSource,
@@ -160,7 +164,7 @@ protected BindValue getBindableValue(Method method, @Nullable Object value, int
160164

161165
private BindValue getBindableValue(JdbcParameters.JdbcParameter parameter, @Nullable Object value) {
162166

163-
JdbcValue jdbcValue = StringValueUtil.getBindValue(aggregateOperations.getConverter(), value,
167+
JdbcValue jdbcValue = StringValueUtil.getBindValue(operations.getConverter(), value,
164168
parameter.getTypeInformation(), parameter.getSqlType(), parameter.getActualSqlType());
165169
SQLType jdbcType = jdbcValue.getJdbcType();
166170

@@ -223,9 +227,9 @@ private static SQLType getSqlType(Class<?> valueType) {
223227

224228
if (!projection.isInterface()) {
225229

226-
RelationalMappingContext mappingContext = aggregateOperations.getConverter().getMappingContext();
230+
RelationalMappingContext mappingContext = operations.getConverter().getMappingContext();
227231
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(projection, mappingContext,
228-
aggregateOperations.getConverter().getEntityInstantiators());
232+
operations.getConverter().getEntityInstantiators());
229233
return (T) converter.convert(result);
230234
}
231235

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/JdbcCodeBlocks.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.core.annotation.MergedAnnotation;
3434
import org.springframework.data.domain.SliceImpl;
3535
import org.springframework.data.domain.Sort;
36-
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
3736
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
3837
import org.springframework.data.jdbc.repository.query.Modifying;
3938
import org.springframework.data.jdbc.repository.query.ParameterBinding;
@@ -715,7 +714,7 @@ private CodeBlock delete(Builder builder, String rowMapper, String result, TypeN
715714
builder.addStatement("$T $L = ($T) getJdbcOperations().query($L, $L, new $T<>($L))", List.class, result,
716715
List.class, queryVariableName, parameterSourceVariableName, RowMapperResultSetExtractor.class, rowMapper);
717716

718-
builder.addStatement("$L.forEach($L::delete)", result, context.fieldNameOf(JdbcAggregateOperations.class));
717+
builder.addStatement("$L.forEach(getOperations()::delete)", result);
719718

720719
if (Collection.class.isAssignableFrom(context.getReturnType().toClass())) {
721720
builder.addStatement("return ($T) convertMany($L, $T.class)", context.getReturnTypeName(), result,

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/JdbcRepositoryContributor.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.core.annotation.MergedAnnotations;
2525
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
2626
import org.springframework.data.jdbc.core.convert.JdbcConverter;
27+
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
2728
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
2829
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
2930
import org.springframework.data.jdbc.repository.query.Modifying;
@@ -36,6 +37,7 @@
3637
import org.springframework.data.repository.aot.generate.MethodContributor;
3738
import org.springframework.data.repository.aot.generate.RepositoryContributor;
3839
import org.springframework.data.repository.config.AotRepositoryContext;
40+
import org.springframework.data.repository.config.RepositoryConfigurationSource;
3941
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
4042
import org.springframework.data.repository.query.QueryMethod;
4143
import org.springframework.data.repository.query.ReturnedType;
@@ -44,6 +46,7 @@
4446
import org.springframework.javapoet.CodeBlock;
4547
import org.springframework.javapoet.TypeName;
4648
import org.springframework.util.ClassUtils;
49+
import org.springframework.util.StringUtils;
4750

4851
/**
4952
* JDBC-specific {@link RepositoryContributor} contributing an AOT repository fragment.
@@ -55,15 +58,21 @@ public class JdbcRepositoryContributor extends RepositoryContributor {
5558

5659
private final RelationalMappingContext mappingContext;
5760
private final QueriesFactory queriesFactory;
61+
private final @Nullable String jdbcAggregateOperationsRef;
5862

5963
public JdbcRepositoryContributor(AotRepositoryContext repositoryContext, JdbcDialect dialect,
6064
JdbcConverter converter) {
6165

6266
super(repositoryContext);
6367

6468
this.mappingContext = converter.getMappingContext();
65-
this.queriesFactory = new QueriesFactory(repositoryContext.getConfigurationSource(), converter, dialect,
69+
70+
RepositoryConfigurationSource configurationSource = repositoryContext.getConfigurationSource();
71+
72+
this.queriesFactory = new QueriesFactory(configurationSource, converter, dialect,
6673
repositoryContext.getRequiredClassLoader(), ValueExpressionDelegate.create());
74+
75+
jdbcAggregateOperationsRef = configurationSource.getAttribute("jdbcAggregateOperationsRef").orElse(null);
6776
}
6877

6978
@Override
@@ -75,12 +84,19 @@ protected void customizeClass(AotRepositoryClassBuilder classBuilder) {
7584
protected void customizeConstructor(AotRepositoryConstructorBuilder constructorBuilder) {
7685

7786
constructorBuilder.addParameter("beanFactory", BeanFactory.class, false);
78-
constructorBuilder.addParameter("operations", JdbcAggregateOperations.class);
79-
constructorBuilder.addParameter("context", RepositoryFactoryBeanSupport.FragmentCreationContext.class,
80-
false);
87+
constructorBuilder.addParameter("context", RepositoryFactoryBeanSupport.FragmentCreationContext.class, false);
8188

8289
constructorBuilder.customize(builder -> {
83-
builder.addStatement("super(new $T(beanFactory), operations, context)", BeanFactoryAwareRowMapperFactory.class);
90+
91+
if (StringUtils.hasText(jdbcAggregateOperationsRef)) {
92+
builder.addStatement(
93+
"super(new $1T(beanFactory, beanFactory.getBean($2S, $3T.class), beanFactory.getBeanProvider($4T.class).getIfUnique(() -> $4T.EMPTY)), beanFactory.getBean($2S, $3T.class), context)",
94+
BeanFactoryAwareRowMapperFactory.class, jdbcAggregateOperationsRef, JdbcAggregateOperations.class,
95+
QueryMappingConfiguration.class);
96+
} else {
97+
builder.addStatement("super(new $1T(beanFactory), beanFactory.getBean($2T.class), context)",
98+
BeanFactoryAwareRowMapperFactory.class, JdbcAggregateOperations.class);
99+
}
84100
});
85101
}
86102

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.function.Supplier;
2020
import java.util.stream.Stream;
2121

22-
import org.springframework.core.convert.converter.Converter;
2322
import org.springframework.dao.EmptyResultDataAccessException;
2423
import org.springframework.data.repository.query.RepositoryQuery;
2524
import org.springframework.data.repository.query.ResultProcessor;
@@ -147,29 +146,5 @@ private <T> JdbcQueryExecution<T> createSingleReadingQueryExecution(ResultSetExt
147146
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
148147
}
149148

150-
/**
151-
* Factory to create a {@link RowMapper} for a given class.
152-
*
153-
* @since 2.3
154-
* @deprecated Use {@link org.springframework.data.jdbc.repository.query.RowMapperFactory} instead
155-
*/
156-
@Deprecated(forRemoval = true, since = "3.4.4")
157-
public interface RowMapperFactory extends org.springframework.data.jdbc.repository.query.RowMapperFactory {}
158149

159-
/**
160-
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
161-
*
162-
* @param <T>
163-
* @since 2.3
164-
* @deprecated use {@link org.springframework.data.jdbc.repository.query.ConvertingRowMapper} instead
165-
*/
166-
@Deprecated(forRemoval = true, since = "3.4.4")
167-
protected static class ConvertingRowMapper<T>
168-
extends org.springframework.data.jdbc.repository.query.ConvertingRowMapper {
169-
170-
@SuppressWarnings("unchecked")
171-
public ConvertingRowMapper(RowMapper<T> delegate, Converter<Object, Object> converter) {
172-
super((RowMapper<Object>) delegate, converter);
173-
}
174-
}
175150
}

0 commit comments

Comments
 (0)