Skip to content

Commit 12f219f

Browse files
committed
Polishing and documentation.
1 parent 9dd8e4f commit 12f219f

24 files changed

+425
-208
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
* @author Jens Schauder
2727
* @author Thomas Lang
2828
* @author Milan Milanov
29+
* @author Chirag Tailor
2930
*/
3031
public interface JdbcAggregateOperations {
3132

@@ -38,6 +39,14 @@ public interface JdbcAggregateOperations {
3839
*/
3940
<T> T save(T instance);
4041

42+
/**
43+
* Saves all aggregate instances, including all the members of each aggregate instance.
44+
*
45+
* @param instances the aggregate roots to be saved. Must not be {@code null}.
46+
* @param <T> the type of the aggregate root.
47+
* @return the saved instances.
48+
* @since 3.0
49+
*/
4150
<T> Iterable<T> saveAll(Iterable<T> instances);
4251

4352
/**

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

+5
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ public void deleteAll(Class<?> domainType) {
282282
}
283283

284284
private <T> T afterExecute(AggregateChange<T> change, T entityAfterExecution) {
285+
285286
Object identifier = context.getRequiredPersistentEntity(change.getEntityType())
286287
.getIdentifierAccessor(entityAfterExecution).getIdentifier();
287288

@@ -292,6 +293,7 @@ private <T> T afterExecute(AggregateChange<T> change, T entityAfterExecution) {
292293

293294
private <T> AggregateChangeWithRoot<T> beforeExecute(T aggregateRoot,
294295
Function<T, AggregateChangeWithRoot<T>> changeCreator) {
296+
295297
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
296298

297299
aggregateRoot = triggerBeforeConvert(aggregateRoot);
@@ -301,6 +303,7 @@ private <T> AggregateChangeWithRoot<T> beforeExecute(T aggregateRoot,
301303
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
302304

303305
change.setRoot(aggregateRoot);
306+
304307
return change;
305308
}
306309

@@ -316,6 +319,7 @@ private <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType)
316319
}
317320

318321
private <T> List<T> performSaveChange(Iterable<T> instances, Function<T, Function<T, AggregateChangeWithRoot<T>>> changeCreatorSelector) {
322+
319323
ArrayList<T> instancesList = new ArrayList<>();
320324
instances.forEach(instancesList::add);
321325
Assert.notEmpty(instancesList, "Aggregate instances must not be empty!");
@@ -332,6 +336,7 @@ private <T> List<T> performSaveChange(Iterable<T> instances, Function<T, Functio
332336
}
333337

334338
private <T> Function<T, AggregateChangeWithRoot<T>> changeCreatorSelectorForSave(T instance) {
339+
335340
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
336341
? changeCreatorSelectorForInsert(instance)
337342
: changeCreatorSelectorForUpdate(instance);

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
1616
package org.springframework.data.jdbc.repository.support;
1717

1818
import java.util.Optional;
19-
import java.util.stream.Collectors;
2019

2120
import org.springframework.data.domain.Page;
2221
import org.springframework.data.domain.Pageable;
@@ -25,7 +24,6 @@
2524
import org.springframework.data.mapping.PersistentEntity;
2625
import org.springframework.data.repository.CrudRepository;
2726
import org.springframework.data.repository.PagingAndSortingRepository;
28-
import org.springframework.data.util.Streamable;
2927
import org.springframework.transaction.annotation.Transactional;
3028
import org.springframework.util.Assert;
3129

@@ -35,6 +33,7 @@
3533
* @author Jens Schauder
3634
* @author Oliver Gierke
3735
* @author Milan Milanov
36+
* @author Chirag Tailor
3837
*/
3938
@Transactional(readOnly = true)
4039
public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, PagingAndSortingRepository<T, ID> {

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@
4646
*
4747
* @author Jens Schauder
4848
* @author Salim Achouche
49-
* @author Chirag Taylor
49+
* @author Chirag Tailor
5050
*/
5151
@ContextConfiguration
5252
@Transactional

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* Test for the {@link JdbcAggregateChangeExecutionContext} when operating on immutable classes.
4747
*
4848
* @author Jens Schauder
49-
* @author Chirag Taylor
49+
* @author Chirag Tailor
5050
*/
5151
public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
5252

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

+113-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jdbc.repository;
1717

1818
import static java.util.Arrays.*;
19+
import static java.util.Collections.*;
1920
import static org.assertj.core.api.Assertions.*;
2021
import static org.assertj.core.api.SoftAssertions.*;
2122
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
@@ -50,6 +51,7 @@
5051
import org.springframework.data.domain.Pageable;
5152
import org.springframework.data.domain.Slice;
5253
import org.springframework.data.jdbc.core.mapping.AggregateReference;
54+
import org.springframework.data.relational.core.mapping.MappedCollection;
5355
import org.springframework.data.relational.repository.Lock;
5456
import org.springframework.data.jdbc.repository.query.Modifying;
5557
import org.springframework.data.jdbc.repository.query.Query;
@@ -63,6 +65,7 @@
6365
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
6466
import org.springframework.data.relational.core.sql.LockMode;
6567
import org.springframework.data.repository.CrudRepository;
68+
import org.springframework.data.repository.ListCrudRepository;
6669
import org.springframework.data.repository.core.NamedQueries;
6770
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
6871
import org.springframework.data.repository.query.Param;
@@ -80,6 +83,7 @@
8083
*
8184
* @author Jens Schauder
8285
* @author Mark Paluch
86+
* @author Chirag Tailor
8387
*/
8488
@Transactional
8589
@TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
@@ -89,6 +93,7 @@ public class JdbcRepositoryIntegrationTests {
8993
@Autowired NamedParameterJdbcTemplate template;
9094
@Autowired DummyEntityRepository repository;
9195
@Autowired MyEventListener eventListener;
96+
@Autowired RootRepository rootRepository;
9297

9398
private static DummyEntity createDummyEntity() {
9499

@@ -282,7 +287,7 @@ public void updateMany() {
282287
.containsExactlyInAnyOrder(entity.getName(), other.getName());
283288
}
284289

285-
@Test
290+
@Test // GH-537
286291
void insertsOrUpdatesManyEntities() {
287292

288293
DummyEntity entity = repository.save(createDummyEntity());
@@ -589,6 +594,84 @@ void nullStringResult() {
589594
assertThat(repository.returnInput(null)).isNull();
590595
}
591596

597+
@Test // GH-537
598+
void manyInsertsWithNestedEntities() {
599+
Root root1 = createRoot("root1");
600+
Root root2 = createRoot("root2");
601+
602+
List<Root> savedRoots = rootRepository.saveAll(asList(root1, root2));
603+
604+
List<Root> reloadedRoots = rootRepository.findAll();
605+
assertThat(reloadedRoots).isEqualTo(savedRoots);
606+
assertThat(reloadedRoots).hasSize(2);
607+
assertIsEqualToWithNonNullIds(reloadedRoots.get(0), root1);
608+
assertIsEqualToWithNonNullIds(reloadedRoots.get(1), root2);
609+
}
610+
611+
@Test // GH-537
612+
@EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
613+
void manyUpdatesWithNestedEntities() {
614+
Root root1 = createRoot("root1");
615+
Root root2 = createRoot("root2");
616+
List<Root> roots = rootRepository.saveAll(asList(root1, root2));
617+
Root savedRoot1 = roots.get(0);
618+
Root updatedRoot1 = new Root(savedRoot1.id, "updated" + savedRoot1.name,
619+
new Intermediate(savedRoot1.intermediate.id, "updated" + savedRoot1.intermediate.name,
620+
new Leaf(savedRoot1.intermediate.leaf.id, "updated" + savedRoot1.intermediate.leaf.name), emptyList()),
621+
savedRoot1.intermediates);
622+
Root savedRoot2 = roots.get(1);
623+
Root updatedRoot2 = new Root(savedRoot2.id, "updated" + savedRoot2.name, savedRoot2.intermediate,
624+
singletonList(
625+
new Intermediate(savedRoot2.intermediates.get(0).id, "updated" + savedRoot2.intermediates.get(0).name, null,
626+
singletonList(new Leaf(savedRoot2.intermediates.get(0).leaves.get(0).id,
627+
"updated" + savedRoot2.intermediates.get(0).leaves.get(0).name)))));
628+
629+
List<Root> updatedRoots = rootRepository.saveAll(asList(updatedRoot1, updatedRoot2));
630+
631+
List<Root> reloadedRoots = rootRepository.findAll();
632+
assertThat(reloadedRoots).isEqualTo(updatedRoots);
633+
assertThat(reloadedRoots).containsExactly(updatedRoot1, updatedRoot2);
634+
}
635+
636+
@Test // GH-537
637+
@EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
638+
void manyInsertsAndUpdatesWithNestedEntities() {
639+
Root root1 = createRoot("root1");
640+
Root savedRoot1 = rootRepository.save(root1);
641+
Root updatedRoot1 = new Root(savedRoot1.id, "updated" + savedRoot1.name,
642+
new Intermediate(savedRoot1.intermediate.id, "updated" + savedRoot1.intermediate.name,
643+
new Leaf(savedRoot1.intermediate.leaf.id, "updated" + savedRoot1.intermediate.leaf.name), emptyList()),
644+
savedRoot1.intermediates);
645+
Root root2 = createRoot("root2");
646+
List<Root> savedRoots = rootRepository.saveAll(asList(updatedRoot1, root2));
647+
648+
List<Root> reloadedRoots = rootRepository.findAll();
649+
assertThat(reloadedRoots).isEqualTo(savedRoots);
650+
assertThat(reloadedRoots.get(0)).isEqualTo(updatedRoot1);
651+
assertIsEqualToWithNonNullIds(reloadedRoots.get(1), root2);
652+
}
653+
654+
private Root createRoot(String namePrefix) {
655+
return new Root(null, namePrefix,
656+
new Intermediate(null, namePrefix + "Intermediate", new Leaf(null, namePrefix + "Leaf"), emptyList()),
657+
singletonList(new Intermediate(null, namePrefix + "QualifiedIntermediate", null,
658+
singletonList(new Leaf(null, namePrefix + "QualifiedLeaf")))));
659+
}
660+
661+
private void assertIsEqualToWithNonNullIds(Root reloadedRoot1, Root root1) {
662+
assertThat(reloadedRoot1.id).isNotNull();
663+
assertThat(reloadedRoot1.name).isEqualTo(root1.name);
664+
assertThat(reloadedRoot1.intermediate.id).isNotNull();
665+
assertThat(reloadedRoot1.intermediate.name).isEqualTo(root1.intermediate.name);
666+
assertThat(reloadedRoot1.intermediates.get(0).id).isNotNull();
667+
assertThat(reloadedRoot1.intermediates.get(0).name).isEqualTo(root1.intermediates.get(0).name);
668+
assertThat(reloadedRoot1.intermediate.leaf.id).isNotNull();
669+
assertThat(reloadedRoot1.intermediate.leaf.name).isEqualTo(root1.intermediate.leaf.name);
670+
assertThat(reloadedRoot1.intermediates.get(0).leaves.get(0).id).isNotNull();
671+
assertThat(reloadedRoot1.intermediates.get(0).leaves.get(0).name)
672+
.isEqualTo(root1.intermediates.get(0).leaves.get(0).name);
673+
}
674+
592675
private Instant createDummyBeforeAndAfterNow() {
593676

594677
Instant now = Instant.now();
@@ -692,6 +775,11 @@ DummyEntityRepository dummyEntityRepository() {
692775
return factory.getRepository(DummyEntityRepository.class);
693776
}
694777

778+
@Bean
779+
RootRepository rootRepository() {
780+
return factory.getRepository(RootRepository.class);
781+
}
782+
695783
@Bean
696784
NamedQueries namedQueries() throws IOException {
697785

@@ -707,6 +795,30 @@ MyEventListener eventListener() {
707795
}
708796
}
709797

798+
interface RootRepository extends ListCrudRepository<Root, Long> {}
799+
800+
@Value
801+
static class Root {
802+
@Id Long id;
803+
String name;
804+
Intermediate intermediate;
805+
@MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") List<Intermediate> intermediates;
806+
}
807+
808+
@Value
809+
static class Intermediate {
810+
@Id Long id;
811+
String name;
812+
Leaf leaf;
813+
@MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") List<Leaf> leaves;
814+
}
815+
816+
@Value
817+
static class Leaf {
818+
@Id Long id;
819+
String name;
820+
}
821+
710822
static class MyEventListener implements ApplicationListener<AbstractRelationalEvent<?>> {
711823

712824
private List<AbstractRelationalEvent<?>> events = new ArrayList<>();

0 commit comments

Comments
 (0)