Use Converter-based projection for R2DBC repository queries.

Previously, we instantiated the underlying entity. Now, we either read results directly into the result type or use a Map-backed projection.

Closes #1687
This commit is contained in:
Mark Paluch
2023-12-05 15:09:12 +01:00
parent 343569bae0
commit 61a145178b
6 changed files with 216 additions and 14 deletions

View File

@@ -159,6 +159,22 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
*/
<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 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}.
* @param resultType the returned entity, type must not be {@literal null}.
* @return a {@link RowsFetchSpec} ready to materialize.
* @throws DataAccessException if there is any problem issuing the execution.
* @since 3.2.1
*/
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass, Class<T> resultType)
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
@@ -234,6 +250,9 @@ public interface R2dbcEntityOperations extends FluentR2dbcOperations {
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass,
BiFunction<Row, RowMetadata, T> rowMapper) throws DataAccessException;
<T> RowsFetchSpec<T> getRowsFetchSpec(DatabaseClient.GenericExecuteSpec executeSpec, Class<?> entityType,
Class<T> resultType);
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------

View File

@@ -419,11 +419,16 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
@Override
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<T> entityClass) {
return query(operation, entityClass, entityClass);
}
@Override
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass, Class<T> resultType) throws DataAccessException {
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),
return new EntityCallbackAdapter<>(getRowsFetchSpec(databaseClient.sql(operation), entityClass, resultType),
getTableNameOrEmpty(entityClass));
}
@@ -774,7 +779,7 @@ 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<?> entityType,
public <T> RowsFetchSpec<T> getRowsFetchSpec(DatabaseClient.GenericExecuteSpec executeSpec, Class<?> entityType,
Class<T> resultType) {
boolean simpleType = getConverter().isSimpleType(resultType);

View File

@@ -90,13 +90,15 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
} else if (isExistsQuery()) {
fetchSpec = entityOperations.getDatabaseClient().sql(operation).map(row -> true);
} else {
fetchSpec = entityOperations.query(operation, resolveResultType(processor));
fetchSpec = entityOperations.query(operation, processor.getReturnedType()
.getDomainType(),
resolveResultType(processor));
}
R2dbcQueryExecution execution = new ResultProcessingExecution(getExecutionToWrap(processor.getReturnedType()),
new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators));
return execution.execute(RowsFetchSpec.class.cast(fetchSpec));
return execution.execute((RowsFetchSpec) fetchSpec);
}
Class<?> resolveResultType(ResultProcessor resultProcessor) {
@@ -107,7 +109,7 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
return returnedType.getDomainType();
}
return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType();
return returnedType.getReturnedType();
}
private R2dbcQueryExecution getExecutionToWrap(ReturnedType returnedType) {
@@ -122,17 +124,17 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) {
return fs.rowsUpdated().map(integer -> integer > 0);
}
}
if (Number.class.isAssignableFrom(returnedType.getReturnedType())) {
if (Number.class.isAssignableFrom(returnedType.getReturnedType())) {
return fs.rowsUpdated()
.map(count -> converter.getConversionService().convert(count, returnedType.getReturnedType()));
}
}
if (ReflectionUtils.isVoid(returnedType.getReturnedType())) {
if (ReflectionUtils.isVoid(returnedType.getReturnedType())) {
return fs.rowsUpdated().then();
}
}
return fs.rowsUpdated();
};