Skip to content

Commit d09f5ea

Browse files
committed
Support Single Query Loading for aggregates with more than one collection.
Closes #1448
1 parent dbf467d commit d09f5ea

13 files changed

+450
-40
lines changed

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

+10-7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* Query Loading.
2828
*
2929
* @author Mark Paluch
30+
* @author Jens Schauder
3031
* @since 3.2
3132
*/
3233
class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy {
@@ -88,28 +89,30 @@ public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
8889
private boolean isSingleSelectQuerySupported(Class<?> entityType) {
8990

9091
return sqlGeneratorSource.getDialect().supportsSingleQueryLoading()//
91-
&& entityQualifiesForSingleSelectQuery(entityType);
92+
&& entityQualifiesForSingleQueryLoading(entityType);
9293
}
9394

94-
private boolean entityQualifiesForSingleSelectQuery(Class<?> entityType) {
95+
private boolean entityQualifiesForSingleQueryLoading(Class<?> entityType) {
9596

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

102+
// single references are currently not supported
103+
if (!(property.isMap() || property.isCollectionLike())) {
104+
return false;
105+
}
106+
102107
// embedded entities are currently not supported
103108
if (property.isEmbedded()) {
104109
return false;
105110
}
106111

107-
// only a single reference is currently supported
108-
if (referenceFound) {
112+
// nested references are currently not supported
113+
if (path.getLength() > 1) {
109114
return false;
110115
}
111-
112-
referenceFound = true;
113116
}
114117

115118
// AggregateReferences aren't supported yet

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java

+133
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*;
2424
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
2525

26+
import java.sql.ResultSet;
27+
import java.sql.SQLException;
2628
import java.time.LocalDateTime;
2729
import java.util.ArrayList;
2830
import java.util.Collections;
@@ -37,13 +39,15 @@
3739
import java.util.stream.IntStream;
3840

3941
import org.assertj.core.api.SoftAssertions;
42+
import org.junit.jupiter.api.Disabled;
4043
import org.junit.jupiter.api.Test;
4144
import org.junit.jupiter.api.extension.ExtendWith;
4245
import org.springframework.beans.factory.annotation.Autowired;
4346
import org.springframework.context.ApplicationEventPublisher;
4447
import org.springframework.context.annotation.Bean;
4548
import org.springframework.context.annotation.Configuration;
4649
import org.springframework.context.annotation.Import;
50+
import org.springframework.dao.DataAccessException;
4751
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
4852
import org.springframework.dao.OptimisticLockingFailureException;
4953
import org.springframework.data.annotation.Id;
@@ -67,6 +71,7 @@
6771
import org.springframework.data.relational.core.mapping.MappedCollection;
6872
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
6973
import org.springframework.data.relational.core.mapping.Table;
74+
import org.springframework.jdbc.core.ResultSetExtractor;
7075
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
7176
import org.springframework.test.context.ActiveProfiles;
7277
import org.springframework.test.context.ContextConfiguration;
@@ -98,6 +103,7 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
98103
@Autowired JdbcAggregateOperations template;
99104
@Autowired NamedParameterJdbcOperations jdbcTemplate;
100105
@Autowired RelationalMappingContext mappingContext;
106+
@Autowired NamedParameterJdbcOperations jdbc;
101107

102108
LegoSet legoSet = createLegoSet("Star Destroyer");
103109

@@ -1149,6 +1155,115 @@ void readEnumArray() {
11491155
assertThat(template.findById(entity.id, EnumArrayOwner.class).digits).isEqualTo(new Color[] { Color.BLUE });
11501156
}
11511157

1158+
@Test // GH-1448
1159+
void multipleCollections() {
1160+
1161+
MultipleCollections aggregate = new MultipleCollections();
1162+
aggregate.name = "aggregate";
1163+
1164+
aggregate.listElements.add(new ListElement("one"));
1165+
aggregate.listElements.add(new ListElement("two"));
1166+
aggregate.listElements.add(new ListElement("three"));
1167+
1168+
aggregate.setElements.add(new SetElement("one"));
1169+
aggregate.setElements.add(new SetElement("two"));
1170+
1171+
aggregate.mapElements.put("alpha", new MapElement("one"));
1172+
aggregate.mapElements.put("beta", new MapElement("two"));
1173+
aggregate.mapElements.put("gamma", new MapElement("three"));
1174+
aggregate.mapElements.put("delta", new MapElement("four"));
1175+
1176+
template.save(aggregate);
1177+
1178+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1179+
1180+
assertSoftly(softly -> {
1181+
1182+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1183+
1184+
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
1185+
aggregate.listElements.get(1), aggregate.listElements.get(2));
1186+
1187+
softly.assertThat(reloaded.setElements)
1188+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1189+
1190+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1191+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1192+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1193+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1194+
});
1195+
}
1196+
1197+
@Test // GH-1448
1198+
void multipleCollectionsWithEmptySet() {
1199+
1200+
MultipleCollections aggregate = new MultipleCollections();
1201+
aggregate.name = "aggregate";
1202+
1203+
aggregate.listElements.add(new ListElement("one"));
1204+
aggregate.listElements.add(new ListElement("two"));
1205+
aggregate.listElements.add(new ListElement("three"));
1206+
1207+
aggregate.mapElements.put("alpha", new MapElement("one"));
1208+
aggregate.mapElements.put("beta", new MapElement("two"));
1209+
aggregate.mapElements.put("gamma", new MapElement("three"));
1210+
aggregate.mapElements.put("delta", new MapElement("four"));
1211+
1212+
template.save(aggregate);
1213+
1214+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1215+
1216+
assertSoftly(softly -> {
1217+
1218+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1219+
1220+
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
1221+
aggregate.listElements.get(1), aggregate.listElements.get(2));
1222+
1223+
softly.assertThat(reloaded.setElements)
1224+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1225+
1226+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1227+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1228+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1229+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1230+
});
1231+
}
1232+
1233+
@Test // GH-1448
1234+
void multipleCollectionsWithEmptyList() {
1235+
1236+
MultipleCollections aggregate = new MultipleCollections();
1237+
aggregate.name = "aggregate";
1238+
1239+
aggregate.setElements.add(new SetElement("one"));
1240+
aggregate.setElements.add(new SetElement("two"));
1241+
1242+
aggregate.mapElements.put("alpha", new MapElement("one"));
1243+
aggregate.mapElements.put("beta", new MapElement("two"));
1244+
aggregate.mapElements.put("gamma", new MapElement("three"));
1245+
aggregate.mapElements.put("delta", new MapElement("four"));
1246+
1247+
template.save(aggregate);
1248+
1249+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1250+
1251+
assertSoftly(softly -> {
1252+
1253+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1254+
1255+
softly.assertThat(reloaded.listElements).containsExactly();
1256+
1257+
softly.assertThat(reloaded.setElements)
1258+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1259+
1260+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1261+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1262+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1263+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1264+
});
1265+
}
1266+
11521267
private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate,
11531268
Function<Number, T> toConcreteNumber) {
11541269
saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0);
@@ -1858,6 +1973,24 @@ static class WithInsertOnly {
18581973
@InsertOnlyProperty String insertOnly;
18591974
}
18601975

1976+
@Table
1977+
static class MultipleCollections {
1978+
@Id Long id;
1979+
String name;
1980+
List<ListElement> listElements = new ArrayList<>();
1981+
Set<SetElement> setElements = new HashSet<>();
1982+
Map<String, MapElement> mapElements = new HashMap<>();
1983+
}
1984+
1985+
record ListElement(String name) {
1986+
}
1987+
1988+
record SetElement(String name) {
1989+
}
1990+
1991+
record MapElement(String name) {
1992+
}
1993+
18611994
@Configuration
18621995
@Import(TestConfiguration.class)
18631996
static class Config {

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql

+32-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ DROP TABLE WITH_ID_ONLY;
4141

4242
DROP TABLE WITH_INSERT_ONLY;
4343

44+
DROP TABLE MULTIPLE_COLLECTIONS;
45+
DROP TABLE MAP_ELEMENT;
46+
DROP TABLE LIST_ELEMENT;
47+
DROP TABLE SET_ELEMENT;
48+
4449
CREATE TABLE LEGO_SET
4550
(
4651
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@@ -365,4 +370,30 @@ CREATE TABLE WITH_INSERT_ONLY
365370
(
366371
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
367372
INSERT_ONLY VARCHAR(100)
368-
);
373+
);
374+
375+
CREATE TABLE MULTIPLE_COLLECTIONS
376+
(
377+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
378+
NAME VARCHAR(100)
379+
);
380+
381+
CREATE TABLE SET_ELEMENT
382+
(
383+
MULTIPLE_COLLECTIONS BIGINT,
384+
NAME VARCHAR(100)
385+
);
386+
387+
CREATE TABLE LIST_ELEMENT
388+
(
389+
MULTIPLE_COLLECTIONS BIGINT,
390+
MULTIPLE_COLLECTIONS_KEY INT,
391+
NAME VARCHAR(100)
392+
);
393+
394+
CREATE TABLE MAP_ELEMENT
395+
(
396+
MULTIPLE_COLLECTIONS BIGINT,
397+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
398+
NAME VARCHAR(100)
399+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql

+27-1
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,30 @@ CREATE TABLE WITH_INSERT_ONLY
332332
(
333333
ID SERIAL PRIMARY KEY,
334334
INSERT_ONLY VARCHAR(100)
335-
);
335+
);
336+
337+
CREATE TABLE MULTIPLE_COLLECTIONS
338+
(
339+
ID SERIAL PRIMARY KEY,
340+
NAME VARCHAR(100)
341+
);
342+
343+
CREATE TABLE SET_ELEMENT
344+
(
345+
MULTIPLE_COLLECTIONS BIGINT,
346+
NAME VARCHAR(100)
347+
);
348+
349+
CREATE TABLE LIST_ELEMENT
350+
(
351+
MULTIPLE_COLLECTIONS BIGINT,
352+
MULTIPLE_COLLECTIONS_KEY INT,
353+
NAME VARCHAR(100)
354+
);
355+
356+
CREATE TABLE MAP_ELEMENT
357+
(
358+
MULTIPLE_COLLECTIONS BIGINT,
359+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
360+
NAME VARCHAR(100)
361+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql

+27-1
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,30 @@ CREATE TABLE WITH_INSERT_ONLY
334334
CREATE TABLE WITH_ID_ONLY
335335
(
336336
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
337-
)
337+
);
338+
339+
CREATE TABLE MULTIPLE_COLLECTIONS
340+
(
341+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
342+
NAME VARCHAR(100)
343+
);
344+
345+
CREATE TABLE SET_ELEMENT
346+
(
347+
MULTIPLE_COLLECTIONS BIGINT,
348+
NAME VARCHAR(100)
349+
);
350+
351+
CREATE TABLE LIST_ELEMENT
352+
(
353+
MULTIPLE_COLLECTIONS BIGINT,
354+
MULTIPLE_COLLECTIONS_KEY INT,
355+
NAME VARCHAR(100)
356+
);
357+
358+
CREATE TABLE MAP_ELEMENT
359+
(
360+
MULTIPLE_COLLECTIONS BIGINT,
361+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
362+
NAME VARCHAR(100)
363+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql

+27-1
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,30 @@ CREATE TABLE WITH_INSERT_ONLY
307307
(
308308
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
309309
INSERT_ONLY VARCHAR(100)
310-
);
310+
);
311+
312+
CREATE TABLE MULTIPLE_COLLECTIONS
313+
(
314+
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
315+
NAME VARCHAR(100)
316+
);
317+
318+
CREATE TABLE SET_ELEMENT
319+
(
320+
MULTIPLE_COLLECTIONS BIGINT,
321+
NAME VARCHAR(100)
322+
);
323+
324+
CREATE TABLE LIST_ELEMENT
325+
(
326+
MULTIPLE_COLLECTIONS BIGINT,
327+
MULTIPLE_COLLECTIONS_KEY INT,
328+
NAME VARCHAR(100)
329+
);
330+
331+
CREATE TABLE MAP_ELEMENT
332+
(
333+
MULTIPLE_COLLECTIONS BIGINT,
334+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
335+
NAME VARCHAR(100)
336+
);

0 commit comments

Comments
 (0)