Skip to content

Support Single Query Loading for aggregates with more than one collection #1622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.0-more-than-one-collection</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.0-more-than-one-collection</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.0-more-than-one-collection</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>3.2.0-SNAPSHOT</version>
<version>3.2.0-more-than-one-collection</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,24 @@
* intermediate {@link RowDocumentResultSetExtractor RowDocument} and mapped via
* {@link org.springframework.data.relational.core.conversion.RelationalConverter#read(Class, RowDocument)}.
*
* @param <T> the type of aggregate produced by this reader.
* @author Jens Schauder
* @author Mark Paluch
* @since 3.2
*/
class AggregateReader<T> implements PathToColumnMapping {
class AggregateReader implements PathToColumnMapping {

private final RelationalPersistentEntity<T> aggregate;
private final Table table;
private final AliasFactory aliasFactory;
private final SqlGenerator sqlGenerator;
private final JdbcConverter converter;
private final NamedParameterJdbcOperations jdbcTemplate;
private final AliasFactory aliasFactory;
private final RowDocumentResultSetExtractor extractor;

AggregateReader(Dialect dialect, JdbcConverter converter, AliasFactory aliasFactory,
NamedParameterJdbcOperations jdbcTemplate, RelationalPersistentEntity<T> aggregate) {
AggregateReader(Dialect dialect, JdbcConverter converter, NamedParameterJdbcOperations jdbcTemplate) {

this.aliasFactory = new AliasFactory();
this.converter = converter;
this.aggregate = aggregate;
this.jdbcTemplate = jdbcTemplate;
this.table = Table.create(aggregate.getQualifiedTableName());
this.sqlGenerator = new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate);
this.aliasFactory = aliasFactory;
this.sqlGenerator = new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect);
this.extractor = new RowDocumentResultSetExtractor(converter.getMappingContext(), this);
}

Expand All @@ -93,54 +87,55 @@ public String keyColumn(AggregatePath path) {
}

@Nullable
public T findById(Object id) {
public <T> T findById(Object id, RelationalPersistentEntity<T> entity) {

Query query = Query.query(Criteria.where(aggregate.getRequiredIdProperty().getName()).is(id)).limit(1);
Query query = Query.query(Criteria.where(entity.getRequiredIdProperty().getName()).is(id)).limit(1);

return findOne(query);
return findOne(query, entity);
}

@Nullable
public T findOne(Query query) {
return doFind(query, this::extractZeroOrOne);
public <T> T findOne(Query query, RelationalPersistentEntity<T> entity) {
return doFind(query, entity, rs -> extractZeroOrOne(rs, entity));
}

public List<T> findAllById(Iterable<?> ids) {
public <T> List<T> findAllById(Iterable<?> ids, RelationalPersistentEntity<T> entity) {

Collection<?> identifiers = ids instanceof Collection<?> idl ? idl : Streamable.of(ids).toList();
Query query = Query.query(Criteria.where(aggregate.getRequiredIdProperty().getName()).in(identifiers));
Query query = Query.query(Criteria.where(entity.getRequiredIdProperty().getName()).in(identifiers));

return findAll(query);
return findAll(query, entity);
}

@SuppressWarnings("ConstantConditions")
public List<T> findAll() {
return jdbcTemplate.query(sqlGenerator.findAll(), this::extractAll);
public <T> List<T> findAll(RelationalPersistentEntity<T> entity) {
return jdbcTemplate.query(sqlGenerator.findAll(entity),
(ResultSetExtractor<? extends List<T>>) rs -> extractAll(rs, entity));
}

public List<T> findAll(Query query) {
return doFind(query, this::extractAll);
public <T> List<T> findAll(Query query, RelationalPersistentEntity<T> entity) {
return doFind(query, entity, rs -> extractAll(rs, entity));
}

@SuppressWarnings("ConstantConditions")
private <R> R doFind(Query query, ResultSetExtractor<R> extractor) {
private <T, R> R doFind(Query query, RelationalPersistentEntity<T> entity, ResultSetExtractor<R> extractor) {

MapSqlParameterSource parameterSource = new MapSqlParameterSource();
Condition condition = createCondition(query, parameterSource);
String sql = sqlGenerator.findAll(condition);
Condition condition = createCondition(query, parameterSource, entity);
String sql = sqlGenerator.findAll(entity, condition);

return jdbcTemplate.query(sql, parameterSource, extractor);
}

@Nullable
private Condition createCondition(Query query, MapSqlParameterSource parameterSource) {
private Condition createCondition(Query query, MapSqlParameterSource parameterSource,
RelationalPersistentEntity<?> entity) {

QueryMapper queryMapper = new QueryMapper(converter);

Optional<CriteriaDefinition> criteria = query.getCriteria();
return criteria
.map(criteriaDefinition -> queryMapper.getMappedObject(parameterSource, criteriaDefinition, table, aggregate))
.orElse(null);
return criteria.map(criteriaDefinition -> queryMapper.getMappedObject(parameterSource, criteriaDefinition,
Table.create(entity.getQualifiedTableName()), entity)).orElse(null);
}

/**
Expand All @@ -152,12 +147,13 @@ private Condition createCondition(Query query, MapSqlParameterSource parameterSo
* @return a {@code List} of aggregates, fully converted.
* @throws SQLException on underlying JDBC errors.
*/
private List<T> extractAll(ResultSet rs) throws SQLException {
private <T> List<T> extractAll(ResultSet rs, RelationalPersistentEntity<T> entity) throws SQLException {

Iterator<RowDocument> iterate = extractor.iterate(aggregate, rs);
Iterator<RowDocument> iterate = extractor.iterate(entity, rs);
List<T> resultList = new ArrayList<>();

while (iterate.hasNext()) {
resultList.add(converter.read(aggregate.getType(), iterate.next()));
resultList.add(converter.read(entity.getType(), iterate.next()));
}

return resultList;
Expand All @@ -175,17 +171,19 @@ private List<T> extractAll(ResultSet rs) throws SQLException {
* @throws IncorrectResultSizeDataAccessException when the conversion yields more than one instance.
*/
@Nullable
private T extractZeroOrOne(ResultSet rs) throws SQLException {
private <T> T extractZeroOrOne(ResultSet rs, RelationalPersistentEntity<T> entity) throws SQLException {

Iterator<RowDocument> iterate = extractor.iterate(entity, rs);

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 converter.read(entity.getType(), object);
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.core.sqlgeneration.AliasFactory;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.ConcurrentLruCache;

/**
* A {@link ReadingDataAccessStrategy} that uses an {@link AggregateReader} to load entities with a single query.
Expand All @@ -39,31 +37,28 @@
class SingleQueryDataAccessStrategy implements ReadingDataAccessStrategy {

private final RelationalMappingContext mappingContext;
private final AliasFactory aliasFactory;
private final ConcurrentLruCache<RelationalPersistentEntity<?>, AggregateReader<?>> readerCache;
private final AggregateReader aggregateReader;

public SingleQueryDataAccessStrategy(Dialect dialect, JdbcConverter converter,
NamedParameterJdbcOperations jdbcTemplate) {

this.mappingContext = converter.getMappingContext();
this.aliasFactory = new AliasFactory();
this.readerCache = new ConcurrentLruCache<>(256,
entity -> new AggregateReader<>(dialect, converter, aliasFactory, jdbcTemplate, entity));
this.aggregateReader = new AggregateReader(dialect, converter, jdbcTemplate);
}

@Override
public <T> T findById(Object id, Class<T> domainType) {
return getReader(domainType).findById(id);
return aggregateReader.findById(id, getPersistentEntity(domainType));
}

@Override
public <T> List<T> findAll(Class<T> domainType) {
return getReader(domainType).findAll();
return aggregateReader.findAll(getPersistentEntity(domainType));
}

@Override
public <T> List<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return getReader(domainType).findAllById(ids);
return aggregateReader.findAllById(ids, getPersistentEntity(domainType));
}

@Override
Expand All @@ -78,12 +73,12 @@ public <T> List<T> findAll(Class<T> domainType, Pageable pageable) {

@Override
public <T> Optional<T> findOne(Query query, Class<T> domainType) {
return Optional.ofNullable(getReader(domainType).findOne(query));
return Optional.ofNullable(aggregateReader.findOne(query, getPersistentEntity(domainType)));
}

@Override
public <T> List<T> findAll(Query query, Class<T> domainType) {
return getReader(domainType).findAll(query);
return aggregateReader.findAll(query, getPersistentEntity(domainType));
}

@Override
Expand All @@ -92,11 +87,7 @@ public <T> List<T> findAll(Query query, Class<T> domainType, Pageable pageable)
}

@SuppressWarnings("unchecked")
private <T> AggregateReader<T> getReader(Class<T> domainType) {

RelationalPersistentEntity<T> persistentEntity = (RelationalPersistentEntity<T>) mappingContext
.getRequiredPersistentEntity(domainType);

return (AggregateReader<T>) readerCache.get(persistentEntity);
private <T> RelationalPersistentEntity<T> getPersistentEntity(Class<T> domainType) {
return (RelationalPersistentEntity<T>) mappingContext.getRequiredPersistentEntity(domainType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* Query Loading.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 3.2
*/
class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy {
Expand Down Expand Up @@ -120,23 +121,25 @@ private boolean isSingleSelectQuerySupported(Class<?> entityType) {

private boolean entityQualifiesForSingleQueryLoading(Class<?> entityType) {

boolean referenceFound = false;
for (PersistentPropertyPath<RelationalPersistentProperty> path : converter.getMappingContext()
.findPersistentPropertyPaths(entityType, __ -> true)) {
RelationalPersistentProperty property = path.getLeafProperty();
if (property.isEntity()) {

// single references are currently not supported
if (!(property.isMap() || property.isCollectionLike())) {
return false;
}

// embedded entities are currently not supported
if (property.isEmbedded()) {
return false;
}

// only a single reference is currently supported
if (referenceFound) {
// nested references are currently not supported
if (path.getLength() > 1) {
return false;
}

referenceFound = true;
}
}
return true;
Expand Down
Loading