Support Single Query Loading for aggregates with more than one collection.

Closes #1448
Original pull request: #1622
This commit is contained in:
Jens Schauder
2023-09-19 13:15:56 +02:00
committed by Mark Paluch
parent 8fa9e3e1d5
commit 9e41d25d15
14 changed files with 453 additions and 41 deletions

View File

@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
* Query Loading.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 3.2
*/
class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy {
@@ -120,23 +121,25 @@ class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy
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;

View File

@@ -22,6 +22,8 @@ import static org.assertj.core.api.SoftAssertions.*;
import static org.springframework.data.jdbc.testing.TestConfiguration.*;
import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,6 +39,7 @@ import java.util.function.Function;
import java.util.stream.IntStream;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
@@ -44,6 +47,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
@@ -70,6 +74,7 @@ import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.CriteriaDefinition;
import org.springframework.data.relational.core.query.Query;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
@@ -95,6 +100,7 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
@Autowired JdbcAggregateOperations template;
@Autowired NamedParameterJdbcOperations jdbcTemplate;
@Autowired RelationalMappingContext mappingContext;
@Autowired NamedParameterJdbcOperations jdbc;
LegoSet legoSet = createLegoSet("Star Destroyer");
@@ -1202,6 +1208,115 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
assertThat(template.findById(entity.id, EnumArrayOwner.class).digits).isEqualTo(new Color[] { Color.BLUE });
}
@Test // GH-1448
void multipleCollections() {
MultipleCollections aggregate = new MultipleCollections();
aggregate.name = "aggregate";
aggregate.listElements.add(new ListElement("one"));
aggregate.listElements.add(new ListElement("two"));
aggregate.listElements.add(new ListElement("three"));
aggregate.setElements.add(new SetElement("one"));
aggregate.setElements.add(new SetElement("two"));
aggregate.mapElements.put("alpha", new MapElement("one"));
aggregate.mapElements.put("beta", new MapElement("two"));
aggregate.mapElements.put("gamma", new MapElement("three"));
aggregate.mapElements.put("delta", new MapElement("four"));
template.save(aggregate);
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
assertSoftly(softly -> {
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
aggregate.listElements.get(1), aggregate.listElements.get(2));
softly.assertThat(reloaded.setElements)
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
});
}
@Test // GH-1448
void multipleCollectionsWithEmptySet() {
MultipleCollections aggregate = new MultipleCollections();
aggregate.name = "aggregate";
aggregate.listElements.add(new ListElement("one"));
aggregate.listElements.add(new ListElement("two"));
aggregate.listElements.add(new ListElement("three"));
aggregate.mapElements.put("alpha", new MapElement("one"));
aggregate.mapElements.put("beta", new MapElement("two"));
aggregate.mapElements.put("gamma", new MapElement("three"));
aggregate.mapElements.put("delta", new MapElement("four"));
template.save(aggregate);
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
assertSoftly(softly -> {
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
aggregate.listElements.get(1), aggregate.listElements.get(2));
softly.assertThat(reloaded.setElements)
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
});
}
@Test // GH-1448
void multipleCollectionsWithEmptyList() {
MultipleCollections aggregate = new MultipleCollections();
aggregate.name = "aggregate";
aggregate.setElements.add(new SetElement("one"));
aggregate.setElements.add(new SetElement("two"));
aggregate.mapElements.put("alpha", new MapElement("one"));
aggregate.mapElements.put("beta", new MapElement("two"));
aggregate.mapElements.put("gamma", new MapElement("three"));
aggregate.mapElements.put("delta", new MapElement("four"));
template.save(aggregate);
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
assertSoftly(softly -> {
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
softly.assertThat(reloaded.listElements).containsExactly();
softly.assertThat(reloaded.setElements)
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
});
}
private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate,
Function<Number, T> toConcreteNumber) {
saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0);
@@ -1932,6 +2047,24 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
@InsertOnlyProperty String insertOnly;
}
@Table
static class MultipleCollections {
@Id Long id;
String name;
List<ListElement> listElements = new ArrayList<>();
Set<SetElement> setElements = new HashSet<>();
Map<String, MapElement> mapElements = new HashMap<>();
}
record ListElement(String name) {
}
record SetElement(String name) {
}
record MapElement(String name) {
}
@Configuration
@Import(TestConfiguration.class)
static class Config {

View File

@@ -42,6 +42,11 @@ DROP TABLE WITH_ID_ONLY;
DROP TABLE WITH_INSERT_ONLY;
DROP TABLE MULTIPLE_COLLECTIONS;
DROP TABLE MAP_ELEMENT;
DROP TABLE LIST_ELEMENT;
DROP TABLE SET_ELEMENT;
CREATE TABLE LEGO_SET
(
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@@ -373,4 +378,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -340,4 +340,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID SERIAL PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID SERIAL PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -341,4 +341,30 @@ CREATE TABLE WITH_INSERT_ONLY
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
)
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -314,4 +314,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -347,4 +347,35 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT IDENTITY PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
DROP TABLE MULTIPLE_COLLECTIONS;
DROP TABLE MAP_ELEMENT;
DROP TABLE LIST_ELEMENT;
DROP TABLE SET_ELEMENT;
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID BIGINT IDENTITY PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -319,4 +319,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -32,6 +32,11 @@ DROP TABLE WITH_LOCAL_DATE_TIME CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_ID_ONLY CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_INSERT_ONLY CASCADE CONSTRAINTS PURGE;
DROP TABLE MULTIPLE_COLLECTIONS CASCADE CONSTRAINTS PURGE;
DROP TABLE MAP_ELEMENT CASCADE CONSTRAINTS PURGE;
DROP TABLE LIST_ELEMENT CASCADE CONSTRAINTS PURGE;
DROP TABLE SET_ELEMENT CASCADE CONSTRAINTS PURGE;
CREATE TABLE LEGO_SET
(
"id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
@@ -354,4 +359,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS NUMBER,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS NUMBER,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS NUMBER,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -35,6 +35,11 @@ DROP TABLE WITH_LOCAL_DATE_TIME;
DROP TABLE WITH_ID_ONLY;
DROP TABLE WITH_INSERT_ONLY;
DROP TABLE MULTIPLE_COLLECTIONS;
DROP TABLE MAP_ELEMENT;
DROP TABLE LIST_ELEMENT;
DROP TABLE SET_ELEMENT;
CREATE TABLE LEGO_SET
(
"id1" SERIAL PRIMARY KEY,
@@ -376,4 +381,30 @@ CREATE TABLE WITH_INSERT_ONLY
(
ID SERIAL PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
);
CREATE TABLE MULTIPLE_COLLECTIONS
(
ID SERIAL PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE SET_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
NAME VARCHAR(100)
);
CREATE TABLE LIST_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY INT,
NAME VARCHAR(100)
);
CREATE TABLE MAP_ELEMENT
(
MULTIPLE_COLLECTIONS BIGINT,
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
NAME VARCHAR(100)
);

View File

@@ -187,16 +187,12 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
backReferenceAlias = aliases.getBackReferenceAlias(basePath);
columns.add(table.column(basePath.getTableInfo().reverseColumnInfo().name()).as(backReferenceAlias));
if (basePath.isQualified()) {
keyAlias = aliases.getKeyAlias(basePath);
Expression keyExpression = basePath.isQualified()
? table.column(basePath.getTableInfo().qualifierColumnInfo().name()).as(keyAlias)
: createRowNumberExpression(basePath, table, keyAlias);
columns.add(keyExpression);
keyAlias = aliases.getKeyAlias(basePath);
columns.add(table.column(basePath.getTableInfo().qualifierColumnInfo().name()).as(keyAlias));
} else {
String alias = aliases.getColumnAlias(basePath);
columns.add(new AliasedExpression(just("1"), alias));
columnAliases.add(just(alias));
}
}
String id = null;
@@ -215,8 +211,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
SelectBuilder.BuildSelect buildSelect = condition != null ? select.where(condition) : select;
InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(false),
aliases.getTableAlias(context.getAggregatePath(entity)));
InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(false), aliases.getTableAlias(basePath));
return QueryMeta.of(basePath, inlineQuery, columnAliases, just(id), just(backReferenceAlias), just(keyAlias),
just(rowNumberAlias), just(rowCountAlias));
}
@@ -252,6 +247,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
Expressions.just(backReferenceAlias));
select = select.leftOuterJoin(queryMeta.inlineQuery).on(joinCondition);
}
return select;
}
@@ -274,18 +270,54 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
private SelectBuilder.SelectOrdered applyWhereCondition(AggregatePath rootPath, List<QueryMeta> inlineQueries,
SelectBuilder.SelectJoin select) {
SelectBuilder.SelectWhereAndOr selectWhere = null;
for (QueryMeta queryMeta : inlineQueries) {
SelectBuilder.SelectWhere selectWhere = (SelectBuilder.SelectWhere) select;
AggregatePath path = queryMeta.basePath;
Expression childRowNumber = just(aliases.getRowNumberAlias(path));
Condition pseudoJoinCondition = Conditions.isNull(childRowNumber)
.or(Conditions.isEqual(childRowNumber, Expressions.just(aliases.getRowNumberAlias(rootPath))))
.or(Conditions.isGreater(childRowNumber, Expressions.just(aliases.getRowCountAlias(rootPath))));
Condition joins = null;
selectWhere = ((SelectBuilder.SelectWhere) select).where(pseudoJoinCondition);
for (int left = 0; left < inlineQueries.size(); left++) {
QueryMeta leftQueryMeta = inlineQueries.get(left);
AggregatePath leftPath = leftQueryMeta.basePath;
Expression leftRowNumber = just(aliases.getRowNumberAlias(leftPath));
Expression leftRowCount = just(aliases.getRowCountAlias(leftPath));
for (int right = left + 1; right < inlineQueries.size(); right++) {
QueryMeta rightQueryMeta = inlineQueries.get(right);
AggregatePath rightPath = rightQueryMeta.basePath;
Expression rightRowNumber = just(aliases.getRowNumberAlias(rightPath));
Expression rightRowCount = just(aliases.getRowCountAlias(rightPath));
System.out.println("joining: " + leftPath + " and " + rightPath);
Condition mutualJoin = Conditions.isEqual(leftRowNumber, rightRowNumber).or(Conditions.isNull(leftRowNumber))
.or(Conditions.isNull(rightRowNumber))
.or(Conditions.nest(Conditions.isGreater(leftRowNumber, rightRowCount)
.and(Conditions.isEqual(rightRowNumber, SQL.literalOf(1)))))
.or(Conditions.nest(Conditions.isGreater(rightRowNumber, leftRowCount)
.and(Conditions.isEqual(leftRowNumber, SQL.literalOf(1)))));
mutualJoin = Conditions.nest(mutualJoin);
if (joins == null) {
joins = mutualJoin;
} else {
joins = joins.and(mutualJoin);
}
}
}
// for (QueryMeta queryMeta : inlineQueries) {
//
// AggregatePath path = queryMeta.basePath;
// Expression childRowNumber = just(aliases.getRowNumberAlias(path));
// Condition pseudoJoinCondition = Conditions.isNull(childRowNumber)
// .or(Conditions.isEqual(childRowNumber, Expressions.just(aliases.getRowNumberAlias(rootPath))))
// .or(Conditions.isGreater(childRowNumber, Expressions.just(aliases.getRowCountAlias(rootPath))));
//
selectWhere = (SelectBuilder.SelectWhere) selectWhere.where(joins);
// }
return selectWhere == null ? (SelectBuilder.SelectOrdered) select : selectWhere;
}

View File

@@ -53,7 +53,7 @@ class AliasFactoryUnitTests {
}
@Test
void nameGetsSanatized() {
void nameGetsSanitized() {
String alias = aliasFactory.getColumnAlias(
context.getAggregatePath( context.getPersistentPropertyPath("evil", DummyEntity.class)));
@@ -136,6 +136,21 @@ class AliasFactoryUnitTests {
}
}
@Nested
class TableAlias {
@Test // GH-1448
void tableAliasIsDifferentForDifferentPathsToSameEntity() {
String alias = aliasFactory.getTableAlias(
context.getAggregatePath(context.getPersistentPropertyPath("dummy", Reference.class)));
String alias2 = aliasFactory.getTableAlias(
context.getAggregatePath(context.getPersistentPropertyPath("dummy2", Reference.class)));
assertThat(alias).isNotEqualTo(alias2);
}
}
static class DummyEntity {
String name;
@@ -144,5 +159,6 @@ class AliasFactoryUnitTests {
static class Reference {
DummyEntity dummy;
DummyEntity dummy2;
}
}

View File

@@ -161,11 +161,9 @@ class SingleQuerySqlGeneratorUnitTests {
func("coalesce", col(trivialsRowNumber), lit(1))), //
col(backref), //
col(keyAlias) //
).extractWhereClause() //
.doesNotContainIgnoringCase("and") //
.containsIgnoringCase(trivialsRowNumber + " is null") //
.containsIgnoringCase(trivialsRowNumber + " = " + rootRowNumber) //
.containsIgnoringCase(trivialsRowNumber + " > " + rootCount);
)
.extractWhereClause() //
.isEqualTo("");
baseSelect.hasInlineViewSelectingFrom("\"single_reference_aggregate\"") //
.hasExactlyColumns( //
lit(1).as(rnAlias()), lit(1).as(rootCount), //

View File

@@ -28,11 +28,11 @@ If the aggregate root references other entities those are loaded with separate s
With this an arbitrary number of aggregates can be fully loaded with a single SQL query.
This should be significant more efficient, especially for complex aggregates, consisting of many entities.
+
Currently, Single Query Loading is restricted to:
Currently, Single Query Loading is restricted in different ways:
1. It only works for aggregates that only reference one entity collection.The plan is to remove this constraint in the future.
1. The aggregate must not have nested collections, this includes `Map`.The plan is to remove this constraint in the future.
2. The aggregate must also not use `AggregateReference` or embedded entities.The plan is to remove this constraint in the future.
2. The aggregate must not use `AggregateReference` or embedded entities.The plan is to remove this constraint in the future.
3. The database dialect must support it.Of the dialects provided by Spring Data JDBC all but H2 and HSQL support this.H2 and HSQL don't support analytic functions (aka windowing functions).
@@ -40,6 +40,8 @@ Currently, Single Query Loading is restricted to:
5. Single Query Loading needs to be enabled in the `JdbcMappingContext`, by calling `setSingleQueryLoadingEnabled(true)`
If any condition is not fulfilled Spring Data JDBC falls back to the default approach of loading aggregates.
NOTE: Single Query Loading is to be considered experimental.
We appreciate feedback on how it works for you.