From 41524ea1492115bd3b58fa3b949bfca450625222 Mon Sep 17 00:00:00 2001 From: mhyeon-lee Date: Sat, 17 Dec 2022 21:17:55 +0900 Subject: [PATCH] Add insertAll and updateAll for JdbcAggregateOperations --- .../jdbc/core/JdbcAggregateOperations.java | 24 +++++++ .../data/jdbc/core/JdbcAggregateTemplate.java | 67 ++++++++++++++----- ...JdbcAggregateTemplateIntegrationTests.java | 24 +++++++ 3 files changed, 99 insertions(+), 16 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java index 40ac59b023..089c2415f3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java @@ -33,6 +33,7 @@ * @author Milan Milanov * @author Chirag Tailor * @author Diego Krupitza + * @author Myeonghyeon Lee */ public interface JdbcAggregateOperations { @@ -71,6 +72,19 @@ public interface JdbcAggregateOperations { */ T insert(T instance); + /** + * Inserts all aggregate instances, including all the members of each aggregate instance. + *

+ * This is useful if the client provides an id for new aggregate roots. + *

+ * + * @param instances the aggregate roots to be inserted. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the saved instances. + * @since 3.1 + */ + Iterable insertAll(Iterable instances); + /** * Dedicated update function. This skips the test if the aggregate root is new or not and always performs an update * operation. @@ -81,6 +95,16 @@ public interface JdbcAggregateOperations { */ T update(T instance); + /** + * Updates all aggregate instances, including all the members of each aggregate instance. + * + * @param instances the aggregate roots to be inserted. Must not be {@code null}. + * @param the type of the aggregate root. + * @return the saved instances. + * @since 3.1 + */ + Iterable updateAll(Iterable instances); + /** * Counts the number of aggregates of a given type. * diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index 01c4dcf8b9..02455011b1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -164,7 +164,7 @@ public T save(T instance) { Assert.notNull(instance, "Aggregate instance must not be null"); - return performSave(instance, changeCreatorSelectorForSave(instance)); + return performSave(new EntityAndChangeCreator<>(instance, changeCreatorSelectorForSave(instance))); } @Override @@ -172,7 +172,11 @@ public Iterable saveAll(Iterable instances) { Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty"); - return performSaveAll(instances); + List> entityAndChangeCreators = new ArrayList<>(); + for (T instance : instances) { + entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changeCreatorSelectorForSave(instance))); + } + return performSaveAll(entityAndChangeCreators); } /** @@ -187,7 +191,21 @@ public T insert(T instance) { Assert.notNull(instance, "Aggregate instance must not be null"); - return performSave(instance, entity -> createInsertChange(prepareVersionForInsert(entity))); + return performSave(new EntityAndChangeCreator<>( + instance, entity -> createInsertChange(prepareVersionForInsert(entity)))); + } + + @Override + public Iterable insertAll(Iterable instances) { + + Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty"); + + List> entityAndChangeCreators = new ArrayList<>(); + for (T instance : instances) { + entityAndChangeCreators.add(new EntityAndChangeCreator<>( + instance, entity -> createInsertChange(prepareVersionForInsert(entity)))); + } + return performSaveAll(entityAndChangeCreators); } /** @@ -202,7 +220,21 @@ public T update(T instance) { Assert.notNull(instance, "Aggregate instance must not be null"); - return performSave(instance, entity -> createUpdateChange(prepareVersionForUpdate(entity))); + return performSave(new EntityAndChangeCreator<>( + instance, entity -> createUpdateChange(prepareVersionForUpdate(entity)))); + } + + @Override + public Iterable updateAll(Iterable instances) { + + Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty"); + + List> entityAndChangeCreators = new ArrayList<>(); + for (T instance : instances) { + entityAndChangeCreators.add(new EntityAndChangeCreator<>( + instance, entity -> createUpdateChange(prepareVersionForUpdate(entity)))); + } + return performSaveAll(entityAndChangeCreators); } @Override @@ -401,13 +433,13 @@ private T afterExecute(AggregateChange change, T entityAfterExecution) { return triggerAfterSave(entityAfterExecution, change); } - private RootAggregateChange beforeExecute(T aggregateRoot, Function> changeCreator) { + private RootAggregateChange beforeExecute(EntityAndChangeCreator instance) { - Assert.notNull(aggregateRoot, "Aggregate instance must not be null"); + Assert.notNull(instance.entity, "Aggregate instance must not be null"); - aggregateRoot = triggerBeforeConvert(aggregateRoot); + T aggregateRoot = triggerBeforeConvert(instance.entity); - RootAggregateChange change = changeCreator.apply(aggregateRoot); + RootAggregateChange change = instance.changeCreator.apply(aggregateRoot); aggregateRoot = triggerBeforeSave(change.getRoot(), change); @@ -427,12 +459,12 @@ private void deleteTree(Object id, @Nullable T entity, Class domainType) triggerAfterDelete(entity, id, change); } - private T performSave(T instance, Function> changeCreator) { + private T performSave(EntityAndChangeCreator instance) { // noinspection unchecked BatchingAggregateChange> batchingAggregateChange = // - BatchingAggregateChange.forSave((Class) ClassUtils.getUserClass(instance)); - batchingAggregateChange.add(beforeExecute(instance, changeCreator)); + BatchingAggregateChange.forSave((Class) ClassUtils.getUserClass(instance.entity)); + batchingAggregateChange.add(beforeExecute(instance)); Iterator afterExecutionIterator = executor.executeSave(batchingAggregateChange).iterator(); @@ -441,16 +473,16 @@ private T performSave(T instance, Function> change return afterExecute(batchingAggregateChange, afterExecutionIterator.next()); } - private List performSaveAll(Iterable instances) { - + private List performSaveAll(Iterable> instances) { BatchingAggregateChange> batchingAggregateChange = null; - for (T instance : instances) { + for (EntityAndChangeCreator instance : instances) { if (batchingAggregateChange == null) { // noinspection unchecked - batchingAggregateChange = BatchingAggregateChange.forSave((Class) ClassUtils.getUserClass(instance)); + batchingAggregateChange = BatchingAggregateChange.forSave( + (Class) ClassUtils.getUserClass(instance.entity)); } - batchingAggregateChange.add(beforeExecute(instance, changeCreatorSelectorForSave(instance))); + batchingAggregateChange.add(beforeExecute(instance)); } Assert.notNull(batchingAggregateChange, "Iterable in saveAll must not be empty"); @@ -604,4 +636,7 @@ private T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableA private record EntityAndPreviousVersion (T entity, @Nullable Number version) { } + + private record EntityAndChangeCreator (T entity, Function> changeCreator) { + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index b3cbcf3b8c..4d92ffd315 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -384,6 +384,30 @@ void saveAndDeleteAllByAggregateRootsWithVersion() { assertThat(template.count(AggregateWithImmutableVersion.class)).isEqualTo(0); } + @Test // GH-1395 + void insertAndUpdateAllByAggregateRootsWithVersion() { + + AggregateWithImmutableVersion aggregate1 = new AggregateWithImmutableVersion(null, null); + AggregateWithImmutableVersion aggregate2 = new AggregateWithImmutableVersion(null, null); + AggregateWithImmutableVersion aggregate3 = new AggregateWithImmutableVersion(null, null); + Iterator savedAggregatesIterator = template + .insertAll(List.of(aggregate1, aggregate2, aggregate3)).iterator(); + assertThat(template.count(AggregateWithImmutableVersion.class)).isEqualTo(3); + + AggregateWithImmutableVersion savedAggregate1 = savedAggregatesIterator.next(); + AggregateWithImmutableVersion twiceSavedAggregate2 = template.save(savedAggregatesIterator.next()); + AggregateWithImmutableVersion twiceSavedAggregate3 = template.save(savedAggregatesIterator.next()); + + savedAggregatesIterator = template.updateAll( + List.of(savedAggregate1, twiceSavedAggregate2, twiceSavedAggregate3)).iterator(); + + assertThat(savedAggregatesIterator.next().version).isEqualTo(1); + assertThat(savedAggregatesIterator.next().version).isEqualTo(2); + assertThat(savedAggregatesIterator.next().version).isEqualTo(2); + + AggregateWithImmutableVersion.clearConstructorInvocationData(); + } + @Test // DATAJDBC-112 @EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES }) void updateReferencedEntityFromNull() {