Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4cb07a6

Browse files
committedMar 16, 2025·
rowmappers extraction
1 parent 131d17c commit 4cb07a6

11 files changed

+430
-171
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.sql.ResultSet;
19+
import java.sql.SQLException;
20+
21+
import org.springframework.jdbc.core.RowMapper;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Abstract {@link RowMapper} that delegates the actual mapping logic to a {@link AbstractDelegatingRowMapper#delegate delegate}
27+
*
28+
* @author Mikhail Polivakha
29+
*/
30+
public abstract class AbstractDelegatingRowMapper<T> implements RowMapper<T> {
31+
32+
private final RowMapper<T> delegate;
33+
34+
protected AbstractDelegatingRowMapper(RowMapper<T> delegate) {
35+
Assert.notNull(delegate, "Delegating RowMapper cannot be null");
36+
37+
this.delegate = delegate;
38+
}
39+
40+
@Override
41+
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
42+
T intermediate = delegate.mapRow(rs, rowNum);
43+
return postProcessMapping(intermediate);
44+
}
45+
46+
/**
47+
* The post-processing callback for implementations.
48+
*
49+
* @return the mapped entity after applying post-processing logic
50+
*/
51+
protected T postProcessMapping(@Nullable T object) {
52+
return object;
53+
}
54+
}

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

+9-44
Original file line numberDiff line numberDiff line change
@@ -153,60 +153,25 @@ private <T> JdbcQueryExecution<T> createSingleReadingQueryExecution(ResultSetExt
153153
* Factory to create a {@link RowMapper} for a given class.
154154
*
155155
* @since 2.3
156+
* @deprecated Use {@link org.springframework.data.jdbc.repository.query.RowMapperFactory} instead
156157
*/
157-
public interface RowMapperFactory {
158-
159-
/**
160-
* Create a {@link RowMapper} based on the expected return type passed in as an argument.
161-
*
162-
* @param result must not be {@code null}.
163-
* @return a {@code RowMapper} producing instances of {@code result}.
164-
*/
165-
RowMapper<Object> create(Class<?> result);
166-
167-
/**
168-
* Obtain a {@code RowMapper} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
169-
*
170-
* @param reference must not be {@code null}.
171-
* @since 3.4
172-
*/
173-
default RowMapper<Object> getRowMapper(String reference) {
174-
throw new UnsupportedOperationException("getRowMapper is not supported");
175-
}
176-
177-
/**
178-
* Obtain a {@code ResultSetExtractor} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
179-
*
180-
* @param reference must not be {@code null}.
181-
* @since 3.4
182-
*/
183-
default ResultSetExtractor<Object> getResultSetExtractor(String reference) {
184-
throw new UnsupportedOperationException("getResultSetExtractor is not supported");
185-
}
186-
}
158+
@Deprecated(forRemoval = true, since = "3.4.4")
159+
public interface RowMapperFactory extends org.springframework.data.jdbc.repository.query.RowMapperFactory { }
187160

188161
/**
189162
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
190163
*
191164
* @param <T>
192165
* @since 2.3
166+
* @deprecated use {@link org.springframework.data.jdbc.repository.query.ConvertingRowMapper} instead
193167
*/
194-
protected static class ConvertingRowMapper<T> implements RowMapper<Object> {
195-
196-
private final RowMapper<T> delegate;
197-
private final Converter<Object, Object> converter;
168+
@Deprecated(forRemoval = true, since = "3.4.4")
169+
protected static class ConvertingRowMapper<T> extends
170+
org.springframework.data.jdbc.repository.query.ConvertingRowMapper {
198171

172+
@SuppressWarnings("unchecked")
199173
public ConvertingRowMapper(RowMapper<T> delegate, Converter<Object, Object> converter) {
200-
this.delegate = delegate;
201-
this.converter = converter;
202-
}
203-
204-
@Override
205-
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
206-
207-
T object = delegate.mapRow(rs, rowNum);
208-
209-
return object == null ? null : converter.convert(object);
174+
super((RowMapper<Object>) delegate, converter);
210175
}
211176
}
212177
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.sql.ResultSet;
19+
20+
import org.springframework.context.ApplicationEventPublisher;
21+
import org.springframework.data.mapping.callback.EntityCallbacks;
22+
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
23+
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
24+
import org.springframework.jdbc.core.RowMapper;
25+
import org.springframework.lang.Nullable;
26+
27+
/**
28+
* Delegating {@link RowMapper} implementation that applies post-processing logic
29+
* after the {@link RowMapper#mapRow(ResultSet, int)}. In particular, it emits the
30+
* {@link AfterConvertEvent} event and invokes the {@link AfterConvertCallback} callbacks.
31+
*
32+
* @author Mark Paluch
33+
* @author Mikhail Polivakha
34+
*/
35+
public class CallbackCapableRowMapper<T> extends AbstractDelegatingRowMapper<T> {
36+
37+
private final ApplicationEventPublisher publisher;
38+
private final @Nullable EntityCallbacks callbacks;
39+
40+
public CallbackCapableRowMapper(RowMapper<T> delegate, ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks) {
41+
super(delegate);
42+
this.publisher = publisher;
43+
this.callbacks = callbacks;
44+
}
45+
46+
@Override
47+
public T postProcessMapping(@Nullable T object) {
48+
if (object != null) {
49+
50+
publisher.publishEvent(new AfterConvertEvent<>(object));
51+
52+
if (callbacks != null) {
53+
return callbacks.callback(AfterConvertCallback.class, object);
54+
}
55+
56+
}
57+
return object;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.core.convert.converter.Converter;
19+
import org.springframework.jdbc.core.RowMapper;
20+
import org.springframework.lang.Nullable;
21+
22+
/**
23+
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
24+
*
25+
* @author Mark Paluch
26+
* @author Mikhail Polivakha
27+
*
28+
* @since 2.3
29+
*/
30+
public class ConvertingRowMapper extends AbstractDelegatingRowMapper<Object> {
31+
32+
private final Converter<Object, Object> converter;
33+
34+
public ConvertingRowMapper(RowMapper<Object> delegate, Converter<Object, Object> converter) {
35+
super(delegate);
36+
this.converter = converter;
37+
}
38+
39+
@Override
40+
public Object postProcessMapping(@Nullable Object object) {
41+
return object != null ? converter.convert(object) : null;
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.context.ApplicationEventPublisher;
19+
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
20+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
21+
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
22+
import org.springframework.data.mapping.callback.EntityCallbacks;
23+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
24+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
25+
import org.springframework.jdbc.core.ResultSetExtractor;
26+
import org.springframework.jdbc.core.RowMapper;
27+
import org.springframework.jdbc.core.SingleColumnRowMapper;
28+
29+
/**
30+
* Default implementation of {@link RowMapperFactory}. Honors the custom mappings defined
31+
* in {@link QueryMappingConfiguration}.
32+
* <p>
33+
* This implementation is not capable of loading the {@link RowMapper} or {@link ResultSetExtractor}
34+
* by reference via corresponding methods from {@link RowMapperFactory}.
35+
*
36+
* @implNote Public APIs of this class are thread-safe.
37+
* @author Mikhail Polivakha
38+
*/
39+
public class DefaultRowMapperFactory implements RowMapperFactory {
40+
41+
private final RelationalMappingContext context;
42+
private final JdbcConverter converter;
43+
private final QueryMappingConfiguration queryMappingConfiguration;
44+
private final EntityCallbacks entityCallbacks;
45+
private final ApplicationEventPublisher publisher;
46+
47+
public DefaultRowMapperFactory(
48+
RelationalMappingContext context,
49+
JdbcConverter converter,
50+
QueryMappingConfiguration queryMappingConfiguration,
51+
EntityCallbacks entityCallbacks,
52+
ApplicationEventPublisher publisher
53+
) {
54+
this.context = context;
55+
this.converter = converter;
56+
this.queryMappingConfiguration = queryMappingConfiguration;
57+
this.entityCallbacks = entityCallbacks;
58+
this.publisher = publisher;
59+
}
60+
61+
@Override
62+
@SuppressWarnings("unchecked")
63+
public RowMapper<Object> get(Class<?> returnedObjectType) {
64+
65+
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
66+
67+
if (persistentEntity == null) {
68+
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
69+
converter.getConversionService());
70+
}
71+
72+
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);
73+
}
74+
75+
private RowMapper<?> determineDefaultMapper(Class<?> returnedObjectType) {
76+
77+
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(returnedObjectType);
78+
79+
if (configuredQueryMapper != null) {
80+
return configuredQueryMapper;
81+
}
82+
83+
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
84+
context.getRequiredPersistentEntity(returnedObjectType), //
85+
converter //
86+
);
87+
88+
return new CallbackCapableRowMapper<>(defaultEntityRowMapper, publisher, entityCallbacks);
89+
}
90+
}

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.function.Function;
2525
import java.util.function.LongSupplier;
2626
import java.util.function.Supplier;
27-
import java.util.stream.Stream;
2827

2928
import org.springframework.core.convert.converter.Converter;
3029
import org.springframework.data.domain.Pageable;
@@ -98,7 +97,7 @@ public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod query
9897
* @since 2.3
9998
*/
10099
public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod queryMethod, Dialect dialect,
101-
JdbcConverter converter, NamedParameterJdbcOperations operations, RowMapperFactory rowMapperFactory) {
100+
JdbcConverter converter, NamedParameterJdbcOperations operations, org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory) {
102101

103102
super(queryMethod, operations);
104103

@@ -292,17 +291,17 @@ class CachedRowMapperFactory implements Supplier<RowMapper<?>> {
292291
private final Lazy<RowMapper<?>> rowMapper;
293292
private final Function<ResultProcessor, RowMapper<?>> rowMapperFunction;
294293

295-
public CachedRowMapperFactory(PartTree tree, RowMapperFactory rowMapperFactory, RelationalConverter converter,
294+
public CachedRowMapperFactory(PartTree tree, org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, RelationalConverter converter,
296295
ResultProcessor defaultResultProcessor) {
297296

298297
this.rowMapperFunction = processor -> {
299298

300299
if (tree.isCountProjection() || tree.isExistsProjection()) {
301-
return rowMapperFactory.create(resolveTypeToRead(processor));
300+
return rowMapperFactory.get(resolveTypeToRead(processor));
302301
}
303302
Converter<Object, Object> resultProcessingConverter = new ResultProcessingConverter(processor,
304303
converter.getMappingContext(), converter.getEntityInstantiators());
305-
return new ConvertingRowMapper<>(rowMapperFactory.create(processor.getReturnedType().getDomainType()),
304+
return new org.springframework.data.jdbc.repository.query.ConvertingRowMapper(rowMapperFactory.get(processor.getReturnedType().getDomainType()),
306305
resultProcessingConverter);
307306
};
308307

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.jdbc.core.ResultSetExtractor;
19+
import org.springframework.jdbc.core.RowMapper;
20+
21+
/**
22+
* Factory to create a {@link RowMapper} for a given class.
23+
*
24+
* @author Jens Schauder
25+
* @author Mikhail Polivakha
26+
*
27+
* @since 2.3
28+
*/
29+
public interface RowMapperFactory {
30+
31+
/**
32+
* Obtain a {@link RowMapper} based on the expected return type passed in as an argument.
33+
*
34+
* @param result must not be {@code null}.
35+
* @return a {@code RowMapper} producing instances of {@code result}.
36+
*/
37+
RowMapper<Object> get(Class<?> result);
38+
39+
/**
40+
* Obtain a {@link RowMapper} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
41+
*
42+
* @param reference must not be {@code null}.
43+
* @since 3.4
44+
*/
45+
default RowMapper<Object> get(String reference) {
46+
throw new UnsupportedOperationException("getRowMapper by reference is not supported");
47+
}
48+
49+
/**
50+
* Obtain a {@code ResultSetExtractor} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
51+
*
52+
* @param reference must not be {@code null}.
53+
* @since 3.4
54+
*/
55+
default ResultSetExtractor<Object> getResultSetExtractor(String reference) {
56+
throw new UnsupportedOperationException("getResultSetExtractor by reference is not supported");
57+
}
58+
}

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

+13-13
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
8383
private final static String LOCKING_IS_NOT_SUPPORTED = "Currently, @Lock is supported only on derived queries. In other words, for queries created with @Query, the locking condition specified with @Lock does nothing";
8484
private static final Log LOG = LogFactory.getLog(StringBasedJdbcQuery.class);
8585
private final JdbcConverter converter;
86-
private final RowMapperFactory rowMapperFactory;
86+
private final org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory;
8787
private final ValueExpressionQueryRewriter.ParsedQuery parsedQuery;
8888
private final String query;
8989

@@ -110,7 +110,7 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
110110

111111
/**
112112
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
113-
* and {@link RowMapperFactory}.
113+
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
114114
*
115115
* @param queryMethod must not be {@literal null}.
116116
* @param operations must not be {@literal null}.
@@ -122,15 +122,15 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
122122
*/
123123
@Deprecated(since = "3.4")
124124
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
125-
RowMapperFactory rowMapperFactory, JdbcConverter converter,
125+
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter,
126126
QueryMethodEvaluationContextProvider evaluationContextProvider) {
127127
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter,
128128
evaluationContextProvider);
129129
}
130130

131131
/**
132132
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
133-
* and {@link RowMapperFactory}.
133+
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
134134
*
135135
* @param queryMethod must not be {@literal null}.
136136
* @param operations must not be {@literal null}.
@@ -140,13 +140,13 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
140140
* @since 3.4
141141
*/
142142
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
143-
RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
143+
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
144144
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter, delegate);
145145
}
146146

147147
/**
148148
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
149-
* and {@link RowMapperFactory}.
149+
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
150150
*
151151
* @param query must not be {@literal null} or empty.
152152
* @param queryMethod must not be {@literal null}.
@@ -157,7 +157,7 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
157157
* @since 3.4
158158
*/
159159
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
160-
RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
160+
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
161161
super(queryMethod, operations);
162162
Assert.hasText(query, "Query must not be null or empty");
163163
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
@@ -181,7 +181,7 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
181181
}
182182

183183
this.cachedRowMapperFactory = new CachedRowMapperFactory(
184-
() -> rowMapperFactory.create(queryMethod.getResultProcessor().getReturnedType().getReturnedType()));
184+
() -> rowMapperFactory.get(queryMethod.getResultProcessor().getReturnedType().getReturnedType()));
185185
this.cachedResultSetExtractorFactory = new CachedResultSetExtractorFactory(
186186
this.cachedRowMapperFactory::getRowMapper);
187187

@@ -199,7 +199,7 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
199199

200200
/**
201201
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
202-
* and {@link RowMapperFactory}.
202+
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
203203
*
204204
* @param query must not be {@literal null} or empty.
205205
* @param queryMethod must not be {@literal null}.
@@ -212,7 +212,7 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara
212212
*/
213213
@Deprecated(since = "3.4")
214214
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
215-
RowMapperFactory rowMapperFactory, JdbcConverter converter,
215+
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter,
216216
QueryMethodEvaluationContextProvider evaluationContextProvider) {
217217
this(query, queryMethod, operations, rowMapperFactory, converter, new CachingValueExpressionDelegate(
218218
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), rootObject -> evaluationContextProvider
@@ -382,11 +382,11 @@ RowMapper<Object> determineRowMapper(ResultProcessor resultProcessor, boolean ha
382382

383383
if (hasDynamicProjection) {
384384

385-
RowMapper<Object> rowMapperToUse = rowMapperFactory.create(resultProcessor.getReturnedType().getDomainType());
385+
RowMapper<Object> rowMapperToUse = rowMapperFactory.get(resultProcessor.getReturnedType().getDomainType());
386386

387387
ResultProcessingConverter converter = new ResultProcessingConverter(resultProcessor,
388388
this.converter.getMappingContext(), this.converter.getEntityInstantiators());
389-
return new ConvertingRowMapper<>(rowMapperToUse, converter);
389+
return new org.springframework.data.jdbc.repository.query.ConvertingRowMapper(rowMapperToUse, converter);
390390
}
391391

392392
return cachedRowMapperFactory.getRowMapper();
@@ -438,7 +438,7 @@ public CachedRowMapperFactory(Supplier<RowMapper<Object>> defaultMapper) {
438438
this.cachedRowMapper = Lazy.of(() -> {
439439

440440
if (!ObjectUtils.isEmpty(rowMapperRef)) {
441-
return rowMapperFactory.getRowMapper(rowMapperRef);
441+
return rowMapperFactory.get(rowMapperRef);
442442
}
443443

444444
if (isUnconfigured(rowMapperClass, RowMapper.class)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.support;
17+
18+
import org.springframework.beans.factory.BeanFactory;
19+
import org.springframework.context.ApplicationEventPublisher;
20+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
21+
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
22+
import org.springframework.data.jdbc.repository.query.DefaultRowMapperFactory;
23+
import org.springframework.data.jdbc.repository.query.RowMapperFactory;
24+
import org.springframework.data.mapping.callback.EntityCallbacks;
25+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
26+
import org.springframework.jdbc.core.ResultSetExtractor;
27+
import org.springframework.jdbc.core.RowMapper;
28+
import org.springframework.lang.Nullable;
29+
30+
/**
31+
* This {@link RowMapperFactory} implementation extends the {@link DefaultRowMapperFactory}
32+
* by adding the capabilities to load {@link RowMapper} or {@link ResultSetExtractor} beans by
33+
* their names in {@link BeanFactory}.
34+
*
35+
* @author Mark Paluch
36+
* @author Jens Schauder
37+
* @author Mikhail Polivakha
38+
*/
39+
@SuppressWarnings("unchecked")
40+
public class BeanFactoryAwareRowMapperFactory extends DefaultRowMapperFactory {
41+
42+
private final @Nullable BeanFactory beanFactory;
43+
44+
public BeanFactoryAwareRowMapperFactory(
45+
RelationalMappingContext context,
46+
JdbcConverter converter,
47+
QueryMappingConfiguration queryMappingConfiguration,
48+
EntityCallbacks entityCallbacks,
49+
ApplicationEventPublisher publisher,
50+
@Nullable BeanFactory beanFactory
51+
) {
52+
super(context, converter, queryMappingConfiguration, entityCallbacks, publisher);
53+
54+
this.beanFactory = beanFactory;
55+
}
56+
57+
@Override
58+
public RowMapper<Object> get(String reference) {
59+
if (beanFactory == null) {
60+
throw new IllegalStateException(
61+
"Cannot resolve RowMapper bean reference '" + reference + "'; BeanFactory is not configured.");
62+
}
63+
64+
return beanFactory.getBean(reference, RowMapper.class);
65+
}
66+
67+
@Override
68+
public ResultSetExtractor<Object> getResultSetExtractor(String reference) {
69+
if (beanFactory == null) {
70+
throw new IllegalStateException(
71+
"Cannot resolve ResultSetExtractor bean reference '" + reference + "'; BeanFactory is not configured.");
72+
}
73+
74+
return beanFactory.getBean(reference, ResultSetExtractor.class);
75+
}
76+
}

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

+19-103
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,28 @@
1616
package org.springframework.data.jdbc.repository.support;
1717

1818
import java.lang.reflect.Method;
19-
import java.sql.ResultSet;
20-
import java.sql.SQLException;
2119

2220
import org.apache.commons.logging.Log;
2321
import org.apache.commons.logging.LogFactory;
2422
import org.springframework.beans.factory.BeanFactory;
2523
import org.springframework.context.ApplicationEventPublisher;
26-
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
2724
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2825
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
29-
import org.springframework.data.jdbc.repository.query.AbstractJdbcQuery;
26+
import org.springframework.data.jdbc.repository.query.DefaultRowMapperFactory;
3027
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
3128
import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery;
29+
import org.springframework.data.jdbc.repository.query.RowMapperFactory;
3230
import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery;
3331
import org.springframework.data.mapping.callback.EntityCallbacks;
3432
import org.springframework.data.projection.ProjectionFactory;
3533
import org.springframework.data.relational.core.dialect.Dialect;
3634
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
37-
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
38-
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
39-
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
4035
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
4136
import org.springframework.data.repository.core.NamedQueries;
4237
import org.springframework.data.repository.core.RepositoryMetadata;
4338
import org.springframework.data.repository.query.QueryLookupStrategy;
4439
import org.springframework.data.repository.query.RepositoryQuery;
4540
import org.springframework.data.repository.query.ValueExpressionDelegate;
46-
import org.springframework.jdbc.core.ResultSetExtractor;
47-
import org.springframework.jdbc.core.RowMapper;
48-
import org.springframework.jdbc.core.SingleColumnRowMapper;
4941
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
5042
import org.springframework.lang.Nullable;
5143
import org.springframework.util.Assert;
@@ -62,6 +54,7 @@
6254
* @author Hebert Coelho
6355
* @author Diego Krupitza
6456
* @author Christopher Klein
57+
* @author Mikhail Polivakha
6558
*/
6659
abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
6760

@@ -109,13 +102,17 @@ public RelationalMappingContext getMappingContext() {
109102
*/
110103
static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
111104

105+
private final RowMapperFactory rowMapperFactory;
106+
112107
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
113108
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
114109
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
115110
ValueExpressionDelegate delegate) {
116111

117112
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
118113
delegate);
114+
115+
this.rowMapperFactory = new DefaultRowMapperFactory(getMappingContext(), getConverter(), getQueryMappingConfiguration(), getCallbacks(), getPublisher());
119116
}
120117

121118
@Override
@@ -124,8 +121,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
124121

125122
JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
126123

127-
return new PartTreeJdbcQuery(getMappingContext(), queryMethod, getDialect(), getConverter(), getOperations(),
128-
this::createMapper);
124+
return new PartTreeJdbcQuery(getMappingContext(), queryMethod, getDialect(), getConverter(), getOperations(), rowMapperFactory);
129125
}
130126
}
131127

@@ -138,7 +134,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
138134
*/
139135
static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
140136

141-
private final AbstractJdbcQuery.RowMapperFactory rowMapperFactory;
137+
private final RowMapperFactory rowMapperFactory;
142138

143139
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
144140
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
@@ -147,7 +143,7 @@ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
147143
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
148144
delegate);
149145

150-
this.rowMapperFactory = new BeanFactoryRowMapperFactory(beanfactory);
146+
this.rowMapperFactory = new BeanFactoryAwareRowMapperFactory(context, converter, queryMappingConfiguration, callbacks, publisher, beanfactory);
151147
}
152148

153149
@Override
@@ -172,44 +168,6 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
172168
throw new IllegalStateException(
173169
String.format("Did neither find a NamedQuery nor an annotated query for method %s", method));
174170
}
175-
176-
@SuppressWarnings("unchecked")
177-
private class BeanFactoryRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory {
178-
179-
private final @Nullable BeanFactory beanFactory;
180-
181-
BeanFactoryRowMapperFactory(@Nullable BeanFactory beanFactory) {
182-
this.beanFactory = beanFactory;
183-
}
184-
185-
@Override
186-
public RowMapper<Object> create(Class<?> result) {
187-
return createMapper(result);
188-
}
189-
190-
@Override
191-
public RowMapper<Object> getRowMapper(String reference) {
192-
193-
if (beanFactory == null) {
194-
throw new IllegalStateException(
195-
"Cannot resolve RowMapper bean reference '" + reference + "'; BeanFactory is not configured.");
196-
}
197-
198-
return beanFactory.getBean(reference, RowMapper.class);
199-
}
200-
201-
@Override
202-
public ResultSetExtractor<Object> getResultSetExtractor(String reference) {
203-
204-
if (beanFactory == null) {
205-
throw new IllegalStateException(
206-
"Cannot resolve ResultSetExtractor bean reference '" + reference + "'; BeanFactory is not configured.");
207-
}
208-
209-
return beanFactory.getBean(reference, ResultSetExtractor.class);
210-
}
211-
}
212-
213171
}
214172

215173
/**
@@ -320,57 +278,15 @@ NamedParameterJdbcOperations getOperations() {
320278
return operations;
321279
}
322280

323-
@SuppressWarnings("unchecked")
324-
RowMapper<Object> createMapper(Class<?> returnedObjectType) {
325-
326-
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(returnedObjectType);
327-
328-
if (persistentEntity == null) {
329-
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
330-
converter.getConversionService());
331-
}
332-
333-
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);
334-
}
335-
336-
private RowMapper<?> determineDefaultMapper(Class<?> returnedObjectType) {
337-
338-
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(returnedObjectType);
339-
340-
if (configuredQueryMapper != null)
341-
return configuredQueryMapper;
342-
343-
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
344-
getMappingContext().getRequiredPersistentEntity(returnedObjectType), //
345-
converter //
346-
);
347-
348-
return new PostProcessingRowMapper<>(defaultEntityRowMapper);
349-
}
350-
351-
class PostProcessingRowMapper<T> implements RowMapper<T> {
352-
353-
private final RowMapper<T> delegate;
281+
QueryMappingConfiguration getQueryMappingConfiguration() {
282+
return queryMappingConfiguration;
283+
}
354284

355-
PostProcessingRowMapper(RowMapper<T> delegate) {
356-
this.delegate = delegate;
357-
}
358-
359-
@Override
360-
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
361-
362-
T entity = delegate.mapRow(rs, rowNum);
363-
364-
if (entity != null) {
365-
366-
publisher.publishEvent(new AfterConvertEvent<>(entity));
285+
EntityCallbacks getCallbacks() {
286+
return callbacks;
287+
}
367288

368-
if (callbacks != null) {
369-
return callbacks.callback(AfterConvertCallback.class, entity);
370-
}
371-
}
372-
373-
return entity;
374-
}
375-
}
289+
ApplicationEventPublisher getPublisher() {
290+
return publisher;
291+
}
376292
}

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ public Object getRootObject() {
633633
}
634634
}
635635

636-
private class StubRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory {
636+
private class StubRowMapperFactory implements RowMapperFactory {
637637

638638
private final String preparedReference;
639639
private final Object value;
@@ -643,18 +643,17 @@ private class StubRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory
643643
this.value = value;
644644
}
645645

646-
@Override
647-
public RowMapper<Object> create(Class<?> result) {
646+
public RowMapper<Object> get(Class<?> result) {
648647
return defaultRowMapper;
649648
}
650649

651650
@Override
652-
public RowMapper<Object> getRowMapper(String reference) {
651+
public RowMapper<Object> get(String reference) {
653652

654653
if (preparedReference.equals(reference)) {
655654
return (RowMapper<Object>) value;
656655
}
657-
return AbstractJdbcQuery.RowMapperFactory.super.getRowMapper(reference);
656+
return RowMapperFactory.super.get(reference);
658657
}
659658

660659
@Override
@@ -663,7 +662,7 @@ public ResultSetExtractor<Object> getResultSetExtractor(String reference) {
663662
if (preparedReference.equals(reference)) {
664663
return (ResultSetExtractor<Object>) value;
665664
}
666-
return AbstractJdbcQuery.RowMapperFactory.super.getResultSetExtractor(reference);
665+
return RowMapperFactory.super.getResultSetExtractor(reference);
667666
}
668667
}
669668
}

0 commit comments

Comments
 (0)
Please sign in to comment.