Skip to content

Commit 1d65e9c

Browse files
committed
rowmappers extraction
1 parent 131d17c commit 1d65e9c

11 files changed

+411
-178
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

-61
Original file line numberDiff line numberDiff line change
@@ -148,65 +148,4 @@ private <T> JdbcQueryExecution<Stream<T>> streamQuery(RowMapper<T> rowMapper) {
148148
private <T> JdbcQueryExecution<T> createSingleReadingQueryExecution(ResultSetExtractor<T> resultSetExtractor) {
149149
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
150150
}
151-
152-
/**
153-
* Factory to create a {@link RowMapper} for a given class.
154-
*
155-
* @since 2.3
156-
*/
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-
}
187-
188-
/**
189-
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
190-
*
191-
* @param <T>
192-
* @since 2.3
193-
*/
194-
protected static class ConvertingRowMapper<T> implements RowMapper<Object> {
195-
196-
private final RowMapper<T> delegate;
197-
private final Converter<Object, Object> converter;
198-
199-
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);
210-
}
211-
}
212151
}
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

+2-3
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;
@@ -298,11 +297,11 @@ public CachedRowMapperFactory(PartTree tree, RowMapperFactory rowMapperFactory,
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 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+
}

0 commit comments

Comments
 (0)