Support Single Query Loading for aggregates with more than one collection.
Closes #1448 Original pull request: #1622
This commit is contained in:
committed by
Mark Paluch
parent
8fa9e3e1d5
commit
9e41d25d15
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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), //
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user