Extract RowMapper into top level classes.

Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>

Commit message changed by Jens Schauder

Original pull request #2000
Closes #1998
This commit is contained in:
mipo256
2025-02-21 16:13:44 +03:00
committed by Mark Paluch
parent cb86d91ff0
commit 1ef4182907
11 changed files with 420 additions and 161 deletions

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Abstract {@link RowMapper} that delegates the actual mapping logic to a {@link AbstractDelegatingRowMapper#delegate delegate}
*
* @author Mikhail Polivakha
*/
public abstract class AbstractDelegatingRowMapper<T> implements RowMapper<T> {
private final RowMapper<T> delegate;
protected AbstractDelegatingRowMapper(RowMapper<T> delegate) {
Assert.notNull(delegate, "Delegating RowMapper cannot be null");
this.delegate = delegate;
}
@Override
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
T intermediate = delegate.mapRow(rs, rowNum);
return postProcessMapping(intermediate);
}
/**
* The post-processing callback for implementations.
*
* @return the mapped entity after applying post-processing logic
*/
protected T postProcessMapping(@Nullable T object) {
return object;
}
}

View File

@@ -153,60 +153,25 @@ public abstract class AbstractJdbcQuery implements RepositoryQuery {
* Factory to create a {@link RowMapper} for a given class.
*
* @since 2.3
* @deprecated Use {@link org.springframework.data.jdbc.repository.query.RowMapperFactory} instead
*/
public interface RowMapperFactory {
/**
* Create a {@link RowMapper} based on the expected return type passed in as an argument.
*
* @param result must not be {@code null}.
* @return a {@code RowMapper} producing instances of {@code result}.
*/
RowMapper<Object> create(Class<?> result);
/**
* Obtain a {@code RowMapper} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
*
* @param reference must not be {@code null}.
* @since 3.4
*/
default RowMapper<Object> getRowMapper(String reference) {
throw new UnsupportedOperationException("getRowMapper is not supported");
}
/**
* Obtain a {@code ResultSetExtractor} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
*
* @param reference must not be {@code null}.
* @since 3.4
*/
default ResultSetExtractor<Object> getResultSetExtractor(String reference) {
throw new UnsupportedOperationException("getResultSetExtractor is not supported");
}
}
@Deprecated(forRemoval = true, since = "3.4.4")
public interface RowMapperFactory extends org.springframework.data.jdbc.repository.query.RowMapperFactory { }
/**
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
*
* @param <T>
* @since 2.3
* @deprecated use {@link org.springframework.data.jdbc.repository.query.ConvertingRowMapper} instead
*/
protected static class ConvertingRowMapper<T> implements RowMapper<Object> {
private final RowMapper<T> delegate;
private final Converter<Object, Object> converter;
@Deprecated(forRemoval = true, since = "3.4.4")
protected static class ConvertingRowMapper<T> extends
org.springframework.data.jdbc.repository.query.ConvertingRowMapper {
@SuppressWarnings("unchecked")
public ConvertingRowMapper(RowMapper<T> delegate, Converter<Object, Object> converter) {
this.delegate = delegate;
this.converter = converter;
}
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
T object = delegate.mapRow(rs, rowNum);
return object == null ? null : converter.convert(object);
super((RowMapper<Object>) delegate, converter);
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import java.sql.ResultSet;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
/**
* Delegating {@link RowMapper} implementation that applies post-processing logic
* after the {@link RowMapper#mapRow(ResultSet, int)}. In particular, it emits the
* {@link AfterConvertEvent} event and invokes the {@link AfterConvertCallback} callbacks.
*
* @author Mark Paluch
* @author Mikhail Polivakha
*/
public class CallbackCapableRowMapper<T> extends AbstractDelegatingRowMapper<T> {
private final ApplicationEventPublisher publisher;
private final @Nullable EntityCallbacks callbacks;
public CallbackCapableRowMapper(RowMapper<T> delegate, ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks) {
super(delegate);
this.publisher = publisher;
this.callbacks = callbacks;
}
@Override
public T postProcessMapping(@Nullable T object) {
if (object != null) {
publisher.publishEvent(new AfterConvertEvent<>(object));
if (callbacks != null) {
return callbacks.callback(AfterConvertCallback.class, object);
}
}
return object;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import org.springframework.core.convert.converter.Converter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
/**
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
*
* @author Mark Paluch
* @author Mikhail Polivakha
*
* @since 2.3
*/
public class ConvertingRowMapper extends AbstractDelegatingRowMapper<Object> {
private final Converter<Object, Object> converter;
public ConvertingRowMapper(RowMapper<Object> delegate, Converter<Object, Object> converter) {
super(delegate);
this.converter = converter;
}
@Override
public Object postProcessMapping(@Nullable Object object) {
return object != null ? converter.convert(object) : null;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
/**
* Default implementation of {@link RowMapperFactory}. Honors the custom mappings defined
* in {@link QueryMappingConfiguration}.
* <p>
* This implementation is not capable of loading the {@link RowMapper} or {@link ResultSetExtractor}
* by reference via corresponding methods from {@link RowMapperFactory}.
*
* @implNote Public APIs of this class are thread-safe.
* @author Mikhail Polivakha
*/
public class DefaultRowMapperFactory implements RowMapperFactory {
private final RelationalMappingContext context;
private final JdbcConverter converter;
private final QueryMappingConfiguration queryMappingConfiguration;
private final EntityCallbacks entityCallbacks;
private final ApplicationEventPublisher publisher;
public DefaultRowMapperFactory(
RelationalMappingContext context,
JdbcConverter converter,
QueryMappingConfiguration queryMappingConfiguration,
EntityCallbacks entityCallbacks,
ApplicationEventPublisher publisher
) {
this.context = context;
this.converter = converter;
this.queryMappingConfiguration = queryMappingConfiguration;
this.entityCallbacks = entityCallbacks;
this.publisher = publisher;
}
@Override
@SuppressWarnings("unchecked")
public RowMapper<Object> create(Class<?> returnedObjectType) {
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
if (persistentEntity == null) {
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
converter.getConversionService());
}
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);
}
private RowMapper<?> determineDefaultMapper(Class<?> returnedObjectType) {
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(returnedObjectType);
if (configuredQueryMapper != null) {
return configuredQueryMapper;
}
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
context.getRequiredPersistentEntity(returnedObjectType), //
converter //
);
return new CallbackCapableRowMapper<>(defaultEntityRowMapper, publisher, entityCallbacks);
}
}

View File

@@ -24,7 +24,6 @@ import java.util.List;
import java.util.function.Function;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Pageable;
@@ -98,7 +97,7 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
* @since 2.3
*/
public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod queryMethod, Dialect dialect,
JdbcConverter converter, NamedParameterJdbcOperations operations, RowMapperFactory rowMapperFactory) {
JdbcConverter converter, NamedParameterJdbcOperations operations, org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory) {
super(queryMethod, operations);
@@ -292,7 +291,7 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
private final Lazy<RowMapper<?>> rowMapper;
private final Function<ResultProcessor, RowMapper<?>> rowMapperFunction;
public CachedRowMapperFactory(PartTree tree, RowMapperFactory rowMapperFactory, RelationalConverter converter,
public CachedRowMapperFactory(PartTree tree, org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, RelationalConverter converter,
ResultProcessor defaultResultProcessor) {
this.rowMapperFunction = processor -> {
@@ -302,7 +301,7 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
}
Converter<Object, Object> resultProcessingConverter = new ResultProcessingConverter(processor,
converter.getMappingContext(), converter.getEntityInstantiators());
return new ConvertingRowMapper<>(rowMapperFactory.create(processor.getReturnedType().getDomainType()),
return new org.springframework.data.jdbc.repository.query.ConvertingRowMapper(rowMapperFactory.create(processor.getReturnedType().getDomainType()),
resultProcessingConverter);
};

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
/**
* Factory to create a {@link RowMapper} for a given class.
*
* @author Jens Schauder
* @author Mikhail Polivakha
*
* @since 2.3
*/
public interface RowMapperFactory {
/**
* Obtain a {@link RowMapper} based on the expected return type passed in as an argument.
*
* @param result must not be {@code null}.
* @return a {@code RowMapper} producing instances of {@code result}.
*/
RowMapper<Object> create(Class<?> result);
/**
* Obtain a {@link RowMapper} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
*
* @param reference must not be {@code null}.
* @since 3.4
*/
default RowMapper<Object> getRowMapper(String reference) {
throw new UnsupportedOperationException("getRowMapper by reference is not supported");
}
/**
* Obtain a {@code ResultSetExtractor} from some other source, typically a {@link org.springframework.beans.factory.BeanFactory}.
*
* @param reference must not be {@code null}.
* @since 3.4
*/
default ResultSetExtractor<Object> getResultSetExtractor(String reference) {
throw new UnsupportedOperationException("getResultSetExtractor by reference is not supported");
}
}

View File

@@ -75,7 +75,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or use the javac flag -parameters";
private static final 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. Offending method: ";
private final JdbcConverter converter;
private final RowMapperFactory rowMapperFactory;
private final org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory;
private final ValueExpressionQueryRewriter.ParsedQuery parsedQuery;
private final String query;
@@ -85,7 +85,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
@@ -95,13 +95,13 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
* @since 3.4
*/
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
this(queryMethod.getRequiredQuery(), queryMethod, operations, rowMapperFactory, converter, delegate);
}
/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
* and {@link org.springframework.data.jdbc.repository.query.RowMapperFactory}.
*
* @param query must not be {@literal null} or empty.
* @param queryMethod must not be {@literal null}.
@@ -112,7 +112,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
* @since 3.4
*/
public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
org.springframework.data.jdbc.repository.query.RowMapperFactory rowMapperFactory, JdbcConverter converter, ValueExpressionDelegate delegate) {
super(queryMethod, operations);
Assert.hasText(query, "Query must not be null or empty");
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
@@ -318,7 +318,7 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
ResultProcessingConverter converter = new ResultProcessingConverter(resultProcessor,
this.converter.getMappingContext(), this.converter.getEntityInstantiators());
return new ConvertingRowMapper<>(rowMapperToUse, converter);
return new org.springframework.data.jdbc.repository.query.ConvertingRowMapper(rowMapperToUse, converter);
}
return cachedRowMapperFactory.getRowMapper();

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.support;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.jdbc.repository.query.DefaultRowMapperFactory;
import org.springframework.data.jdbc.repository.query.RowMapperFactory;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
/**
* This {@link RowMapperFactory} implementation extends the {@link DefaultRowMapperFactory}
* by adding the capabilities to load {@link RowMapper} or {@link ResultSetExtractor} beans by
* their names in {@link BeanFactory}.
*
* @author Mark Paluch
* @author Jens Schauder
* @author Mikhail Polivakha
*/
@SuppressWarnings("unchecked")
public class BeanFactoryAwareRowMapperFactory extends DefaultRowMapperFactory {
private final @Nullable BeanFactory beanFactory;
public BeanFactoryAwareRowMapperFactory(
RelationalMappingContext context,
JdbcConverter converter,
QueryMappingConfiguration queryMappingConfiguration,
EntityCallbacks entityCallbacks,
ApplicationEventPublisher publisher,
@Nullable BeanFactory beanFactory
) {
super(context, converter, queryMappingConfiguration, entityCallbacks, publisher);
this.beanFactory = beanFactory;
}
@Override
public RowMapper<Object> getRowMapper(String reference) {
if (beanFactory == null) {
throw new IllegalStateException(
"Cannot resolve RowMapper bean reference '" + reference + "'; BeanFactory is not configured.");
}
return beanFactory.getBean(reference, RowMapper.class);
}
@Override
public ResultSetExtractor<Object> getResultSetExtractor(String reference) {
if (beanFactory == null) {
throw new IllegalStateException(
"Cannot resolve ResultSetExtractor bean reference '" + reference + "'; BeanFactory is not configured.");
}
return beanFactory.getBean(reference, ResultSetExtractor.class);
}
}

View File

@@ -16,36 +16,28 @@
package org.springframework.data.jdbc.repository.support;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.convert.EntityRowMapper;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
import org.springframework.data.jdbc.repository.query.AbstractJdbcQuery;
import org.springframework.data.jdbc.repository.query.DefaultRowMapperFactory;
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery;
import org.springframework.data.jdbc.repository.query.RowMapperFactory;
import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -62,6 +54,7 @@ import org.springframework.util.Assert;
* @author Hebert Coelho
* @author Diego Krupitza
* @author Christopher Klein
* @author Mikhail Polivakha
*/
abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
@@ -109,6 +102,8 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
*/
static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy {
private final RowMapperFactory rowMapperFactory;
CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
@@ -116,6 +111,8 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
delegate);
this.rowMapperFactory = new DefaultRowMapperFactory(getMappingContext(), getConverter(), getQueryMappingConfiguration(), getCallbacks(), getPublisher());
}
@Override
@@ -124,8 +121,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries);
return new PartTreeJdbcQuery(getMappingContext(), queryMethod, getDialect(), getConverter(), getOperations(),
this::createMapper);
return new PartTreeJdbcQuery(getMappingContext(), queryMethod, getDialect(), getConverter(), getOperations(), rowMapperFactory);
}
}
@@ -138,7 +134,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
*/
static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy {
private final AbstractJdbcQuery.RowMapperFactory rowMapperFactory;
private final RowMapperFactory rowMapperFactory;
DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcConverter converter, Dialect dialect,
@@ -147,7 +143,7 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations,
delegate);
this.rowMapperFactory = new BeanFactoryRowMapperFactory(beanfactory);
this.rowMapperFactory = new BeanFactoryAwareRowMapperFactory(context, converter, queryMappingConfiguration, callbacks, publisher, beanfactory);
}
@Override
@@ -172,44 +168,6 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
throw new IllegalStateException(
String.format("Did neither find a NamedQuery nor an annotated query for method %s", method));
}
@SuppressWarnings("unchecked")
private class BeanFactoryRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory {
private final @Nullable BeanFactory beanFactory;
BeanFactoryRowMapperFactory(@Nullable BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public RowMapper<Object> create(Class<?> result) {
return createMapper(result);
}
@Override
public RowMapper<Object> getRowMapper(String reference) {
if (beanFactory == null) {
throw new IllegalStateException(
"Cannot resolve RowMapper bean reference '" + reference + "'; BeanFactory is not configured.");
}
return beanFactory.getBean(reference, RowMapper.class);
}
@Override
public ResultSetExtractor<Object> getResultSetExtractor(String reference) {
if (beanFactory == null) {
throw new IllegalStateException(
"Cannot resolve ResultSetExtractor bean reference '" + reference + "'; BeanFactory is not configured.");
}
return beanFactory.getBean(reference, ResultSetExtractor.class);
}
}
}
/**
@@ -320,57 +278,15 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
return operations;
}
@SuppressWarnings("unchecked")
RowMapper<Object> createMapper(Class<?> returnedObjectType) {
QueryMappingConfiguration getQueryMappingConfiguration() {
return queryMappingConfiguration;
}
RelationalPersistentEntity<?> persistentEntity = getMappingContext().getPersistentEntity(returnedObjectType);
EntityCallbacks getCallbacks() {
return callbacks;
}
if (persistentEntity == null) {
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType,
converter.getConversionService());
}
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);
}
private RowMapper<?> determineDefaultMapper(Class<?> returnedObjectType) {
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(returnedObjectType);
if (configuredQueryMapper != null)
return configuredQueryMapper;
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
getMappingContext().getRequiredPersistentEntity(returnedObjectType), //
converter //
);
return new PostProcessingRowMapper<>(defaultEntityRowMapper);
}
class PostProcessingRowMapper<T> implements RowMapper<T> {
private final RowMapper<T> delegate;
PostProcessingRowMapper(RowMapper<T> delegate) {
this.delegate = delegate;
}
@Override
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
T entity = delegate.mapRow(rs, rowNum);
if (entity != null) {
publisher.publishEvent(new AfterConvertEvent<>(entity));
if (callbacks != null) {
return callbacks.callback(AfterConvertCallback.class, entity);
}
}
return entity;
}
}
ApplicationEventPublisher getPublisher() {
return publisher;
}
}

View File

@@ -652,7 +652,7 @@ class StringBasedJdbcQueryUnitTests {
}
}
private class StubRowMapperFactory implements AbstractJdbcQuery.RowMapperFactory {
private class StubRowMapperFactory implements RowMapperFactory {
private final String preparedReference;
private final Object value;
@@ -662,7 +662,6 @@ class StringBasedJdbcQueryUnitTests {
this.value = value;
}
@Override
public RowMapper<Object> create(Class<?> result) {
return defaultRowMapper;
}
@@ -673,7 +672,7 @@ class StringBasedJdbcQueryUnitTests {
if (preparedReference.equals(reference)) {
return (RowMapper<Object>) value;
}
return AbstractJdbcQuery.RowMapperFactory.super.getRowMapper(reference);
return RowMapperFactory.super.getRowMapper(reference);
}
@Override
@@ -682,7 +681,7 @@ class StringBasedJdbcQueryUnitTests {
if (preparedReference.equals(reference)) {
return (ResultSetExtractor<Object>) value;
}
return AbstractJdbcQuery.RowMapperFactory.super.getResultSetExtractor(reference);
return RowMapperFactory.super.getResultSetExtractor(reference);
}
}
}