Skip to content

Commit 03a8f98

Browse files
schaudermp911de
authored andcommitted
Support Single Query Loading for aggregates with more than one collection.
Closes #1448
1 parent 77b4675 commit 03a8f98

13 files changed

+448
-38
lines changed

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* Query Loading.
3030
*
3131
* @author Mark Paluch
32+
* @author Jens Schauder
3233
* @since 3.2
3334
*/
3435
class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy {
@@ -120,23 +121,25 @@ private boolean isSingleSelectQuerySupported(Class<?> entityType) {
120121

121122
private boolean entityQualifiesForSingleQueryLoading(Class<?> entityType) {
122123

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

129+
// single references are currently not supported
130+
if (!(property.isMap() || property.isCollectionLike())) {
131+
return false;
132+
}
133+
129134
// embedded entities are currently not supported
130135
if (property.isEmbedded()) {
131136
return false;
132137
}
133138

134-
// only a single reference is currently supported
135-
if (referenceFound) {
139+
// nested references are currently not supported
140+
if (path.getLength() > 1) {
136141
return false;
137142
}
138-
139-
referenceFound = true;
140143
}
141144
}
142145
return true;

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

+133
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static org.springframework.data.jdbc.testing.TestConfiguration.*;
2323
import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*;
2424

25+
import java.sql.ResultSet;
26+
import java.sql.SQLException;
2527
import java.time.LocalDateTime;
2628
import java.util.ArrayList;
2729
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.springframework.beans.factory.annotation.Autowired;
4245
import org.springframework.context.ApplicationEventPublisher;
4346
import org.springframework.context.annotation.Bean;
4447
import org.springframework.context.annotation.Configuration;
4548
import org.springframework.context.annotation.Import;
4649
import org.springframework.dao.IncorrectResultSizeDataAccessException;
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;
@@ -70,6 +74,7 @@
7074
import org.springframework.data.relational.core.query.Criteria;
7175
import org.springframework.data.relational.core.query.CriteriaDefinition;
7276
import org.springframework.data.relational.core.query.Query;
77+
import org.springframework.jdbc.core.ResultSetExtractor;
7378
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
7479
import org.springframework.test.context.ActiveProfiles;
7580
import org.springframework.test.context.ContextConfiguration;
@@ -95,6 +100,7 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
95100
@Autowired JdbcAggregateOperations template;
96101
@Autowired NamedParameterJdbcOperations jdbcTemplate;
97102
@Autowired RelationalMappingContext mappingContext;
103+
@Autowired NamedParameterJdbcOperations jdbc;
98104

99105
LegoSet legoSet = createLegoSet("Star Destroyer");
100106

@@ -1202,6 +1208,115 @@ void readEnumArray() {
12021208
assertThat(template.findById(entity.id, EnumArrayOwner.class).digits).isEqualTo(new Color[] { Color.BLUE });
12031209
}
12041210

1211+
@Test // GH-1448
1212+
void multipleCollections() {
1213+
1214+
MultipleCollections aggregate = new MultipleCollections();
1215+
aggregate.name = "aggregate";
1216+
1217+
aggregate.listElements.add(new ListElement("one"));
1218+
aggregate.listElements.add(new ListElement("two"));
1219+
aggregate.listElements.add(new ListElement("three"));
1220+
1221+
aggregate.setElements.add(new SetElement("one"));
1222+
aggregate.setElements.add(new SetElement("two"));
1223+
1224+
aggregate.mapElements.put("alpha", new MapElement("one"));
1225+
aggregate.mapElements.put("beta", new MapElement("two"));
1226+
aggregate.mapElements.put("gamma", new MapElement("three"));
1227+
aggregate.mapElements.put("delta", new MapElement("four"));
1228+
1229+
template.save(aggregate);
1230+
1231+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1232+
1233+
assertSoftly(softly -> {
1234+
1235+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1236+
1237+
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
1238+
aggregate.listElements.get(1), aggregate.listElements.get(2));
1239+
1240+
softly.assertThat(reloaded.setElements)
1241+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1242+
1243+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1244+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1245+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1246+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1247+
});
1248+
}
1249+
1250+
@Test // GH-1448
1251+
void multipleCollectionsWithEmptySet() {
1252+
1253+
MultipleCollections aggregate = new MultipleCollections();
1254+
aggregate.name = "aggregate";
1255+
1256+
aggregate.listElements.add(new ListElement("one"));
1257+
aggregate.listElements.add(new ListElement("two"));
1258+
aggregate.listElements.add(new ListElement("three"));
1259+
1260+
aggregate.mapElements.put("alpha", new MapElement("one"));
1261+
aggregate.mapElements.put("beta", new MapElement("two"));
1262+
aggregate.mapElements.put("gamma", new MapElement("three"));
1263+
aggregate.mapElements.put("delta", new MapElement("four"));
1264+
1265+
template.save(aggregate);
1266+
1267+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1268+
1269+
assertSoftly(softly -> {
1270+
1271+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1272+
1273+
softly.assertThat(reloaded.listElements).containsExactly(aggregate.listElements.get(0),
1274+
aggregate.listElements.get(1), aggregate.listElements.get(2));
1275+
1276+
softly.assertThat(reloaded.setElements)
1277+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1278+
1279+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1280+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1281+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1282+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1283+
});
1284+
}
1285+
1286+
@Test // GH-1448
1287+
void multipleCollectionsWithEmptyList() {
1288+
1289+
MultipleCollections aggregate = new MultipleCollections();
1290+
aggregate.name = "aggregate";
1291+
1292+
aggregate.setElements.add(new SetElement("one"));
1293+
aggregate.setElements.add(new SetElement("two"));
1294+
1295+
aggregate.mapElements.put("alpha", new MapElement("one"));
1296+
aggregate.mapElements.put("beta", new MapElement("two"));
1297+
aggregate.mapElements.put("gamma", new MapElement("three"));
1298+
aggregate.mapElements.put("delta", new MapElement("four"));
1299+
1300+
template.save(aggregate);
1301+
1302+
MultipleCollections reloaded = template.findById(aggregate.id, MultipleCollections.class);
1303+
1304+
assertSoftly(softly -> {
1305+
1306+
softly.assertThat(reloaded.name).isEqualTo(aggregate.name);
1307+
1308+
softly.assertThat(reloaded.listElements).containsExactly();
1309+
1310+
softly.assertThat(reloaded.setElements)
1311+
.containsExactlyInAnyOrder(aggregate.setElements.toArray(new SetElement[0]));
1312+
1313+
softly.assertThat(reloaded.mapElements.get("alpha")).isEqualTo(new MapElement("one"));
1314+
softly.assertThat(reloaded.mapElements.get("beta")).isEqualTo(new MapElement("two"));
1315+
softly.assertThat(reloaded.mapElements.get("gamma")).isEqualTo(new MapElement("three"));
1316+
softly.assertThat(reloaded.mapElements.get("delta")).isEqualTo(new MapElement("four"));
1317+
});
1318+
}
1319+
12051320
private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate,
12061321
Function<Number, T> toConcreteNumber) {
12071322
saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0);
@@ -1932,6 +2047,24 @@ static class WithInsertOnly {
19322047
@InsertOnlyProperty String insertOnly;
19332048
}
19342049

2050+
@Table
2051+
static class MultipleCollections {
2052+
@Id Long id;
2053+
String name;
2054+
List<ListElement> listElements = new ArrayList<>();
2055+
Set<SetElement> setElements = new HashSet<>();
2056+
Map<String, MapElement> mapElements = new HashMap<>();
2057+
}
2058+
2059+
record ListElement(String name) {
2060+
}
2061+
2062+
record SetElement(String name) {
2063+
}
2064+
2065+
record MapElement(String name) {
2066+
}
2067+
19352068
@Configuration
19362069
@Import(TestConfiguration.class)
19372070
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
@@ -42,6 +42,11 @@ DROP TABLE WITH_ID_ONLY;
4242

4343
DROP TABLE WITH_INSERT_ONLY;
4444

45+
DROP TABLE MULTIPLE_COLLECTIONS;
46+
DROP TABLE MAP_ELEMENT;
47+
DROP TABLE LIST_ELEMENT;
48+
DROP TABLE SET_ELEMENT;
49+
4550
CREATE TABLE LEGO_SET
4651
(
4752
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@@ -373,4 +378,30 @@ CREATE TABLE WITH_INSERT_ONLY
373378
(
374379
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
375380
INSERT_ONLY VARCHAR(100)
376-
);
381+
);
382+
383+
CREATE TABLE MULTIPLE_COLLECTIONS
384+
(
385+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
386+
NAME VARCHAR(100)
387+
);
388+
389+
CREATE TABLE SET_ELEMENT
390+
(
391+
MULTIPLE_COLLECTIONS BIGINT,
392+
NAME VARCHAR(100)
393+
);
394+
395+
CREATE TABLE LIST_ELEMENT
396+
(
397+
MULTIPLE_COLLECTIONS BIGINT,
398+
MULTIPLE_COLLECTIONS_KEY INT,
399+
NAME VARCHAR(100)
400+
);
401+
402+
CREATE TABLE MAP_ELEMENT
403+
(
404+
MULTIPLE_COLLECTIONS BIGINT,
405+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
406+
NAME VARCHAR(100)
407+
);

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,30 @@ CREATE TABLE WITH_INSERT_ONLY
340340
(
341341
ID SERIAL PRIMARY KEY,
342342
INSERT_ONLY VARCHAR(100)
343-
);
343+
);
344+
345+
CREATE TABLE MULTIPLE_COLLECTIONS
346+
(
347+
ID SERIAL PRIMARY KEY,
348+
NAME VARCHAR(100)
349+
);
350+
351+
CREATE TABLE SET_ELEMENT
352+
(
353+
MULTIPLE_COLLECTIONS BIGINT,
354+
NAME VARCHAR(100)
355+
);
356+
357+
CREATE TABLE LIST_ELEMENT
358+
(
359+
MULTIPLE_COLLECTIONS BIGINT,
360+
MULTIPLE_COLLECTIONS_KEY INT,
361+
NAME VARCHAR(100)
362+
);
363+
364+
CREATE TABLE MAP_ELEMENT
365+
(
366+
MULTIPLE_COLLECTIONS BIGINT,
367+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
368+
NAME VARCHAR(100)
369+
);

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,30 @@ CREATE TABLE WITH_INSERT_ONLY
341341
CREATE TABLE WITH_ID_ONLY
342342
(
343343
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
344-
)
344+
);
345+
346+
CREATE TABLE MULTIPLE_COLLECTIONS
347+
(
348+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
349+
NAME VARCHAR(100)
350+
);
351+
352+
CREATE TABLE SET_ELEMENT
353+
(
354+
MULTIPLE_COLLECTIONS BIGINT,
355+
NAME VARCHAR(100)
356+
);
357+
358+
CREATE TABLE LIST_ELEMENT
359+
(
360+
MULTIPLE_COLLECTIONS BIGINT,
361+
MULTIPLE_COLLECTIONS_KEY INT,
362+
NAME VARCHAR(100)
363+
);
364+
365+
CREATE TABLE MAP_ELEMENT
366+
(
367+
MULTIPLE_COLLECTIONS BIGINT,
368+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
369+
NAME VARCHAR(100)
370+
);

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,30 @@ CREATE TABLE WITH_INSERT_ONLY
314314
(
315315
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
316316
INSERT_ONLY VARCHAR(100)
317-
);
317+
);
318+
319+
CREATE TABLE MULTIPLE_COLLECTIONS
320+
(
321+
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
322+
NAME VARCHAR(100)
323+
);
324+
325+
CREATE TABLE SET_ELEMENT
326+
(
327+
MULTIPLE_COLLECTIONS BIGINT,
328+
NAME VARCHAR(100)
329+
);
330+
331+
CREATE TABLE LIST_ELEMENT
332+
(
333+
MULTIPLE_COLLECTIONS BIGINT,
334+
MULTIPLE_COLLECTIONS_KEY INT,
335+
NAME VARCHAR(100)
336+
);
337+
338+
CREATE TABLE MAP_ELEMENT
339+
(
340+
MULTIPLE_COLLECTIONS BIGINT,
341+
MULTIPLE_COLLECTIONS_KEY VARCHAR(10),
342+
NAME VARCHAR(100)
343+
);

0 commit comments

Comments
 (0)