Refactor query method execution to use R2dbcEntityTemplate.

Repository query methods are now executed through R2dbcEntityTemplate to participate in entity callbacks.
Previously, query methods were executed directly using DatabaseClient which didn't allow for entity callbacks.

Closes #591
This commit is contained in:
Mark Paluch
2021-05-04 14:45:51 +02:00
parent 56ec512515
commit 673256199c
14 changed files with 705 additions and 413 deletions

View File

@@ -15,15 +15,23 @@
*/
package org.springframework.data.r2dbc.core;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.core.query.Update;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.RowsFetchSpec;
import org.springframework.util.Assert;
/**
* Interface specifying a basic set of reactive R2DBC operations using entities. Implemented by
@@ -95,7 +103,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
* Execute a {@code SELECT} query and convert the resulting items to a stream of entities.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @param entityClass the entity type must not be {@literal null}.
* @return the result objects returned by the action.
* @throws DataAccessException if there is any problem issuing the execution.
*/
@@ -105,7 +113,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
* Execute a {@code SELECT} query and convert the resulting item to an entity ensuring exactly one result.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @param entityClass the entity type must not be {@literal null}.
* @return exactly one result or {@link Mono#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
* @throws DataAccessException if there is any problem issuing the execution.
@@ -117,7 +125,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
*
* @param query must not be {@literal null}.
* @param update must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @param entityClass the entity type must not be {@literal null}.
* @return the number of affected rows.
* @throws DataAccessException if there is any problem executing the query.
*/
@@ -127,12 +135,105 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
* Remove entities (rows)/columns from the table by {@link Query}.
*
* @param query must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @param entityClass the entity type must not be {@literal null}.
* @return the number of affected rows.
* @throws DataAccessException if there is any problem issuing the execution.
*/
Mono<Integer> delete(Query query, Class<?> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.r2dbc.core.PreparedOperation
// -------------------------------------------------------------------------
/**
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
* additional pre-processing such as named parameter expansion. Results of the query are mapped onto
* {@code entityClass}.
*
* @param operation the prepared operation wrapping a SQL query and bind parameters.
* @param entityClass the entity type must not be {@literal null}.
* @return a {@link RowsFetchSpec} ready to materialize.
* @since 1.4
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<T> entityClass) throws DataAccessException;
/**
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
* additional pre-processing such as named parameter expansion. Results of the query are mapped using {@link Function
* rowMapper}.
*
* @param operation the prepared operation wrapping a SQL query and bind parameters.
* @param rowMapper the row mapper must not be {@literal null}.
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
* @throws DataAccessException if there is any problem issuing the execution.
* @since 1.4
* @see #query(PreparedOperation, BiFunction)
*/
default <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Function<Row, T> rowMapper)
throws DataAccessException {
Assert.notNull(rowMapper, "Row mapper must not be null");
return query(operation, ((row, rowMetadata) -> rowMapper.apply(row)));
}
/**
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
* additional pre-processing such as named parameter expansion. Results of the query are mapped using
* {@link BiFunction rowMapper}.
*
* @param operation the prepared operation wrapping a SQL query and bind parameters.
* @param rowMapper the row mapper must not be {@literal null}.
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
* @since 1.4
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, BiFunction<Row, RowMetadata, T> rowMapper)
throws DataAccessException;
/**
* Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}.
* Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The
* query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are
* mapped using {@link Function rowMapper}.
*
* @param operation the prepared operation wrapping a SQL query and bind parameters.
* @param entityClass the entity type must not be {@literal null}.
* @param rowMapper the row mapper must not be {@literal null}.
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
* @throws DataAccessException if there is any problem issuing the execution.
* @since 1.4
* @see #query(PreparedOperation, Class, BiFunction)
*/
default <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass, Function<Row, T> rowMapper)
throws DataAccessException {
Assert.notNull(rowMapper, "Row mapper must not be null");
return query(operation, entityClass, ((row, rowMetadata) -> rowMapper.apply(row)));
}
/**
* Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}.
* Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The
* query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are
* mapped using {@link BiFunction rowMapper}.
*
* @param operation the prepared operation wrapping a SQL query and bind parameters.
* @param entityClass the entity type must not be {@literal null}.
* @param rowMapper the row mapper must not be {@literal null}.
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
* @throws DataAccessException if there is any problem issuing the execution.
* @since 1.4
* @see #query(PreparedOperation, Class, BiFunction)
*/
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass,
BiFunction<Row, RowMetadata, T> rowMapper) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
@@ -140,7 +241,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
/**
* Insert the given entity and emit the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @param entity the entity to insert, must not be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem issuing the execution.
*/
@@ -149,7 +250,7 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
/**
* Update the given entity and emit the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @param entity the entity to update, must not be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem issuing the execution.
* @throws TransientDataAccessResourceException if the update did not affect any rows.

View File

@@ -307,7 +307,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
public Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "entity class must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
return doCount(query, entityClass, getTableName(entityClass));
}
@@ -344,7 +344,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
public Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "entity class must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
return doExists(query, entityClass, getTableName(entityClass));
}
@@ -383,7 +383,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
public <T> Flux<T> select(Query query, Class<T> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "entity class must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
SqlIdentifier tableName = getTableName(entityClass);
return doSelect(query, entityClass, tableName, entityClass, RowsFetchSpec::all);
@@ -432,24 +432,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
boolean simpleType;
BiFunction<Row, RowMetadata, T> rowMapper;
if (returnType.isInterface()) {
simpleType = getConverter().isSimpleType(entityClass);
rowMapper = dataAccessStrategy.getRowMapper(entityClass)
.andThen(o -> projectionFactory.createProjection(returnType, o));
} else {
simpleType = getConverter().isSimpleType(returnType);
rowMapper = dataAccessStrategy.getRowMapper(returnType);
}
// avoid top-level null values if the read type is a simple one (e.g. SELECT MAX(age) via Integer.class)
if (simpleType) {
return new UnwrapOptionalFetchSpecAdapter<>(this.databaseClient.sql(operation)
.map((row, metadata) -> Optional.ofNullable(rowMapper.apply(row, metadata))));
}
return this.databaseClient.sql(operation).map(rowMapper);
return getRowsFetchSpec(databaseClient.sql(operation), entityClass, returnType);
}
/*
@@ -470,7 +453,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
Assert.notNull(query, "Query must not be null");
Assert.notNull(update, "Update must not be null");
Assert.notNull(entityClass, "entity class must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
return doUpdate(query, update, entityClass, getTableName(entityClass));
}
@@ -499,7 +482,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
public Mono<Integer> delete(Query query, Class<?> entityClass) throws DataAccessException {
Assert.notNull(query, "Query must not be null");
Assert.notNull(entityClass, "entity class must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
return doDelete(query, entityClass, getTableName(entityClass));
}
@@ -508,18 +491,64 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
StatementMapper.DeleteSpec selectSpec = statementMapper //
StatementMapper.DeleteSpec deleteSpec = statementMapper //
.createDelete(tableName);
Optional<CriteriaDefinition> criteria = query.getCriteria();
if (criteria.isPresent()) {
selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec);
deleteSpec = criteria.map(deleteSpec::withCriteria).orElse(deleteSpec);
}
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
PreparedOperation<?> operation = statementMapper.getMappedObject(deleteSpec);
return this.databaseClient.sql(operation).fetch().rowsUpdated().defaultIfEmpty(0);
}
// -------------------------------------------------------------------------
// Methods dealing with org.springframework.r2dbc.core.PreparedOperation
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class)
*/
@Override
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<T> entityClass) {
Assert.notNull(operation, "PreparedOperation must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
return new EntityCallbackAdapter<>(getRowsFetchSpec(databaseClient.sql(operation), entityClass, entityClass),
getTableNameOrEmpty(entityClass));
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.util.function.BiFunction)
*/
@Override
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, BiFunction<Row, RowMetadata, T> rowMapper) {
Assert.notNull(operation, "PreparedOperation must not be null");
Assert.notNull(rowMapper, "Row mapper must not be null");
return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), SqlIdentifier.EMPTY);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class, java.util.function.BiFunction)
*/
@Override
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass,
BiFunction<Row, RowMetadata, T> rowMapper) {
Assert.notNull(operation, "PreparedOperation must not be null");
Assert.notNull(entityClass, "Entity class must not be null");
Assert.notNull(rowMapper, "Row mapper must not be null");
return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), getTableNameOrEmpty(entityClass));
}
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
@@ -817,6 +846,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
return getRequiredEntity(entityClass).getTableName();
}
SqlIdentifier getTableNameOrEmpty(Class<?> entityClass) {
RelationalPersistentEntity<?> entity = this.mappingContext.getPersistentEntity(entityClass);
return entity != null ? entity.getTableName() : SqlIdentifier.EMPTY;
}
private RelationalPersistentEntity<?> getRequiredEntity(Class<?> entityClass) {
return this.mappingContext.getRequiredPersistentEntity(entityClass);
}
@@ -846,6 +882,30 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
return query.getColumns().stream().map(table::column).collect(Collectors.toList());
}
private <T> RowsFetchSpec<T> getRowsFetchSpec(DatabaseClient.GenericExecuteSpec executeSpec, Class<?> entityClass,
Class<T> returnType) {
boolean simpleType;
BiFunction<Row, RowMetadata, T> rowMapper;
if (returnType.isInterface()) {
simpleType = getConverter().isSimpleType(entityClass);
rowMapper = dataAccessStrategy.getRowMapper(entityClass)
.andThen(o -> projectionFactory.createProjection(returnType, o));
} else {
simpleType = getConverter().isSimpleType(returnType);
rowMapper = dataAccessStrategy.getRowMapper(returnType);
}
// avoid top-level null values if the read type is a simple one (e.g. SELECT MAX(age) via Integer.class)
if (simpleType) {
return new UnwrapOptionalFetchSpecAdapter<>(
executeSpec.map((row, metadata) -> Optional.ofNullable(rowMapper.apply(row, metadata))));
}
return executeSpec.map(rowMapper);
}
private static ReactiveDataAccessStrategy getDataAccessStrategy(
org.springframework.data.r2dbc.core.DatabaseClient databaseClient) {
@@ -989,6 +1049,11 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
}
}
/**
* {@link RowsFetchSpec} adapter emitting values from {@link Optional} if they exist.
*
* @param <T>
*/
private static class UnwrapOptionalFetchSpecAdapter<T> implements RowsFetchSpec<T> {
private final RowsFetchSpec<Optional<T>> delegate;
@@ -1012,4 +1077,37 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
return delegate.all().handle((optional, sink) -> optional.ifPresent(sink::next));
}
}
/**
* {@link RowsFetchSpec} adapter applying {@link #maybeCallAfterConvert(Object, SqlIdentifier)} to each emitted
* object.
*
* @param <T>
*/
private class EntityCallbackAdapter<T> implements RowsFetchSpec<T> {
private final RowsFetchSpec<T> delegate;
private final SqlIdentifier tableName;
private EntityCallbackAdapter(RowsFetchSpec<T> delegate, SqlIdentifier tableName) {
this.delegate = delegate;
this.tableName = tableName;
}
@Override
public Mono<T> one() {
return delegate.one().flatMap(it -> maybeCallAfterConvert(it, tableName));
}
@Override
public Mono<T> first() {
return delegate.first().flatMap(it -> maybeCallAfterConvert(it, tableName));
}
@Override
public Flux<T> all() {
return delegate.all().flatMap(it -> maybeCallAfterConvert(it, tableName));
}
}
}

View File

@@ -71,6 +71,21 @@ public class SettableValue {
return new SettableValue(Parameter.empty(type));
}
/**
* Factory method to create a {@link SettableValue} from {@link Parameter}. Retains empty/type information.
*
* @param parameter the parameter to create a {@link SettableValue} from.
* @return a new {@link SettableValue} from {@link Parameter}.
* @since 1.4
*/
public static SettableValue fromParameter(Parameter parameter) {
Assert.notNull(parameter, "Parameter must not be null");
return parameter.isEmpty() ? SettableValue.empty(parameter.getType())
: SettableValue.fromOrEmpty(parameter.getValue(), parameter.getType());
}
/**
* Returns the column value. Can be {@literal null}.
*

View File

@@ -15,19 +15,15 @@
*/
package org.springframework.data.r2dbc.repository.query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Optional;
import org.reactivestreams.Publisher;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.r2dbc.convert.EntityRowMapper;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter;
import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.ParameterAccessor;
@@ -35,8 +31,8 @@ import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.FetchSpec;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.RowsFetchSpec;
import org.springframework.util.Assert;
@@ -49,25 +45,26 @@ import org.springframework.util.Assert;
public abstract class AbstractR2dbcQuery implements RepositoryQuery {
private final R2dbcQueryMethod method;
private final DatabaseClient databaseClient;
private final R2dbcEntityOperations entityOperations;
private final R2dbcConverter converter;
private final EntityInstantiators instantiators;
/**
* Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link DatabaseClient}.
* Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link R2dbcEntityOperations}.
*
* @param method must not be {@literal null}.
* @param databaseClient must not be {@literal null}.
* @param entityOperations must not be {@literal null}.
* @param converter must not be {@literal null}.
* @since 1.4
*/
public AbstractR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter) {
public AbstractR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter) {
Assert.notNull(method, "R2dbcQueryMethod must not be null!");
Assert.notNull(databaseClient, "DatabaseClient must not be null!");
Assert.notNull(entityOperations, "R2dbcEntityOperations must not be null!");
Assert.notNull(converter, "R2dbcConverter must not be null!");
this.method = method;
this.databaseClient = databaseClient;
this.entityOperations = entityOperations;
this.converter = converter;
this.instantiators = new EntityInstantiators();
}
@@ -91,38 +88,25 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
return createQuery(parameterAccessor).flatMapMany(it -> executeQuery(parameterAccessor, it));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Publisher<?> executeQuery(RelationalParameterAccessor parameterAccessor, BindableQuery it) {
@SuppressWarnings("unchecked")
private Publisher<?> executeQuery(RelationalParameterAccessor parameterAccessor, PreparedOperation<?> operation) {
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
DatabaseClient.GenericExecuteSpec boundQuery = it.bind(databaseClient.sql(it));
FetchSpec<Object> fetchSpec;
RowsFetchSpec<?> fetchSpec;
if (isExistsQuery()) {
fetchSpec = (FetchSpec) boundQuery.map(row -> true);
} else if (requiresMapping()) {
Class<?> typeToRead = resolveResultType(processor);
EntityRowMapper rowMapper = new EntityRowMapper<>(typeToRead, converter);
if (converter.isSimpleType(typeToRead)) {
fetchSpec = new UnwrapOptionalFetchSpecAdapter<>(
boundQuery.map((row, rowMetadata) -> Optional.ofNullable(rowMapper.apply(row, rowMetadata))));
} else {
fetchSpec = new FetchSpecAdapter<>(boundQuery.map(rowMapper));
}
if (isModifyingQuery()) {
fetchSpec = entityOperations.getDatabaseClient().sql(operation).fetch();
} else if (isExistsQuery()) {
fetchSpec = entityOperations.getDatabaseClient().sql(operation).map(row -> true);
} else {
fetchSpec = (FetchSpec) boundQuery.fetch();
fetchSpec = entityOperations.query(operation, resolveResultType(processor));
}
SqlIdentifier tableName = method.getEntityInformation().getTableName();
R2dbcQueryExecution execution = new ResultProcessingExecution(getExecutionToWrap(processor.getReturnedType()),
new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators));
return execution.execute(fetchSpec, processor.getReturnedType().getDomainType(), tableName);
return execution.execute(RowsFetchSpec.class.cast(fetchSpec));
}
Class<?> resolveResultType(ResultProcessor resultProcessor) {
@@ -136,45 +120,47 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType();
}
private boolean requiresMapping() {
return !isModifyingQuery();
}
private R2dbcQueryExecution getExecutionToWrap(ReturnedType returnedType) {
if (isModifyingQuery()) {
if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) {
return (q, t, c) -> q.rowsUpdated().map(integer -> integer > 0);
return fetchSpec -> {
Assert.isInstanceOf(FetchSpec.class, fetchSpec);
FetchSpec<?> fs = (FetchSpec<?>) fetchSpec;
if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) {
return fs.rowsUpdated().map(integer -> integer > 0);
}
if (Number.class.isAssignableFrom(returnedType.getReturnedType())) {
return (q, t, c) -> q.rowsUpdated().map(integer -> {
return converter.getConversionService().convert(integer, returnedType.getReturnedType());
});
return fs.rowsUpdated()
.map(integer -> converter.getConversionService().convert(integer, returnedType.getReturnedType()));
}
if (ReflectionUtils.isVoid(returnedType.getReturnedType())) {
return (q, t, c) -> q.rowsUpdated().then();
return fs.rowsUpdated().then();
}
return (q, t, c) -> q.rowsUpdated();
return fs.rowsUpdated();
};
}
if (isCountQuery()) {
return (q, t, c) -> q.first().defaultIfEmpty(0L);
return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(0L);
}
if (isExistsQuery()) {
return (q, t, c) -> q.first().defaultIfEmpty(false);
return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(false);
}
if (method.isCollectionQuery()) {
return (q, t, c) -> q.all();
return RowsFetchSpec::all;
}
return (q, t, c) -> q.one();
return RowsFetchSpec::one;
}
/**
@@ -207,63 +193,6 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
* @param accessor must not be {@literal null}.
* @return a mono emitting a {@link BindableQuery}.
*/
protected abstract Mono<BindableQuery> createQuery(RelationalParameterAccessor accessor);
protected abstract Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor accessor);
private static class FetchSpecAdapter<T> implements FetchSpec<T> {
private final RowsFetchSpec<T> delegate;
private FetchSpecAdapter(RowsFetchSpec<T> delegate) {
this.delegate = delegate;
}
@Override
public Mono<T> one() {
return delegate.one();
}
@Override
public Mono<T> first() {
return delegate.first();
}
@Override
public Flux<T> all() {
return delegate.all();
}
@Override
public Mono<Integer> rowsUpdated() {
throw new UnsupportedOperationException("Not supported after applying a row mapper");
}
}
private static class UnwrapOptionalFetchSpecAdapter<T> implements FetchSpec<T> {
private final RowsFetchSpec<Optional<T>> delegate;
private UnwrapOptionalFetchSpecAdapter(RowsFetchSpec<Optional<T>> delegate) {
this.delegate = delegate;
}
@Override
public Mono<T> one() {
return delegate.one().handle((optional, sink) -> optional.ifPresent(sink::next));
}
@Override
public Mono<T> first() {
return delegate.first().handle((optional, sink) -> optional.ifPresent(sink::next));
}
@Override
public Flux<T> all() {
return delegate.all().handle((optional, sink) -> optional.ifPresent(sink::next));
}
@Override
public Mono<Integer> rowsUpdated() {
throw new UnsupportedOperationException("Not supported after applying a row mapper");
}
}
}

View File

@@ -26,7 +26,7 @@ import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.binding.BindTarget;
/**
* {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a
@@ -58,42 +58,35 @@ class ExpressionEvaluatingParameterBinder {
* Bind values provided by {@link RelationalParameterAccessor} to placeholders in {@link ExpressionQuery} while
* considering potential conversions and parameter types.
*
* @param bindSpec must not be {@literal null}.
* @param bindTarget must not be {@literal null}.
* @param parameterAccessor must not be {@literal null}.
* @param evaluator must not be {@literal null}.
*/
DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec,
void bind(BindTarget bindTarget,
RelationalParameterAccessor parameterAccessor, R2dbcSpELExpressionEvaluator evaluator) {
Object[] values = parameterAccessor.getValues();
Parameters<?, ?> bindableParameters = parameterAccessor.getBindableParameters();
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindExpressions(bindSpec, evaluator);
bindSpecToUse = bindParameters(bindSpecToUse, parameterAccessor.hasBindableNullValue(), values, bindableParameters);
return bindSpecToUse;
bindExpressions(bindTarget, evaluator);
bindParameters(bindTarget, parameterAccessor.hasBindableNullValue(), values, bindableParameters);
}
private DatabaseClient.GenericExecuteSpec bindExpressions(DatabaseClient.GenericExecuteSpec bindSpec,
private void bindExpressions(BindTarget bindSpec,
R2dbcSpELExpressionEvaluator evaluator) {
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec;
for (ParameterBinding binding : expressionQuery.getBindings()) {
org.springframework.r2dbc.core.Parameter valueForBinding = getBindValue(
evaluator.evaluate(binding.getExpression()));
bindSpecToUse = bind(bindSpecToUse, binding.getParameterName(), valueForBinding);
bind(bindSpec, binding.getParameterName(), valueForBinding);
}
return bindSpecToUse;
}
private DatabaseClient.GenericExecuteSpec bindParameters(DatabaseClient.GenericExecuteSpec bindSpec,
private void bindParameters(BindTarget bindSpec,
boolean hasBindableNullValue, Object[] values, Parameters<?, ?> bindableParameters) {
DatabaseClient.GenericExecuteSpec bindSpecToUse = bindSpec;
int bindingIndex = 0;
for (Parameter bindableParameter : bindableParameters) {
@@ -109,7 +102,7 @@ class ExpressionEvaluatingParameterBinder {
org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter);
if (!parameter.isEmpty() || hasBindableNullValue) {
bindSpecToUse = bind(bindSpecToUse, name.get(), parameter);
bind(bindSpec, name.get(), parameter);
}
// skip unused named parameters if there is SpEL
@@ -118,12 +111,10 @@ class ExpressionEvaluatingParameterBinder {
org.springframework.r2dbc.core.Parameter parameter = getBindValue(values, bindableParameter);
if (!parameter.isEmpty() || hasBindableNullValue) {
bindSpecToUse = bind(bindSpecToUse, bindingIndex++, parameter);
bind(bindSpec, bindingIndex++, parameter);
}
}
}
return bindSpecToUse;
}
private org.springframework.r2dbc.core.Parameter getBindValue(Object[] values, Parameter bindableParameter) {
@@ -134,26 +125,25 @@ class ExpressionEvaluatingParameterBinder {
return dataAccessStrategy.getBindValue(parameter);
}
private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, String name,
private static void bind(BindTarget spec, String name,
org.springframework.r2dbc.core.Parameter parameter) {
Object value = parameter.getValue();
if (value == null) {
return spec.bindNull(name, parameter.getType());
spec.bindNull(name, parameter.getType());
} else {
return spec.bind(name, value);
spec.bind(name, value);
}
}
private static DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec spec, int index,
private static void bind(BindTarget spec, int index,
org.springframework.r2dbc.core.Parameter parameter) {
Object value = parameter.getValue();
if (value == null) {
return spec.bindNull(index, parameter.getType());
spec.bindNull(index, parameter.getType());
} else {
return spec.bind(index, value);
spec.bind(index, value);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
@@ -52,13 +53,13 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
* {@link R2dbcConverter} and {@link ReactiveDataAccessStrategy}.
*
* @param method query method, must not be {@literal null}.
* @param databaseClient database client, must not be {@literal null}.
* @param entityOperations entity operations, must not be {@literal null}.
* @param converter converter, must not be {@literal null}.
* @param dataAccessStrategy data access strategy, must not be {@literal null}.
*/
public PartTreeR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter,
public PartTreeR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy) {
super(method, databaseClient, converter);
super(method, entityOperations, converter);
this.processor = method.getResultProcessor();
this.dataAccessStrategy = dataAccessStrategy;
@@ -105,7 +106,7 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
* @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor)
*/
@Override
protected Mono<BindableQuery> createQuery(RelationalParameterAccessor accessor) {
protected Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor accessor) {
return Mono.fromSupplier(() -> {
@@ -119,13 +120,20 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
projectedProperties);
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
return new PreparedOperationBindableQuery(preparedQuery);
return queryCreator.createQuery(getDynamicSort(accessor));
});
}
private Sort getDynamicSort(RelationalParameterAccessor accessor) {
return parameters.potentiallySortsDynamically() ? accessor.getSort() : Sort.unsorted();
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [").append(getQueryMethod().getName());
sb.append(']');
return sb.toString();
}
}

View File

@@ -59,7 +59,7 @@ class PreparedOperationBindableQuery implements BindableQuery {
* This class adapts {@link DatabaseClient.GenericExecuteSpec} to {@link BindTarget} allowing easy binding of query
* parameters using {@link PreparedOperation}.
*/
private static class BindSpecBindTargetAdapter implements BindTarget {
static class BindSpecBindTargetAdapter implements BindTarget {
DatabaseClient.GenericExecuteSpec bindSpec;

View File

@@ -25,12 +25,11 @@ 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.core.sql.SqlIdentifier;
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.ReflectionUtils;
import org.springframework.r2dbc.core.FetchSpec;
import org.springframework.r2dbc.core.RowsFetchSpec;
import org.springframework.util.ClassUtils;
/**
@@ -41,7 +40,7 @@ import org.springframework.util.ClassUtils;
*/
interface R2dbcQueryExecution {
Publisher<?> execute(FetchSpec<Object> query, Class<?> type, SqlIdentifier tableName);
Publisher<?> execute(RowsFetchSpec<Object> fetchSpec);
/**
* An {@link R2dbcQueryExecution} that wraps the results of the given delegate with the given result processing.
@@ -57,11 +56,11 @@ interface R2dbcQueryExecution {
}
/* (non-Javadoc)
* @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec, java.lang.Class, java.lang.String)
* @see org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution#execute(org.springframework.data.r2dbc.function.FetchSpec)
*/
@Override
public Publisher<Object> execute(FetchSpec<Object> query, Class<?> type, SqlIdentifier tableName) {
return (Publisher<Object>) this.converter.convert(this.delegate.execute(query, type, tableName));
public Publisher<Object> execute(RowsFetchSpec<Object> fetchSpec) {
return (Publisher<Object>) this.converter.convert(this.delegate.execute(fetchSpec));
}
}

View File

@@ -18,10 +18,16 @@ package org.springframework.data.r2dbc.repository.query;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
@@ -31,6 +37,9 @@ import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.binding.BindTarget;
import org.springframework.util.Assert;
/**
@@ -48,24 +57,24 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
private final ExpressionParser expressionParser;
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
private final ExpressionDependencies expressionDependencies;
private final ReactiveDataAccessStrategy dataAccessStrategy;
/**
* Creates a new {@link StringBasedR2dbcQuery} for the given {@link StringBasedR2dbcQuery}, {@link DatabaseClient},
* {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
*
* @param queryMethod must not be {@literal null}.
* @param databaseClient must not be {@literal null}.
* @param entityOperations must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param dataAccessStrategy must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, DatabaseClient databaseClient, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy,
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, databaseClient, converter, dataAccessStrategy,
expressionParser,
evaluationContextProvider);
public StringBasedR2dbcQuery(R2dbcQueryMethod queryMethod, R2dbcEntityOperations entityOperations,
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, entityOperations, converter, dataAccessStrategy,
expressionParser, evaluationContextProvider);
}
/**
@@ -73,22 +82,23 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
* {@link DatabaseClient}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
*
* @param method must not be {@literal null}.
* @param databaseClient must not be {@literal null}.
* @param entityOperations must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param dataAccessStrategy must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, DatabaseClient databaseClient,
public StringBasedR2dbcQuery(String query, R2dbcQueryMethod method, R2dbcEntityOperations entityOperations,
R2dbcConverter converter, ReactiveDataAccessStrategy dataAccessStrategy, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
super(method, databaseClient, converter);
super(method, entityOperations, converter);
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
Assert.hasText(query, "Query must not be empty");
this.dataAccessStrategy = dataAccessStrategy;
this.expressionQuery = ExpressionQuery.create(query);
this.binder = new ExpressionEvaluatingParameterBinder(expressionQuery, dataAccessStrategy);
this.expressionDependencies = createExpressionDependencies();
@@ -141,20 +151,8 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
* @see org.springframework.data.r2dbc.repository.query.AbstractR2dbcQuery#createQuery(org.springframework.data.relational.repository.query.RelationalParameterAccessor)
*/
@Override
protected Mono<BindableQuery> createQuery(RelationalParameterAccessor accessor) {
return getSpelEvaluator(accessor).map(evaluator -> new BindableQuery() {
@Override
public DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec) {
return binder.bind(bindSpec, accessor, evaluator);
}
@Override
public String get() {
return expressionQuery.getQuery();
}
});
protected Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor accessor) {
return getSpelEvaluator(accessor).map(evaluator -> new ExpandedQuery(accessor, evaluator));
}
@Override
@@ -172,4 +170,108 @@ public class StringBasedR2dbcQuery extends AbstractR2dbcQuery {
context -> new DefaultR2dbcSpELExpressionEvaluator(expressionParser, context))
.defaultIfEmpty(DefaultR2dbcSpELExpressionEvaluator.unsupported());
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [").append(expressionQuery.getQuery());
sb.append(']');
return sb.toString();
}
private class ExpandedQuery implements PreparedOperation<String> {
private final BindTargetRecorder recordedBindings;
private final PreparedOperation<?> expanded;
private final Map<String, Parameter> remainderByName;
private final Map<Integer, Parameter> remainderByIndex;
public ExpandedQuery(RelationalParameterAccessor accessor, R2dbcSpELExpressionEvaluator evaluator) {
this.recordedBindings = new BindTargetRecorder();
binder.bind(recordedBindings, accessor, evaluator);
remainderByName = new LinkedHashMap<>(recordedBindings.byName);
remainderByIndex = new LinkedHashMap<>(recordedBindings.byIndex);
expanded = dataAccessStrategy.processNamedParameters(expressionQuery.getQuery(), (index, name) -> {
if (recordedBindings.byName.containsKey(name)) {
remainderByName.remove(name);
return SettableValue.fromParameter(recordedBindings.byName.get(name));
}
if (recordedBindings.byIndex.containsKey(index)) {
remainderByIndex.remove(index);
return SettableValue.fromParameter(recordedBindings.byIndex.get(index));
}
return null;
});
}
@Override
public String getSource() {
return expressionQuery.getQuery();
}
@Override
public void bindTo(BindTarget target) {
expanded.bindTo(target);
remainderByName.forEach(target::bind);
remainderByIndex.forEach(target::bind);
}
@Override
public String toQuery() {
return expanded.toQuery();
}
@Override
public String toString() {
return String.format("Original: [%s], Expanded: [%s]", expressionQuery.getQuery(), expanded.toQuery());
}
}
private static class BindTargetRecorder implements BindTarget {
final Map<Integer, Parameter> byIndex = new LinkedHashMap<>();
final Map<String, Parameter> byName = new LinkedHashMap<>();
@Override
public void bind(String identifier, Object value) {
byName.put(identifier, toParameter(value));
}
@NotNull
private Parameter toParameter(Object value) {
if (value instanceof SettableValue) {
return ((SettableValue) value).toParameter();
}
return value instanceof Parameter ? (Parameter) value : Parameter.from(value);
}
@Override
public void bind(int index, Object value) {
byIndex.put(index, toParameter(value));
}
@Override
public void bindNull(String identifier, Class<?> type) {
byName.put(identifier, Parameter.empty(type));
}
@Override
public void bindNull(int index, Class<?> type) {
byIndex.put(index, Parameter.empty(type));
}
}
}

View File

@@ -129,7 +129,7 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(new R2dbcQueryLookupStrategy(this.databaseClient,
return Optional.of(new R2dbcQueryLookupStrategy(this.operations,
(ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, this.converter,
this.dataAccessStrategy));
}
@@ -158,16 +158,16 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
*/
private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy {
private final DatabaseClient databaseClient;
private final R2dbcEntityOperations entityOperations;
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
private final R2dbcConverter converter;
private final ReactiveDataAccessStrategy dataAccessStrategy;
private final ExpressionParser parser = new CachingExpressionParser(EXPRESSION_PARSER);
R2dbcQueryLookupStrategy(DatabaseClient databaseClient,
R2dbcQueryLookupStrategy(R2dbcEntityOperations entityOperations,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy) {
this.databaseClient = databaseClient;
this.entityOperations = entityOperations;
this.evaluationContextProvider = evaluationContextProvider;
this.converter = converter;
this.dataAccessStrategy = dataAccessStrategy;
@@ -188,15 +188,15 @@ public class R2dbcRepositoryFactory extends ReactiveRepositoryFactorySupport {
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.databaseClient, this.converter,
return new StringBasedR2dbcQuery(namedQuery, queryMethod, this.entityOperations, this.converter,
this.dataAccessStrategy,
parser, this.evaluationContextProvider);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy,
return new StringBasedR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy,
this.parser,
this.evaluationContextProvider);
} else {
return new PartTreeR2dbcQuery(queryMethod, this.databaseClient, this.converter, this.dataAccessStrategy);
return new PartTreeR2dbcQuery(queryMethod, this.entityOperations, this.converter, this.dataAccessStrategy);
}
}
}

View File

@@ -129,7 +129,7 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
}
@Test
void shouldFindItemsByNameLike() {
void shouldFindItemsByNameContains() {
shouldInsertNewItems();

View File

@@ -25,12 +25,16 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -39,10 +43,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
import org.springframework.data.r2dbc.testing.H2TestSupport;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -59,6 +65,7 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
@Autowired private H2LegoSetRepository repository;
@Autowired private IdOnlyEntityRepository idOnlyEntityRepository;
@Autowired private AfterConvertCallbackRecorder recorder;
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
@@ -70,6 +77,16 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
public ConnectionFactory connectionFactory() {
return H2TestSupport.createConnectionFactory();
}
@Bean
public AfterConvertCallbackRecorder afterConvertCallbackRecorder() {
return new AfterConvertCallbackRecorder();
}
}
@BeforeEach
void setUp() {
recorder.clear();
}
@Override
@@ -92,6 +109,18 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
return H2LegoSetRepository.class;
}
@Test // gh-591
void shouldFindItemsByManual() {
super.shouldFindItemsByManual();
assertThat(recorder.seenEntities).hasSize(1);
}
@Test // gh-591
void shouldFindItemsByNameContains() {
super.shouldFindItemsByNameContains();
assertThat(recorder.seenEntities).hasSize(2);
}
@Test // gh-469
void shouldSuppressNullValues() {
repository.findMax("doo").as(StepVerifier::create).verifyComplete();
@@ -196,4 +225,19 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
static class IdOnlyEntity {
@Id Integer id;
}
static class AfterConvertCallbackRecorder implements AfterConvertCallback<LegoSet> {
List<LegoSet> seenEntities = new ArrayList<>();
@Override
public Publisher<LegoSet> onAfterConvert(LegoSet entity, SqlIdentifier table) {
seenEntities.add(entity);
return Mono.just(entity);
}
public void clear() {
seenEntities.clear();
}
}
}

View File

@@ -42,6 +42,8 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.dialect.DialectResolver;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
@@ -52,6 +54,8 @@ import org.springframework.data.relational.repository.query.RelationalParameters
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.binding.BindTarget;
/**
* Unit tests for {@link PartTreeR2dbcQuery}.
@@ -75,7 +79,7 @@ class PartTreeR2dbcQueryUnitTests {
private RelationalMappingContext mappingContext;
private ReactiveDataAccessStrategy dataAccessStrategy;
private DatabaseClient databaseClient;
private R2dbcEntityOperations operations;
@BeforeEach
void setUp() {
@@ -92,18 +96,19 @@ class PartTreeR2dbcQueryUnitTests {
R2dbcDialect dialect = DialectResolver.getDialect(connectionFactory);
dataAccessStrategy = new DefaultReactiveDataAccessStrategy(dialect, r2dbcConverter);
databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory).build();
operations = new R2dbcEntityTemplate(DatabaseClient.builder().connectionFactory(connectionFactory).build(),
dataAccessStrategy);
}
@Test // gh-282
void createsQueryToFindAllEntitiesByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John");
PreparedOperation<?> preparedOperation = createQuery(queryMethod, r2dbcQuery, "John");
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@@ -111,11 +116,11 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryWithIsNullCondition() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, new Object[] { null });
PreparedOperation<?> preparedOperation = createQuery(queryMethod, r2dbcQuery, new Object[] { null });
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name IS NULL");
}
@@ -123,9 +128,9 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryWithLimitForExistsProjection() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("existsByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John");
PreparedOperation<?> query = createQuery(queryMethod, r2dbcQuery, "John");
assertThat(query.get())
.isEqualTo("SELECT " + TABLE + ".id FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1");
@@ -135,11 +140,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByTwoStringAttributes() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameAndFirstName", String.class, String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" }));
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery,
getAccessor(queryMethod, new Object[] { "Doe", "John" }));
assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
assertThat(preparedOperation.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
+ ".last_name = $1 AND (" + TABLE + ".first_name = $2)");
}
@@ -147,11 +153,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByOneOfTwoStringAttributes() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameOrFirstName", String.class, String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(r2dbcQuery, getAccessor(queryMethod, new Object[] { "Doe", "John" }));
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery,
getAccessor(queryMethod, new Object[] { "Doe", "John" }));
assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
assertThat(preparedOperation.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
+ ".last_name = $1 OR (" + TABLE + ".first_name = $2)");
}
@@ -159,34 +166,33 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByDateAttributeBetween() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBetween", Date.class, Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
Date from = new Date();
Date to = new Date();
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2");
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
when(bindSpecMock.bind(anyInt(), any())).thenReturn(bindSpecMock);
bindableQuery.bind(bindSpecMock);
BindTarget bindTarget = mock(BindTarget.class);
preparedOperation.bindTo(bindTarget);
verify(bindSpecMock, times(1)).bind(0, from);
verify(bindSpecMock, times(1)).bind(1, to);
verify(bindTarget, times(1)).bind(0, from);
verify(bindTarget, times(1)).bind(1, to);
}
@Test // gh-282
void createsQueryToFindAllEntitiesByIntegerAttributeLessThan() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThan", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age < $1");
}
@@ -194,12 +200,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeLessThanEqual() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThanEqual", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age <= $1");
}
@@ -207,12 +213,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThan() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThan", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age > $1");
}
@@ -220,12 +226,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThanEqual() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThanEqual", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age >= $1");
}
@@ -233,24 +239,24 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByDateAttributeAfter() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthAfter", Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth > $1");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByDateAttributeBefore() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBefore", Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth < $1");
}
@@ -258,12 +264,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeIsNull() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNull");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NULL");
}
@@ -271,12 +277,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeIsNotNull() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNotNull");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NOT NULL");
}
@@ -284,12 +290,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByStringAttributeLike() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameLike", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@@ -297,12 +303,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByStringAttributeNotLike() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotLike", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1");
}
@@ -310,148 +316,144 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByStringAttributeStartingWith() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test // gh-282
void appendsLikeOperatorParameterWithPercentSymbolForStartingWithQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
bindableQuery.bind(bindSpecMock);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
BindTarget bindTarget = mock(BindTarget.class);
preparedOperation.bindTo(bindTarget);
verify(bindSpecMock, times(1)).bind(0, "Jo%");
verify(bindTarget, times(1)).bind(0, "Jo%");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByStringAttributeEndingWith() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test // gh-282
void prependsLikeOperatorParameterWithPercentSymbolForEndingWithQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
bindableQuery.bind(bindSpecMock);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
BindTarget bindTarget = mock(BindTarget.class);
preparedOperation.bindTo(bindTarget);
verify(bindSpecMock, times(1)).bind(0, "%hn");
verify(bindTarget, times(1)).bind(0, "%hn");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByStringAttributeContaining() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test // gh-282
void wrapsLikeOperatorParameterWithPercentSymbolsForContainingQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
bindableQuery.bind(bindSpecMock);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
BindTarget bindTarget = mock(BindTarget.class);
preparedOperation.bindTo(bindTarget);
verify(bindSpecMock, times(1)).bind(0, "%oh%");
verify(bindTarget, times(1)).bind(0, "%oh%");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByStringAttributeNotContaining() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test // gh-282
void wrapsLikeOperatorParameterWithPercentSymbolsForNotContainingQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
bindableQuery.bind(bindSpecMock);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
BindTarget bindTarget = mock(BindTarget.class);
preparedOperation.bindTo(bindTarget);
verify(bindSpecMock, times(1)).bind(0, "%oh%");
verify(bindTarget, times(1)).bind(0, "%oh%");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByIntegerAttributeWithDescendingOrderingByStringAttribute()
throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameDesc", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name DESC");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByIntegerAttributeWithAscendingOrderingByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameAsc", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name ASC");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByStringAttributeNot() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameNot", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name != $1");
}
@@ -459,26 +461,26 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByIntegerAttributeIn() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIn", Collection.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IN ($1)");
}
@Test // gh-282
void createsQueryToFindAllEntitiesByIntegerAttributeNotIn() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeNotIn", Collection.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age NOT IN ($1)");
}
@@ -486,12 +488,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByBooleanAttributeTrue() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveTrue");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = TRUE");
}
@@ -499,12 +501,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByBooleanAttributeFalse() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveFalse");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = FALSE");
}
@@ -512,12 +514,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindAllEntitiesByStringAttributeIgnoringCase() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameIgnoreCase", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE UPPER(" + TABLE + ".first_name) = UPPER($1)");
}
@@ -525,7 +527,7 @@ class PartTreeR2dbcQueryUnitTests {
void throwsExceptionWhenIgnoringCaseIsImpossible() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findByIdIgnoringCase", Long.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
assertThatIllegalStateException()
@@ -538,7 +540,7 @@ class PartTreeR2dbcQueryUnitTests {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIn", Long.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy));
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy));
}
@Test // gh-282
@@ -547,14 +549,14 @@ class PartTreeR2dbcQueryUnitTests {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllById", Collection.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy));
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter, dataAccessStrategy));
}
@Test // gh-282
void throwsExceptionWhenConditionKeywordIsUnsupported() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIsEmpty");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
assertThatIllegalArgumentException()
@@ -565,7 +567,7 @@ class PartTreeR2dbcQueryUnitTests {
void throwsExceptionWhenInvalidNumberOfParameterIsGiven() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
assertThatIllegalArgumentException()
@@ -576,12 +578,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryWithLimitToFindEntitiesByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findTop3ByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 3");
}
@@ -589,12 +591,12 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindFirstEntityByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findFirstByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get())
assertThat(preparedOperation.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1");
}
@@ -602,23 +604,23 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToDeleteByFirstName() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("deleteByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = createQuery(r2dbcQuery, accessor);
PreparedOperation<?> preparedOperation = createQuery(r2dbcQuery, accessor);
assertThat(bindableQuery.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
assertThat(preparedOperation.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@Test // gh-344
void createsQueryToFindAllEntitiesByStringAttributeWithDistinct() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findDistinctByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery, "John");
PreparedOperation<?> preparedOperation = createQuery(queryMethod, r2dbcQuery, "John");
assertThat(bindableQuery.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE
assertThat(preparedOperation.get()).isEqualTo("SELECT " + DISTINCT + " " + TABLE + ".first_name, " + TABLE
+ ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@@ -626,11 +628,11 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryToFindByOpenProjection() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findOpenProjectionBy");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery);
PreparedOperation<?> preparedOperation = createQuery(queryMethod, r2dbcQuery);
assertThat(bindableQuery.get()).isEqualTo(
assertThat(preparedOperation.get()).isEqualTo(
"SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM "
+ TABLE);
}
@@ -639,11 +641,11 @@ class PartTreeR2dbcQueryUnitTests {
void createsDtoProjectionQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAsDtoProjectionBy");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery);
PreparedOperation<?> preparedOperation = createQuery(queryMethod, r2dbcQuery);
assertThat(bindableQuery.get()).isEqualTo(
assertThat(preparedOperation.get()).isEqualTo(
"SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM "
+ TABLE);
}
@@ -652,19 +654,21 @@ class PartTreeR2dbcQueryUnitTests {
void createsQueryForCountProjection() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("countByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, operations, r2dbcConverter,
dataAccessStrategy);
BindableQuery query = createQuery(queryMethod, r2dbcQuery, "John");
PreparedOperation<?> query = createQuery(queryMethod, r2dbcQuery, "John");
assertThat(query.get())
.isEqualTo("SELECT COUNT(users.id) FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
private BindableQuery createQuery(R2dbcQueryMethod queryMethod, PartTreeR2dbcQuery r2dbcQuery, Object... parameters) {
private PreparedOperation<?> createQuery(R2dbcQueryMethod queryMethod, PartTreeR2dbcQuery r2dbcQuery,
Object... parameters) {
return createQuery(r2dbcQuery, getAccessor(queryMethod, parameters));
}
private BindableQuery createQuery(PartTreeR2dbcQuery r2dbcQuery, RelationalParametersParameterAccessor accessor) {
private PreparedOperation<?> createQuery(PartTreeR2dbcQuery r2dbcQuery,
RelationalParametersParameterAccessor accessor) {
return r2dbcQuery.createQuery(accessor).block();
}
@@ -678,6 +682,7 @@ class PartTreeR2dbcQueryUnitTests {
return new RelationalParametersParameterAccessor(queryMethod, values);
}
@SuppressWarnings("ALL")
interface UserRepository extends Repository<User, Long> {
Flux<User> findAllByFirstName(String firstName);

View File

@@ -16,7 +16,6 @@
package org.springframework.data.r2dbc.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -34,6 +33,7 @@ import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
@@ -45,8 +45,9 @@ import org.springframework.data.repository.core.support.AbstractRepositoryMetada
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.r2dbc.core.PreparedOperation;
import org.springframework.r2dbc.core.binding.BindTarget;
import org.springframework.util.ReflectionUtils;
/**
@@ -60,8 +61,8 @@ public class StringBasedR2dbcQueryUnitTests {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
@Mock private DatabaseClient databaseClient;
@Mock private GenericExecuteSpec bindSpec;
@Mock private R2dbcEntityOperations entityOperations;
@Mock private BindTarget bindTarget;
private RelationalMappingContext mappingContext;
private MappingR2dbcConverter converter;
@@ -77,9 +78,6 @@ public class StringBasedR2dbcQueryUnitTests {
this.accessStrategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE, converter);
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class);
this.factory = new SpelAwareProxyProjectionFactory();
when(bindSpec.bind(anyInt(), any())).thenReturn(bindSpec);
when(bindSpec.bind(anyString(), any())).thenReturn(bindSpec);
}
@Test
@@ -88,12 +86,12 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("findByLastname", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind(0, "White");
verify(bindTarget).bind(0, Parameter.from("White"));
}
@Test // gh-164
@@ -102,12 +100,12 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("findByLastnamePositional", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind(0, "White");
verify(bindTarget).bind(0, Parameter.from("White"));
}
@Test
@@ -116,12 +114,12 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("findByNamedParameter", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :lastname");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("lastname", "White");
verify(bindTarget).bind(0, "White");
}
@Test
@@ -130,12 +128,12 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("findByNamedBindMarker", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = @lastname");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("lastname", "White");
verify(bindTarget).bind("lastname", Parameter.from("White"));
}
@Test
@@ -144,12 +142,13 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("findNotByNamedBindMarker", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :unknown");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :unknown");
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind(0, "White");
verify(bindTarget).bind(0, "White");
}
@Test // gh-164
@@ -158,12 +157,13 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("simpleSpel");
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod());
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__");
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("__synthetic_0__", "hello");
verify(bindTarget).bind(0, "hello");
}
@Test // gh-164
@@ -172,13 +172,14 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("simpleIndexedSpel", String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.getSource()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__");
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("__synthetic_0__", "White");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "White");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-164
@@ -187,15 +188,15 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("simplePositionalSpel", String.class, String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get())
assertThat(stringQuery.getSource())
.isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("__synthetic_0__", "White");
verify(bindSpec).bind("firstname", "Walter");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "White");
verify(bindTarget).bind(1, "Walter");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-164
@@ -204,15 +205,15 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("simpleNamedSpel", String.class, String.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "White", "Walter");
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get())
.isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__ and firstname = :firstname");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
.isEqualTo("SELECT * FROM person WHERE lastname = $1 and firstname = $2");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("__synthetic_0__", "White");
verify(bindSpec).bind("firstname", "Walter");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "White");
verify(bindTarget).bind(1, "Walter");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-164
@@ -221,13 +222,13 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("queryWithSpelObject", Person.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), new Person("Walter"));
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :__synthetic_0__");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind("__synthetic_0__", "Walter");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "Walter");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-321
@@ -236,13 +237,13 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("queryWithUnusedParameter", String.class, Sort.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), "Walter", null);
BindableQuery stringQuery = query.createQuery(accessor).block();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = :name");
assertThat(stringQuery.bind(bindSpec)).isNotNull();
assertThat(stringQuery.get()).isEqualTo("SELECT * FROM person WHERE lastname = $1");
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind(0, "Walter");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "Walter");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-465
@@ -251,11 +252,11 @@ public class StringBasedR2dbcQueryUnitTests {
StringBasedR2dbcQuery query = getQueryMethod("queryWithEnum", MyEnum.class);
R2dbcParameterAccessor accessor = new R2dbcParameterAccessor(query.getQueryMethod(), MyEnum.INSTANCE);
BindableQuery stringQuery = query.createQuery(accessor).block();
assertThat(stringQuery.bind(bindSpec)).isNotNull();
PreparedOperation<?> stringQuery = query.createQuery(accessor).block();
stringQuery.bindTo(bindTarget);
verify(bindSpec).bind(0, "INSTANCE");
verifyNoMoreInteractions(bindSpec);
verify(bindTarget).bind(0, "INSTANCE");
verifyNoMoreInteractions(bindTarget);
}
@Test // gh-475
@@ -280,7 +281,7 @@ public class StringBasedR2dbcQueryUnitTests {
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory, converter.getMappingContext());
return new StringBasedR2dbcQuery(queryMethod, databaseClient, converter, accessStrategy, PARSER,
return new StringBasedR2dbcQuery(queryMethod, entityOperations, converter, accessStrategy, PARSER,
ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
}