Browse Source

Polishing.

Original pull request #1604
See #1586
pull/1616/head
Jens Schauder 2 years ago
parent
commit
4cf07be37c
No known key found for this signature in database
GPG Key ID: 9537B67540F0A581
  1. 70
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java
  2. 13
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java
  3. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java
  4. 26
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractorUnitTests.java
  5. 3
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java
  6. 4
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java
  7. 1
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java
  8. 1
      spring-data-relational/src/main/java/org/springframework/data/relational/domain/RowDocument.java

70
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java

@ -37,7 +37,7 @@ import org.springframework.util.Assert;
/** /**
* Reads complete Aggregates from the database, by generating appropriate SQL using a {@link SingleQuerySqlGenerator} * Reads complete Aggregates from the database, by generating appropriate SQL using a {@link SingleQuerySqlGenerator}
* through {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}. Results are converterd into an * through {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}. Results are converterd into an
* intermediate {@link ResultSetRowDocumentExtractor RowDocument} and mapped via * intermediate {@link RowDocumentResultSetExtractor RowDocument} and mapped via
* {@link org.springframework.data.relational.core.conversion.RelationalConverter#read(Class, RowDocument)}. * {@link org.springframework.data.relational.core.conversion.RelationalConverter#read(Class, RowDocument)}.
* *
* @param <T> the type of aggregate produced by this reader. * @param <T> the type of aggregate produced by this reader.
@ -47,23 +47,23 @@ import org.springframework.util.Assert;
*/ */
class AggregateReader<T> { class AggregateReader<T> {
private final RelationalPersistentEntity<T> entity; private final RelationalPersistentEntity<T> aggregate;
private final org.springframework.data.relational.core.sqlgeneration.SqlGenerator sqlGenerator; private final org.springframework.data.relational.core.sqlgeneration.SqlGenerator sqlGenerator;
private final JdbcConverter converter; private final JdbcConverter converter;
private final NamedParameterJdbcOperations jdbcTemplate; private final NamedParameterJdbcOperations jdbcTemplate;
private final ResultSetRowDocumentExtractor extractor; private final RowDocumentResultSetExtractor extractor;
AggregateReader(Dialect dialect, JdbcConverter converter, AliasFactory aliasFactory, AggregateReader(Dialect dialect, JdbcConverter converter, AliasFactory aliasFactory,
NamedParameterJdbcOperations jdbcTemplate, RelationalPersistentEntity<T> entity) { NamedParameterJdbcOperations jdbcTemplate, RelationalPersistentEntity<T> aggregate) {
this.converter = converter; this.converter = converter;
this.entity = entity; this.aggregate = aggregate;
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.sqlGenerator = new CachingSqlGenerator( this.sqlGenerator = new CachingSqlGenerator(
new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, entity)); new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate));
this.extractor = new ResultSetRowDocumentExtractor(converter.getMappingContext(), this.extractor = new RowDocumentResultSetExtractor(converter.getMappingContext(),
createPathToColumnMapping(aliasFactory)); createPathToColumnMapping(aliasFactory));
} }
@ -74,44 +74,66 @@ class AggregateReader<T> {
@Nullable @Nullable
public T findById(Object id) { public T findById(Object id) {
id = converter.writeValue(id, entity.getRequiredIdProperty().getTypeInformation()); id = converter.writeValue(id, aggregate.getRequiredIdProperty().getTypeInformation());
return jdbcTemplate.query(sqlGenerator.findById(), Map.of("id", id), rs -> { return jdbcTemplate.query(sqlGenerator.findById(), Map.of("id", id), this::extractZeroOrOne);
Iterator<RowDocument> iterate = extractor.iterate(entity, rs);
if (iterate.hasNext()) {
RowDocument object = iterate.next();
if (iterate.hasNext()) {
throw new IncorrectResultSizeDataAccessException(1);
}
return converter.read(entity.getType(), object);
}
return null;
});
} }
public Iterable<T> findAllById(Iterable<?> ids) { public Iterable<T> findAllById(Iterable<?> ids) {
List<Object> convertedIds = new ArrayList<>(); List<Object> convertedIds = new ArrayList<>();
for (Object id : ids) { for (Object id : ids) {
convertedIds.add(converter.writeValue(id, entity.getRequiredIdProperty().getTypeInformation())); convertedIds.add(converter.writeValue(id, aggregate.getRequiredIdProperty().getTypeInformation()));
} }
return jdbcTemplate.query(sqlGenerator.findAllById(), Map.of("ids", convertedIds), this::extractAll); return jdbcTemplate.query(sqlGenerator.findAllById(), Map.of("ids", convertedIds), this::extractAll);
} }
/**
* Extracts a list of aggregates from the given {@link ResultSet} by utilizing the
* {@link RowDocumentResultSetExtractor} and the {@link JdbcConverter}. When used as a method reference this conforms
* to the {@link org.springframework.jdbc.core.ResultSetExtractor} contract.
*
* @param rs the {@link ResultSet} from which to extract the data. Must not be {(}@literal null}.
* @return a {@code List} of aggregates, fully converted.
* @throws SQLException
*/
private List<T> extractAll(ResultSet rs) throws SQLException { private List<T> extractAll(ResultSet rs) throws SQLException {
Iterator<RowDocument> iterate = extractor.iterate(entity, rs); Iterator<RowDocument> iterate = extractor.iterate(aggregate, rs);
List<T> resultList = new ArrayList<>(); List<T> resultList = new ArrayList<>();
while (iterate.hasNext()) { while (iterate.hasNext()) {
resultList.add(converter.read(entity.getType(), iterate.next())); resultList.add(converter.read(aggregate.getType(), iterate.next()));
} }
return resultList; return resultList;
} }
/**
* Extracts a single aggregate or {@literal null} from the given {@link ResultSet} by utilizing the
* {@link RowDocumentResultSetExtractor} and the {@link JdbcConverter}. When used as a method reference this conforms
* to the {@link org.springframework.jdbc.core.ResultSetExtractor} contract.
*
* @param @param rs the {@link ResultSet} from which to extract the data. Must not be {(}@literal null}.
* @return The single instance when the conversion results in exactly one instance. If the {@literal ResultSet} is empty, null is returned.
* @throws SQLException
* @throws IncorrectResultSizeDataAccessException when the conversion yields more than one instance.
*/
@Nullable
private T extractZeroOrOne(ResultSet rs) throws SQLException {
Iterator<RowDocument> iterate = extractor.iterate(aggregate, rs);
if (iterate.hasNext()) {
RowDocument object = iterate.next();
if (iterate.hasNext()) {
throw new IncorrectResultSizeDataAccessException(1);
}
return converter.read(aggregate.getType(), object);
}
return null;
}
private PathToColumnMapping createPathToColumnMapping(AliasFactory aliasFactory) { private PathToColumnMapping createPathToColumnMapping(AliasFactory aliasFactory) {
return new PathToColumnMapping() { return new PathToColumnMapping() {
@Override @Override

13
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java

@ -79,6 +79,7 @@ abstract class RowDocumentExtractorSupport {
protected AggregateContext(TabularResultAdapter<RS> adapter, RelationalMappingContext context, protected AggregateContext(TabularResultAdapter<RS> adapter, RelationalMappingContext context,
PathToColumnMapping propertyToColumn, Map<String, Integer> columnMap) { PathToColumnMapping propertyToColumn, Map<String, Integer> columnMap) {
this.adapter = adapter; this.adapter = adapter;
this.context = context; this.context = context;
this.propertyToColumn = propertyToColumn; this.propertyToColumn = propertyToColumn;
@ -183,6 +184,7 @@ abstract class RowDocumentExtractorSupport {
public RowDocumentSink(AggregateContext<RS> aggregateContext, RelationalPersistentEntity<?> entity, public RowDocumentSink(AggregateContext<RS> aggregateContext, RelationalPersistentEntity<?> entity,
AggregatePath basePath) { AggregatePath basePath) {
this.aggregateContext = aggregateContext; this.aggregateContext = aggregateContext;
this.entity = entity; this.entity = entity;
this.basePath = basePath; this.basePath = basePath;
@ -234,11 +236,13 @@ abstract class RowDocumentExtractorSupport {
AggregatePath path = basePath.append(property); AggregatePath path = basePath.append(property);
if (property.isEntity() && !property.isEmbedded() && (property.isCollectionLike() || property.isQualified())) { if (property.isEntity() && !property.isEmbedded() && (property.isCollectionLike() || property.isQualified())) {
readerState.put(property, new ContainerSink<>(aggregateContext, property, path)); readerState.put(property, new ContainerSink<>(aggregateContext, property, path));
continue; continue;
} }
if (property.isEmbedded()) { if (property.isEmbedded()) {
RelationalPersistentEntity<?> embeddedEntity = aggregateContext.getRequiredPersistentEntity(property); RelationalPersistentEntity<?> embeddedEntity = aggregateContext.getRequiredPersistentEntity(property);
readEntity(row, document, path, embeddedEntity); readEntity(row, document, path, embeddedEntity);
continue; continue;
@ -286,11 +290,7 @@ abstract class RowDocumentExtractorSupport {
} }
} }
if (result.isEmpty() && key == null) { return !(result.isEmpty() && key == null);
return false;
}
return true;
} }
@Override @Override
@ -308,6 +308,7 @@ abstract class RowDocumentExtractorSupport {
@Override @Override
void reset() { void reset() {
result = null; result = null;
readerState.clear(); readerState.clear();
} }
@ -326,6 +327,7 @@ abstract class RowDocumentExtractorSupport {
private @Nullable Object value; private @Nullable Object value;
public SingleColumnSink(AggregateContext<RS> aggregateContext, AggregatePath path) { public SingleColumnSink(AggregateContext<RS> aggregateContext, AggregatePath path) {
this.aggregateContext = aggregateContext; this.aggregateContext = aggregateContext;
this.columnName = path.getColumnInfo().name().getReference(); this.columnName = path.getColumnInfo().name().getReference();
} }
@ -431,6 +433,7 @@ abstract class RowDocumentExtractorSupport {
public Object getResult() { public Object getResult() {
if (componentReader.hasResult()) { if (componentReader.hasResult()) {
container.add(this.key, componentReader.getResult()); container.add(this.key, componentReader.getResult());
componentReader.reset(); componentReader.reset();
} }

10
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetRowDocumentExtractor.java → spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java

@ -40,12 +40,13 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
* @author Mark Paluch * @author Mark Paluch
* @since 3.2 * @since 3.2
*/ */
class ResultSetRowDocumentExtractor { class RowDocumentResultSetExtractor {
private final RelationalMappingContext context; private final RelationalMappingContext context;
private final PathToColumnMapping propertyToColumn; private final PathToColumnMapping propertyToColumn;
ResultSetRowDocumentExtractor(RelationalMappingContext context, PathToColumnMapping propertyToColumn) { RowDocumentResultSetExtractor(RelationalMappingContext context, PathToColumnMapping propertyToColumn) {
this.context = context; this.context = context;
this.propertyToColumn = propertyToColumn; this.propertyToColumn = propertyToColumn;
} }
@ -54,11 +55,14 @@ class ResultSetRowDocumentExtractor {
* Adapter to extract values and column metadata from a {@link ResultSet}. * Adapter to extract values and column metadata from a {@link ResultSet}.
*/ */
enum ResultSetAdapter implements TabularResultAdapter<ResultSet> { enum ResultSetAdapter implements TabularResultAdapter<ResultSet> {
INSTANCE; INSTANCE;
@Override @Override
public Object getObject(ResultSet row, int index) { public Object getObject(ResultSet row, int index) {
try { try {
Object resultSetValue = JdbcUtils.getResultSetValue(row, index); Object resultSetValue = JdbcUtils.getResultSetValue(row, index);
if (resultSetValue instanceof Array a) { if (resultSetValue instanceof Array a) {
@ -75,6 +79,7 @@ class ResultSetRowDocumentExtractor {
public Map<String, Integer> getColumnMap(ResultSet result) { public Map<String, Integer> getColumnMap(ResultSet result) {
try { try {
ResultSetMetaData metaData = result.getMetaData(); ResultSetMetaData metaData = result.getMetaData();
Map<String, Integer> columns = new LinkedCaseInsensitiveMap<>(metaData.getColumnCount()); Map<String, Integer> columns = new LinkedCaseInsensitiveMap<>(metaData.getColumnCount());
@ -201,5 +206,4 @@ class ResultSetRowDocumentExtractor {
return reader.getResult(); return reader.getResult();
} }
} }
} }

26
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/ResultSetRowDocumentExtractorUnitTests.java → spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractorUnitTests.java

@ -41,19 +41,19 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.relational.domain.RowDocument;
/** /**
* Unit tests for the {@link ResultSetRowDocumentExtractor}. * Unit tests for the {@link RowDocumentResultSetExtractor}.
* *
* @author Jens Schauder * @author Jens Schauder
* @author Mark Paluch * @author Mark Paluch
*/ */
public class ResultSetRowDocumentExtractorUnitTests { public class RowDocumentResultSetExtractorUnitTests {
RelationalMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy()); RelationalMappingContext context = new JdbcMappingContext(new DefaultNamingStrategy());
private final PathToColumnMapping column = new PathToColumnMapping() { private final PathToColumnMapping column = new PathToColumnMapping() {
@Override @Override
public String column(AggregatePath path) { public String column(AggregatePath path) {
return ResultSetRowDocumentExtractorUnitTests.this.column(path); return RowDocumentResultSetExtractorUnitTests.this.column(path);
} }
@Override @Override
@ -62,7 +62,7 @@ public class ResultSetRowDocumentExtractorUnitTests {
} }
}; };
ResultSetRowDocumentExtractor documentExtractor = new ResultSetRowDocumentExtractor(context, column); RowDocumentResultSetExtractor documentExtractor = new RowDocumentResultSetExtractor(context, column);
@Test // GH-1446 @Test // GH-1446
void emptyResultSetYieldsEmptyResult() { void emptyResultSetYieldsEmptyResult() {
@ -272,8 +272,7 @@ public class ResultSetRowDocumentExtractorUnitTests {
@Nested @Nested
class Maps { class Maps {
@Test @Test // GH-1446
// GH-1446
void extractSingleMapReference() { void extractSingleMapReference() {
testerFor(WithMaps.class).resultSet(rsc -> { testerFor(WithMaps.class).resultSet(rsc -> {
@ -288,8 +287,7 @@ public class ResultSetRowDocumentExtractorUnitTests {
}); });
} }
@Test @Test // GH-1446
// GH-1446
void extractMultipleCollectionReference() { void extractMultipleCollectionReference() {
testerFor(WithMapsAndList.class).resultSet(rsc -> { testerFor(WithMapsAndList.class).resultSet(rsc -> {
@ -307,8 +305,7 @@ public class ResultSetRowDocumentExtractorUnitTests {
}); });
} }
@Test @Test // GH-1446
// GH-1446
void extractNestedMapsWithId() { void extractNestedMapsWithId() {
testerFor(WithMaps.class).resultSet(rsc -> { testerFor(WithMaps.class).resultSet(rsc -> {
@ -529,16 +526,19 @@ public class ResultSetRowDocumentExtractorUnitTests {
private static class DocumentTester extends AbstractTester { private static class DocumentTester extends AbstractTester {
private final Class<?> entityType; private final Class<?> entityType;
private final ResultSetRowDocumentExtractor extractor; private final RowDocumentResultSetExtractor extractor;
DocumentTester(Class<?> entityType, RelationalMappingContext context, RowDocumentResultSetExtractor extractor) {
DocumentTester(Class<?> entityType, RelationalMappingContext context, ResultSetRowDocumentExtractor extractor) {
super(entityType, context); super(entityType, context);
this.entityType = entityType; this.entityType = entityType;
this.extractor = extractor; this.extractor = extractor;
} }
@Override @Override
DocumentTester resultSet(Consumer<ResultSetConfigurer> configuration) { DocumentTester resultSet(Consumer<ResultSetConfigurer> configuration) {
super.resultSet(configuration); super.resultSet(configuration);
return this; return this;
} }
@ -561,7 +561,9 @@ public class ResultSetRowDocumentExtractorUnitTests {
@Override @Override
ResultSetTester resultSet(Consumer<ResultSetConfigurer> configuration) { ResultSetTester resultSet(Consumer<ResultSetConfigurer> configuration) {
super.resultSet(configuration); super.resultSet(configuration);
return this; return this;
} }

3
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java

@ -27,8 +27,7 @@ import org.springframework.lang.Nullable;
* {@link org.springframework.expression.PropertyAccessor} to allow entity based field access to * {@link org.springframework.expression.PropertyAccessor} to allow entity based field access to
* {@link org.springframework.data.relational.domain.RowDocument}s. * {@link org.springframework.data.relational.domain.RowDocument}s.
* *
* @author Oliver Gierke * @author Mark Paluch
* @author Christoph Strobl
*/ */
class DocumentPropertyAccessor extends MapAccessor { class DocumentPropertyAccessor extends MapAccessor {

4
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java

@ -67,7 +67,9 @@ public class MappingRelationalConverter extends BasicRelationalConverter {
* @param context must not be {@literal null}. * @param context must not be {@literal null}.
*/ */
public MappingRelationalConverter(RelationalMappingContext context) { public MappingRelationalConverter(RelationalMappingContext context) {
super(context); super(context);
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE); this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
} }
@ -79,7 +81,9 @@ public class MappingRelationalConverter extends BasicRelationalConverter {
* @param conversions must not be {@literal null}. * @param conversions must not be {@literal null}.
*/ */
public MappingRelationalConverter(RelationalMappingContext context, CustomConversions conversions) { public MappingRelationalConverter(RelationalMappingContext context, CustomConversions conversions) {
super(context, conversions); super(context, conversions);
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE); this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
} }

1
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java

@ -94,7 +94,6 @@ record RowDocumentAccessor(RowDocument document) {
* @param property must not be {@literal null}. * @param property must not be {@literal null}.
* @return {@literal true} if no non {@literal null} value present. * @return {@literal true} if no non {@literal null} value present.
*/ */
@SuppressWarnings("unchecked")
public boolean hasValue(RelationalPersistentProperty property) { public boolean hasValue(RelationalPersistentProperty property) {
Assert.notNull(property, "Property must not be null"); Assert.notNull(property, "Property must not be null");

1
spring-data-relational/src/main/java/org/springframework/data/relational/domain/RowDocument.java

@ -42,6 +42,7 @@ public class RowDocument implements Map<String, Object> {
} }
public RowDocument(Map<String, ? extends Object> map) { public RowDocument(Map<String, ? extends Object> map) {
this.delegate = new LinkedCaseInsensitiveMap<>(); this.delegate = new LinkedCaseInsensitiveMap<>();
this.delegate.putAll(delegate); this.delegate.putAll(delegate);
} }

Loading…
Cancel
Save