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:
committed by
Jens Schauder
parent
fc92bd5b60
commit
b3a2925573
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 //
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user