Add support for repository query method projections.

JDBC repository methods now support interface and DTO projections by specifying either the projection type as return type or using generics and providing a Class parameter to query methods.

Closes #971
Original pull request #980
This commit is contained in:
Mark Paluch
2021-05-17 15:37:09 +02:00
committed by Jens Schauder
parent fc92bd5b60
commit b3a2925573
12 changed files with 385 additions and 98 deletions

View File

@@ -15,10 +15,15 @@
*/
package org.springframework.data.jdbc.repository.query;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
@@ -43,23 +48,17 @@ public abstract class AbstractJdbcQuery implements RepositoryQuery {
private final NamedParameterJdbcOperations operations;
/**
* Creates a new {@link AbstractJdbcQuery} for the given {@link JdbcQueryMethod}, {@link NamedParameterJdbcOperations}
* and {@link RowMapper}.
* Creates a new {@link AbstractJdbcQuery} for the given {@link JdbcQueryMethod} and
* {@link NamedParameterJdbcOperations}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
*/
AbstractJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@Nullable RowMapper<?> defaultRowMapper) {
AbstractJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations) {
Assert.notNull(queryMethod, "Query method must not be null!");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");
if (!queryMethod.isModifyingQuery()) {
Assert.notNull(defaultRowMapper, "Mapper must not be null!");
}
this.queryMethod = queryMethod;
this.operations = operations;
}
@@ -123,8 +122,59 @@ public abstract class AbstractJdbcQuery implements RepositoryQuery {
return getQueryExecution(new RowMapperResultSetExtractor<>(rowMapper));
}
/**
* Obtain the result type to read from {@link ResultProcessor}.
*
* @param resultProcessor
* @return
*/
protected Class<?> resolveTypeToRead(ResultProcessor resultProcessor) {
ReturnedType returnedType = resultProcessor.getReturnedType();
if (returnedType.getReturnedType().isAssignableFrom(returnedType.getDomainType())) {
return returnedType.getDomainType();
}
// Slight deviation from R2DBC: Allow direct mapping into DTOs
return returnedType.isProjecting() && returnedType.getReturnedType().isInterface() ? returnedType.getDomainType()
: returnedType.getReturnedType();
}
private <T> JdbcQueryExecution<T> getQueryExecution(ResultSetExtractor<T> resultSetExtractor) {
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
}
/**
* Factory to create a {@link RowMapper} for a given class.
*
* @since 2.3
*/
public interface RowMapperFactory {
RowMapper<Object> create(Class<?> result);
}
/**
* Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
*
* @param <T>
* @since 2.3
*/
protected static class ConvertingRowMapper<T> implements RowMapper<Object> {
private final RowMapper<T> delegate;
private final Converter<Object, Object> converter;
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 converter.convert(object);
}
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.data.relational.core.sql.SelectBuilder;
import org.springframework.data.relational.core.sql.Table;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
/**
@@ -38,8 +39,9 @@ import org.springframework.data.repository.query.parser.PartTree;
class JdbcCountQueryCreator extends JdbcQueryCreator {
JdbcCountQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery) {
super(context, tree, converter, dialect, entityMetadata, accessor, isSliceQuery);
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery,
ReturnedType returnedType) {
super(context, tree, converter, dialect, entityMetadata, accessor, isSliceQuery, returnedType);
}
@Override

View File

@@ -44,6 +44,7 @@ import org.springframework.data.relational.repository.query.RelationalEntityMeta
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -67,6 +68,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
private final RelationalEntityMetadata<?> entityMetadata;
private final RenderContextFactory renderContextFactory;
private final boolean isSliceQuery;
private final ReturnedType returnedType;
/**
* Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
@@ -79,14 +81,17 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
* @param entityMetadata relational entity metadata, must not be {@literal null}.
* @param accessor parameter metadata provider, must not be {@literal null}.
* @param isSliceQuery
* @param returnedType
*/
JdbcQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery) {
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery,
ReturnedType returnedType) {
super(tree, accessor);
Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
Assert.notNull(returnedType, "ReturnedType must not be null");
this.context = context;
this.tree = tree;
@@ -96,6 +101,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
this.queryMapper = new QueryMapper(dialect, converter);
this.renderContextFactory = new RenderContextFactory(dialect);
this.isSliceQuery = isSliceQuery;
this.returnedType = returnedType;
}
/**
@@ -241,6 +247,13 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
joinTables.add(join);
}
if (returnedType.needsCustomConstruction()) {
if (!returnedType.getInputProperties()
.contains(extPath.getRequiredPersistentPropertyPath().getBaseProperty().getName())) {
continue;
}
}
Column column = getColumn(sqlContext, extPath);
if (column != null) {
columnExpressions.add(column);

View File

@@ -15,8 +15,18 @@
*/
package org.springframework.data.jdbc.repository.query;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.repository.query.DtoInstantiatingConverter;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.Lazy;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Interface specifying a query execution strategy. Implementations encapsulate information how to actually execute the
@@ -37,4 +47,41 @@ interface JdbcQueryExecution<T> {
*/
@Nullable
T execute(String query, SqlParameterSource parameter);
/**
* A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}.
*
* @author Mark Paluch
* @since 2.3
*/
class ResultProcessingConverter implements Converter<Object, Object> {
private final ResultProcessor processor;
private final Lazy<Converter<Object, Object>> converter;
ResultProcessingConverter(ResultProcessor processor,
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext,
EntityInstantiators instantiators) {
this.processor = processor;
this.converter = Lazy.of(() -> new DtoInstantiatingConverter(processor.getReturnedType().getReturnedType(),
mappingContext, instantiators));
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Object convert(Object source) {
ReturnedType returnedType = processor.getReturnedType();
if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())
|| returnedType.getReturnedType().isInstance(source)) {
return source;
}
return processor.processResult(source, converter.get());
}
}
}

View File

@@ -15,12 +15,15 @@
*/
package org.springframework.data.jdbc.repository.query;
import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.*;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.LongSupplier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
@@ -32,6 +35,8 @@ import org.springframework.data.relational.repository.query.RelationalEntityMeta
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -53,9 +58,8 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
private final Parameters<?, ?> parameters;
private final Dialect dialect;
private final JdbcConverter converter;
private final RowMapperFactory rowMapperFactory;
private final PartTree tree;
/** The execution for obtaining the bulk of the data. The execution may be decorated with further processing for handling sliced or paged queries */
private final JdbcQueryExecution<?> coreExecution;
/**
* Creates a new {@link PartTreeJdbcQuery}.
@@ -69,26 +73,40 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
*/
public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod queryMethod, Dialect dialect,
JdbcConverter converter, NamedParameterJdbcOperations operations, RowMapper<Object> rowMapper) {
this(context, queryMethod, dialect, converter, operations, it -> rowMapper);
}
super(queryMethod, operations, rowMapper);
/**
* Creates a new {@link PartTreeJdbcQuery}.
*
* @param context must not be {@literal null}.
* @param queryMethod must not be {@literal null}.
* @param dialect must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
* @since 2.3
*/
public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod queryMethod, Dialect dialect,
JdbcConverter converter, NamedParameterJdbcOperations operations, RowMapperFactory rowMapperFactory) {
super(queryMethod, operations);
Assert.notNull(context, "RelationalMappingContext must not be null");
Assert.notNull(queryMethod, "JdbcQueryMethod must not be null");
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
this.context = context;
this.parameters = queryMethod.getParameters();
this.dialect = dialect;
this.converter = converter;
this.rowMapperFactory = rowMapperFactory;
this.tree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType());
JdbcQueryCreator.validate(this.tree, this.parameters, this.converter.getMappingContext());
ResultSetExtractor<Boolean> extractor = tree.isExistsProjection() ? (ResultSet::next) : null;
this.coreExecution = queryMethod.isPageQuery() || queryMethod.isSliceQuery() ? collectionQuery(rowMapper)
: getQueryExecution(queryMethod, extractor, rowMapper);
}
private Sort getDynamicSort(RelationalParameterAccessor accessor) {
@@ -104,30 +122,48 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
RelationalParametersParameterAccessor accessor = new RelationalParametersParameterAccessor(getQueryMethod(),
values);
ParametrizedQuery query = createQuery(accessor);
JdbcQueryExecution<?> execution = getDecoratedExecution(accessor);
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
ParametrizedQuery query = createQuery(accessor, processor.getReturnedType());
JdbcQueryExecution<?> execution = getQueryExecution(processor, accessor);
return execution.execute(query.getQuery(), query.getParameterSource());
}
/**
* The decorated execution is the {@link #coreExecution} decorated with further processing for handling sliced or paged queries.
*/
private JdbcQueryExecution<?> getDecoratedExecution(RelationalParametersParameterAccessor accessor) {
private JdbcQueryExecution<?> getQueryExecution(ResultProcessor processor,
RelationalParametersParameterAccessor accessor) {
ResultSetExtractor<Boolean> extractor = tree.isExistsProjection() ? (ResultSet::next) : null;
RowMapper<Object> rowMapper;
if (tree.isCountProjection() || tree.isExistsProjection()) {
rowMapper = rowMapperFactory.create(resolveTypeToRead(processor));
} else {
Converter<Object, Object> resultProcessingConverter = new ResultProcessingConverter(processor,
this.converter.getMappingContext(), this.converter.getEntityInstantiators());
rowMapper = new ConvertingRowMapper<>(rowMapperFactory.create(processor.getReturnedType().getDomainType()),
resultProcessingConverter);
}
JdbcQueryExecution<?> queryExecution = getQueryMethod().isPageQuery() || getQueryMethod().isSliceQuery()
? collectionQuery(rowMapper)
: getQueryExecution(getQueryMethod(), extractor, rowMapper);
if (getQueryMethod().isSliceQuery()) {
return new SliceQueryExecution<>((JdbcQueryExecution<Collection<Object>>) this.coreExecution, accessor.getPageable());
return new SliceQueryExecution<>((JdbcQueryExecution<Collection<Object>>) queryExecution, accessor.getPageable());
}
if (getQueryMethod().isPageQuery()) {
return new PageQueryExecution<>((JdbcQueryExecution<Collection<Object>>) this.coreExecution, accessor.getPageable(),
return new PageQueryExecution<>((JdbcQueryExecution<Collection<Object>>) queryExecution, accessor.getPageable(),
() -> {
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
JdbcCountQueryCreator queryCreator = new JdbcCountQueryCreator(context, tree, converter, dialect,
entityMetadata, accessor, false);
entityMetadata, accessor, false, processor.getReturnedType());
ParametrizedQuery countQuery = queryCreator.createQuery(Sort.unsorted());
Object count = singleObjectQuery((rs, i) -> rs.getLong(1)).execute(countQuery.getQuery(),
@@ -137,15 +173,15 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery {
});
}
return this.coreExecution;
return queryExecution;
}
protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor accessor) {
protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor accessor, ReturnedType returnedType) {
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
JdbcQueryCreator queryCreator = new JdbcQueryCreator(context, tree, converter, dialect, entityMetadata, accessor,
getQueryMethod().isSliceQuery());
getQueryMethod().isSliceQuery(), returnedType);
return queryCreator.createQuery(getDynamicSort(accessor));
}

View File

@@ -15,18 +15,24 @@
*/
package org.springframework.data.jdbc.repository.query;
import static org.springframework.data.jdbc.repository.query.JdbcQueryExecution.*;
import java.lang.reflect.Constructor;
import java.sql.JDBCType;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.util.Lazy;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -53,8 +59,8 @@ 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 when on Java 8+ use the javac flag -parameters.";
private final JdbcQueryMethod queryMethod;
private final Lazy<JdbcQueryExecution<?>> executor;
private final JdbcConverter converter;
private final RowMapperFactory rowMapperFactory;
private BeanFactory beanFactory;
/**
@@ -67,11 +73,28 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
*/
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@Nullable RowMapper<?> defaultRowMapper, JdbcConverter converter) {
this(queryMethod, operations, result -> (RowMapper<Object>) defaultRowMapper, converter);
}
super(queryMethod, operations, defaultRowMapper);
/**
* Creates a new {@link StringBasedJdbcQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
* and {@link RowMapperFactory}.
*
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param rowMapperFactory must not be {@literal null}.
* @since 2.3
*/
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapperFactory rowMapperFactory, JdbcConverter converter) {
super(queryMethod, operations);
Assert.notNull(rowMapperFactory, "RowMapperFactory must not be null");
this.queryMethod = queryMethod;
this.converter = converter;
this.rowMapperFactory = rowMapperFactory;
if (queryMethod.isSliceQuery()) {
throw new UnsupportedOperationException(
@@ -82,14 +105,6 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
throw new UnsupportedOperationException(
"Page queries are not supported using string-based queries. Offending method: " + queryMethod);
}
executor = Lazy.of(() -> {
RowMapper<Object> rowMapper = determineRowMapper(defaultRowMapper);
return getQueryExecution( //
queryMethod, //
determineResultSetExtractor(rowMapper), //
rowMapper //
);});
}
/*
@@ -98,7 +113,21 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
*/
@Override
public Object execute(Object[] objects) {
return executor.get().execute(determineQuery(), this.bindParameters(objects));
RelationalParameterAccessor accessor = new RelationalParametersParameterAccessor(getQueryMethod(), objects);
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
ResultProcessingConverter converter = new ResultProcessingConverter(processor, this.converter.getMappingContext(),
this.converter.getEntityInstantiators());
RowMapper<Object> rowMapper = determineRowMapper(rowMapperFactory.create(resolveTypeToRead(processor)), converter,
accessor.findDynamicProjection() != null);
JdbcQueryExecution<?> queryExecution = getQueryExecution(//
queryMethod, //
determineResultSetExtractor(rowMapper), //
rowMapper);
return queryExecution.execute(determineQuery(), this.bindParameters(accessor));
}
/*
@@ -110,12 +139,15 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
return queryMethod;
}
private MapSqlParameterSource bindParameters(Object[] objects) {
private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
queryMethod.getParameters().getBindableParameters()
.forEach(p -> convertAndAddParameter(parameters, p, objects[p.getIndex()]));
Parameters<?, ?> bindableParameters = accessor.getBindableParameters();
for (Parameter bindableParameter : bindableParameters) {
convertAndAddParameter(parameters, bindableParameter, accessor.getBindableValue(bindableParameter.getIndex()));
}
return parameters;
}
@@ -179,6 +211,19 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
return BeanUtils.instantiateClass(resultSetExtractorClass);
}
@Nullable
RowMapper<Object> determineRowMapper(@Nullable RowMapper<?> defaultMapper,
Converter<Object, Object> resultProcessingConverter, boolean hasDynamicProjection) {
RowMapper<Object> rowMapperToUse = determineRowMapper(defaultMapper);
if ((hasDynamicProjection || rowMapperToUse == defaultMapper) && rowMapperToUse != null) {
return new ConvertingRowMapper<>(rowMapperToUse, resultProcessingConverter);
}
return rowMapperToUse;
}
@SuppressWarnings("unchecked")
@Nullable
RowMapper<Object> determineRowMapper(@Nullable RowMapper<?> defaultMapper) {

View File

@@ -103,12 +103,11 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
try {
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, mapper, converter);
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, this::createMapper, converter);
query.setBeanFactory(beanfactory);
return query;
} else {
return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, createMapper(queryMethod));
return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, this::createMapper);
}
} catch (Exception e) {
throw QueryCreationException.create(queryMethod, e);
@@ -116,9 +115,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
}
@SuppressWarnings("unchecked")
private RowMapper<Object> createMapper(JdbcQueryMethod queryMethod) {
Class<?> returnedObjectType = queryMethod.getReturnedObjectType();
private RowMapper<Object> createMapper(Class<?> returnedObjectType) {
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(returnedObjectType);
@@ -126,19 +123,18 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
return (RowMapper<Object>) SingleColumnRowMapper.newInstance(returnedObjectType, converter.getConversionService());
}
return (RowMapper<Object>) determineDefaultMapper(queryMethod);
return (RowMapper<Object>) determineDefaultMapper(returnedObjectType);
}
private RowMapper<?> determineDefaultMapper(JdbcQueryMethod queryMethod) {
private RowMapper<?> determineDefaultMapper(Class<?> returnedObjectType) {
Class<?> domainType = queryMethod.getReturnedObjectType();
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(domainType);
RowMapper<?> configuredQueryMapper = queryMappingConfiguration.getRowMapper(returnedObjectType);
if (configuredQueryMapper != null)
return configuredQueryMapper;
EntityRowMapper<?> defaultEntityRowMapper = new EntityRowMapper<>( //
context.getRequiredPersistentEntity(domainType), //
context.getRequiredPersistentEntity(returnedObjectType), //
converter //
);

View File

@@ -22,6 +22,7 @@ import static org.springframework.test.context.TestExecutionListeners.MergeMode.
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import java.io.IOException;
import java.sql.ResultSet;
@@ -441,6 +442,50 @@ public class JdbcRepositoryIntegrationTests {
assertThat(slice.hasNext()).isTrue();
}
@Test // #971
public void stringQueryProjectionShouldReturnProjectedEntities() {
repository.save(createDummyEntity());
List<DummyProjection> result = repository.findProjectedWithSql(DummyProjection.class);
assertThat(result).hasSize(1);
assertThat(result.get(0).getName()).isEqualTo("Entity Name");
}
@Test // #971
public void stringQueryProjectionShouldReturnDtoProjectedEntities() {
repository.save(createDummyEntity());
List<DtoProjection> result = repository.findProjectedWithSql(DtoProjection.class);
assertThat(result).hasSize(1);
assertThat(result.get(0).getName()).isEqualTo("Entity Name");
}
@Test // #971
public void partTreeQueryProjectionShouldReturnProjectedEntities() {
repository.save(createDummyEntity());
List<DummyProjection> result = repository.findProjectedByName("Entity Name");
assertThat(result).hasSize(1);
assertThat(result.get(0).getName()).isEqualTo("Entity Name");
}
@Test // #971
public void pageQueryProjectionShouldReturnProjectedEntities() {
repository.save(createDummyEntity());
Page<DummyProjection> result = repository.findPageProjectionByName("Entity Name", PageRequest.ofSize(10));
assertThat(result).hasSize(1);
assertThat(result.getContent().get(0).getName()).isEqualTo("Entity Name");
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
List<DummyEntity> findAllByNamedQuery();
@@ -450,6 +495,11 @@ public class JdbcRepositoryIntegrationTests {
@Query("SELECT * FROM DUMMY_ENTITY")
List<DummyEntity> findAllWithSql();
@Query("SELECT * FROM DUMMY_ENTITY")
<T> List<T> findProjectedWithSql(Class<T> targetType);
List<DummyProjection> findProjectedByName(String name);
@Query(value = "SELECT * FROM DUMMY_ENTITY", rowMapperClass = CustomRowMapper.class)
List<DummyEntity> findAllWithCustomMapper();
@@ -472,6 +522,8 @@ public class JdbcRepositoryIntegrationTests {
Page<DummyEntity> findPageByNameContains(String name, Pageable pageable);
Page<DummyProjection> findPageProjectionByName(String name, Pageable pageable);
Slice<DummyEntity> findSliceByNameContains(String name, Pageable pageable);
}
@@ -528,6 +580,17 @@ public class JdbcRepositoryIntegrationTests {
}
}
interface DummyProjection {
String getName();
}
@Value
static class DtoProjection {
String name;
}
static class CustomRowMapper implements RowMapper<DummyEntity> {
@Override

View File

@@ -30,6 +30,7 @@ import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
@@ -46,6 +47,7 @@ import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -67,6 +69,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcMappingContext mappingContext = new JdbcMappingContext();
JdbcConverter converter = new BasicJdbcConverter(mappingContext, mock(RelationResolver.class));
ReturnedType returnedType = mock(ReturnedType.class);
@Test // DATAJDBC-318
public void shouldFailForQueryByReference() throws Exception {
@@ -108,17 +111,31 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }), returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
}
@Test // #971
public void createsQueryToFindAllEntitiesByProjectionAttribute() throws Exception {
when(returnedType.needsCustomConstruction()).thenReturn(true);
when(returnedType.getInputProperties()).thenReturn(Collections.singletonList("firstName"));
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }), returnedType);
assertThat(query.getQuery()).isEqualTo("SELECT " + TABLE + ".\"FIRST_NAME\" AS \"FIRST_NAME\" " + JOIN_CLAUSE
+ " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
}
@Test // DATAJDBC-318
public void createsQueryWithIsNullCondition() throws Exception {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { null })));
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { null })), returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" IS NULL");
}
@@ -128,7 +145,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("existsByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })));
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })), returnedType);
assertThat(query.getQuery()).isEqualTo(
"SELECT " + TABLE + ".\"ID\" FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 1");
@@ -139,7 +156,8 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameAndFirstName", String.class, String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }));
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }),
returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" = :last_name AND (" + TABLE
+ ".\"FIRST_NAME\" = :first_name)");
@@ -150,7 +168,8 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameOrFirstName", String.class, String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }));
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }),
returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" = :last_name OR (" + TABLE
+ ".\"FIRST_NAME\" = :first_name)");
@@ -164,7 +183,7 @@ public class PartTreeJdbcQueryUnitTests {
Date from = new Date();
Date to = new Date();
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery())
.isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1");
@@ -179,7 +198,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThan", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" < :age");
}
@@ -190,7 +209,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThanEqual", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" <= :age");
}
@@ -201,7 +220,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThan", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" > :age");
}
@@ -212,7 +231,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThanEqual", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" >= :age");
}
@@ -223,7 +242,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthAfter", Date.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" > :date_of_birth");
}
@@ -233,7 +252,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBefore", Date.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" < :date_of_birth");
}
@@ -244,7 +263,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNull");
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IS NULL");
}
@@ -255,7 +274,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNotNull");
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IS NOT NULL");
}
@@ -266,7 +285,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameLike", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
}
@@ -277,7 +296,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotLike", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name");
}
@@ -288,7 +307,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
}
@@ -299,7 +318,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("Jo%");
@@ -311,7 +330,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
}
@@ -322,7 +341,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%hn");
@@ -334,7 +353,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
}
@@ -345,7 +364,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name");
assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%");
@@ -357,7 +376,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name");
}
@@ -368,7 +387,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name");
assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%");
@@ -380,7 +399,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameDesc", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 123 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery())
.isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" DESC");
@@ -391,7 +410,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameAsc", Integer.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 123 });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery())
.isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" ASC");
@@ -402,7 +421,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameNot", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" != :last_name");
}
@@ -414,7 +433,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IN (:age)");
}
@@ -425,7 +444,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" NOT IN (:age)");
}
@@ -436,7 +455,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByActiveTrue");
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"ACTIVE\" = TRUE");
}
@@ -447,7 +466,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByActiveFalse");
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"ACTIVE\" = FALSE");
}
@@ -458,7 +477,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameIgnoreCase", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
assertThat(query.getQuery())
.isEqualTo(BASE_SELECT + " WHERE UPPER(" + TABLE + ".\"FIRST_NAME\") = UPPER(:first_name)");
@@ -471,7 +490,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
assertThatIllegalStateException()
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L })));
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }), returnedType));
}
@Test // DATAJDBC-318
@@ -481,7 +500,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
assertThatIllegalArgumentException()
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0])));
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
}
@Test // DATAJDBC-318
@@ -491,7 +510,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
assertThatIllegalArgumentException()
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0])));
.isThrownBy(() -> jdbcQuery.createQuery(getAccessor(queryMethod, new Object[0]), returnedType));
}
@Test // DATAJDBC-318
@@ -500,7 +519,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findTop3ByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 3";
assertThat(query.getQuery()).isEqualTo(expectedSql);
@@ -512,7 +531,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findFirstByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 1";
assertThat(query.getQuery()).isEqualTo(expectedSql);
@@ -525,7 +544,7 @@ public class PartTreeJdbcQueryUnitTests {
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { new Address("Hello", "World") });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
String expectedSql = BASE_SELECT + " WHERE (" + TABLE + ".\"USER_STREET\" = :user_street AND " + TABLE
+ ".\"USER_CITY\" = :user_city)";
@@ -541,7 +560,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("findByAddressStreet", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Hello" });
ParametrizedQuery query = jdbcQuery.createQuery(accessor);
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"USER_STREET\" = :user_street";
@@ -554,7 +573,7 @@ public class PartTreeJdbcQueryUnitTests {
JdbcQueryMethod queryMethod = getQueryMethod("countByFirstName", String.class);
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })));
ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })), returnedType);
assertThat(query.getQuery())
.isEqualTo("SELECT COUNT(*) FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");

View File

@@ -199,6 +199,11 @@ public class BasicRelationalConverter implements RelationalConverter {
return conversionService.convert(value, type.getType());
}
@Override
public EntityInstantiators getEntityInstantiators() {
return this.entityInstantiators;
}
/**
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple JDBC type.
* Returns the converted value if so. If not, we perform special enum handling or simply return the value as is.

View File

@@ -22,6 +22,7 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -92,4 +93,12 @@ public interface RelationalConverter {
*/
@Nullable
Object writeValue(@Nullable Object value, TypeInformation<?> type);
/**
* Return the underlying {@link EntityInstantiators}.
*
* @return
* @since 2.3
*/
EntityInstantiators getEntityInstantiators();
}

View File

@@ -1,7 +1,7 @@
[[jdbc.repositories]]
= JDBC Repositories
This chapter points out the specialties for repository support for JDBC. This builds on the core repository support explained in <<repositories>>.
This chapter points out the specialties for repository support for JDBC.This builds on the core repository support explained in <<repositories>>.
You should have a sound understanding of the basic concepts explained there.
[[jdbc.why]]
@@ -696,6 +696,8 @@ You can specify the following return types:
* `int` (updated record count)
* `boolean`(whether a record was updated)
include::{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+2]
[[jdbc.mybatis]]
== MyBatis Integration