instances);
+
/**
* Dedicated insert function. This skips the test if the aggregate root is new and makes an insert.
*
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 24c2de8689..88a61e65c1 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
@@ -16,6 +16,7 @@
package org.springframework.data.jdbc.core;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -31,7 +32,8 @@
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.conversion.AggregateChange;
-import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
+import org.springframework.data.relational.core.conversion.RootAggregateChange;
+import org.springframework.data.relational.core.conversion.BatchingAggregateChange;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import org.springframework.data.relational.core.conversion.RelationalEntityDeleteWriter;
import org.springframework.data.relational.core.conversion.RelationalEntityInsertWriter;
@@ -44,6 +46,7 @@
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* {@link JdbcAggregateOperations} implementation, storing aggregates in and obtaining them from a JDBC data store.
@@ -139,13 +142,15 @@ public T save(T instance) {
Assert.notNull(instance, "Aggregate instance must not be null!");
- RelationalPersistentEntity> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
+ return performSave(instance, changeCreatorSelectorForSave(instance));
+ }
- Function> changeCreator = persistentEntity.isNew(instance)
- ? entity -> createInsertChange(prepareVersionForInsert(entity))
- : entity -> createUpdateChange(prepareVersionForUpdate(entity));
+ @Override
+ public Iterable saveAll(Iterable instances) {
+
+ Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty!");
- return store(instance, changeCreator, persistentEntity);
+ return performSaveAll(instances);
}
/**
@@ -160,9 +165,7 @@ public T insert(T instance) {
Assert.notNull(instance, "Aggregate instance must not be null!");
- RelationalPersistentEntity> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
-
- return store(instance, entity -> createInsertChange(prepareVersionForInsert(entity)), persistentEntity);
+ return performSave(instance, entity -> createInsertChange(prepareVersionForInsert(entity)));
}
/**
@@ -177,9 +180,7 @@ public T update(T instance) {
Assert.notNull(instance, "Aggregate instance must not be null!");
- RelationalPersistentEntity> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
-
- return store(instance, entity -> createUpdateChange(prepareVersionForUpdate(entity)), persistentEntity);
+ return performSave(instance, entity -> createUpdateChange(prepareVersionForUpdate(entity)));
}
@Override
@@ -278,29 +279,33 @@ public void deleteAll(Class> domainType) {
Assert.notNull(domainType, "Domain type must not be null!");
MutableAggregateChange> change = createDeletingChange(domainType);
- executor.execute(change);
+ executor.executeDelete(change);
+ }
+
+ private T afterExecute(AggregateChange change, T entityAfterExecution) {
+
+ Object identifier = context.getRequiredPersistentEntity(change.getEntityType())
+ .getIdentifierAccessor(entityAfterExecution).getIdentifier();
+
+ Assert.notNull(identifier, "After saving the identifier must not be null!");
+
+ return triggerAfterSave(entityAfterExecution, change);
}
- private T store(T aggregateRoot, Function> changeCreator,
- RelationalPersistentEntity> persistentEntity) {
+ private RootAggregateChange beforeExecute(T aggregateRoot,
+ Function> changeCreator) {
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
aggregateRoot = triggerBeforeConvert(aggregateRoot);
- AggregateChangeWithRoot change = changeCreator.apply(aggregateRoot);
+ RootAggregateChange change = changeCreator.apply(aggregateRoot);
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
change.setRoot(aggregateRoot);
- T entityAfterExecution = executor.execute(change);
-
- Object identifier = persistentEntity.getIdentifierAccessor(entityAfterExecution).getIdentifier();
-
- Assert.notNull(identifier, "After saving the identifier must not be null!");
-
- return triggerAfterSave(entityAfterExecution, change);
+ return change;
}
private void deleteTree(Object id, @Nullable T entity, Class domainType) {
@@ -309,23 +314,70 @@ private void deleteTree(Object id, @Nullable T entity, Class domainType)
entity = triggerBeforeDelete(entity, id, change);
- executor.execute(change);
+ executor.executeDelete(change);
triggerAfterDelete(entity, id, change);
}
- private AggregateChangeWithRoot createInsertChange(T instance) {
+ private T performSave(T instance, Function> changeCreator) {
+
+ // noinspection unchecked
+ BatchingAggregateChange> batchingAggregateChange = //
+ BatchingAggregateChange.forSave((Class) ClassUtils.getUserClass(instance));
+ batchingAggregateChange.add(beforeExecute(instance, changeCreator));
+
+ Iterator afterExecutionIterator = executor.executeSave(batchingAggregateChange).iterator();
+
+ Assert.isTrue(afterExecutionIterator.hasNext(), "Instances after execution must not be empty!");
+
+ return afterExecute(batchingAggregateChange, afterExecutionIterator.next());
+ }
+
+ private List performSaveAll(Iterable instances) {
+
+ Iterator iterator = instances.iterator();
+ T firstInstance = iterator.next();
+
+ // noinspection unchecked
+ BatchingAggregateChange> batchingAggregateChange = //
+ BatchingAggregateChange.forSave((Class) ClassUtils.getUserClass(firstInstance));
+ batchingAggregateChange.add(beforeExecute(firstInstance, changeCreatorSelectorForSave(firstInstance)));
+
+ while (iterator.hasNext()) {
+ T instance = iterator.next();
+ batchingAggregateChange.add(beforeExecute(instance, changeCreatorSelectorForSave(instance)));
+ }
+
+ List instancesAfterExecution = executor.executeSave(batchingAggregateChange);
+
+ ArrayList results = new ArrayList<>(instancesAfterExecution.size());
+ for (T instance : instancesAfterExecution) {
+ results.add(afterExecute(batchingAggregateChange, instance));
+ }
+
+ return results;
+ }
+
+ private Function> changeCreatorSelectorForSave(T instance) {
+
+ return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
+ ? entity -> createInsertChange(prepareVersionForInsert(entity))
+ : entity -> createUpdateChange(prepareVersionForUpdate(entity));
+ }
+
+ private RootAggregateChange createInsertChange(T instance) {
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(instance);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(instance);
new RelationalEntityInsertWriter(context).write(instance, aggregateChange);
return aggregateChange;
}
- private AggregateChangeWithRoot createUpdateChange(EntityAndPreviousVersion entityAndVersion) {
+ private RootAggregateChange createUpdateChange(EntityAndPreviousVersion entityAndVersion) {
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
entityAndVersion.version);
- new RelationalEntityUpdateWriter(context).write(entityAndVersion.entity, aggregateChange);
+ new RelationalEntityUpdateWriter(context).write(entityAndVersion.entity,
+ aggregateChange);
return aggregateChange;
}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java
index 297925887f..a4c12f0791 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java
@@ -52,8 +52,7 @@ public interface DataAccessStrategy extends RelationResolver {
* {@link Map} or {@link List}.
* @return the id generated by the database if any.
* @since 1.1
- * @deprecated since 2.4, use {@link #insert(Object, Class, Identifier, IdValueSource)}. This will no longer insert as
- * expected when the id property of the instance is pre-populated.
+ * @deprecated since 2.4, use {@link #insert(Object, Class, Identifier, IdValueSource)}.
*/
@Nullable
@Deprecated
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java
index f67679d249..42ad0b9f6e 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/SimpleJdbcRepository.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2021 the original author or authors.
+ * Copyright 2017-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.data.jdbc.repository.support;
import java.util.Optional;
-import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -25,7 +24,6 @@
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
-import org.springframework.data.util.Streamable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@@ -35,6 +33,7 @@
* @author Jens Schauder
* @author Oliver Gierke
* @author Milan Milanov
+ * @author Chirag Tailor
*/
@Transactional(readOnly = true)
public class SimpleJdbcRepository implements CrudRepository, PagingAndSortingRepository {
@@ -60,10 +59,7 @@ public S save(S instance) {
@Transactional
@Override
public Iterable saveAll(Iterable entities) {
-
- return Streamable.of(entities).stream() //
- .map(this::save) //
- .collect(Collectors.toList());
+ return entityOperations.saveAll(entities);
}
@Override
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java
index 3a92cca271..937c8b7f8a 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java
@@ -39,7 +39,7 @@
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
-import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
+import org.springframework.data.relational.core.conversion.RootAggregateChange;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.IdValueSource;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
@@ -80,10 +80,11 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
@Test // DATAJDBC-291
public void singleRoot() {
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
}
@@ -93,11 +94,12 @@ public void simpleReference() {
entity = entity.withSingle(content);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -111,12 +113,13 @@ public void listReference() {
entity = entity.withContentList(asList(content, content2));
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -130,12 +133,13 @@ public void mapReference() {
entity = entity.withContentMap(createContentMap("a", content, "b", content2));
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.contentMap.values()).extracting(c -> c.id).containsExactly(2, 3);
@@ -150,12 +154,13 @@ public void setIdForDeepReference() {
DbAction.Insert> parentInsert = createInsert("single", content, null);
DbAction.Insert> insert = createDeepInsert("single", tag1, null, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.single.id).isEqualTo(2);
@@ -172,13 +177,14 @@ public void setIdForDeepReferenceElementList() {
DbAction.Insert> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -198,13 +204,14 @@ public void setIdForDeepElementSetElementSet() {
DbAction.Insert> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -231,14 +238,15 @@ public void setIdForDeepElementListSingleReference() {
DbAction.Insert> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -262,7 +270,7 @@ public void setIdForDeepElementListElementList() {
DbAction.Insert> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -270,7 +278,8 @@ public void setIdForDeepElementListElementList() {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -297,7 +306,7 @@ public void setIdForDeepElementMapElementMap() {
DbAction.Insert> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -305,7 +314,8 @@ public void setIdForDeepElementMapElementMap() {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -336,14 +346,15 @@ public void setIdForDeepElementListSingleReferenceWithIntermittentNoId() {
DbAction.Insert> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertSoftly(softly -> {
@@ -362,11 +373,12 @@ public void setIdForEmbeddedDeepReference() {
DbAction.Insert> parentInsert = createInsert("embedded.single", tag1, null);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
- entity = executor.execute(aggregateChange);
+ List result = executor.executeSave(aggregateChange);
+ entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.embedded.single.id).isEqualTo(2);
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java
index 1c7a31e9b0..734e27e9b0 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java
@@ -36,7 +36,7 @@
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
-import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
+import org.springframework.data.relational.core.conversion.RootAggregateChange;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.IdValueSource;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
@@ -71,10 +71,10 @@ public class AggregateChangeIdGenerationUnitTests {
@Test // DATAJDBC-291
public void singleRoot() {
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
}
@@ -84,11 +84,11 @@ public void simpleReference() {
entity.single = content;
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -103,12 +103,12 @@ public void listReference() {
entity.contentList.add(content);
entity.contentList.add(content2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -123,12 +123,12 @@ public void mapReference() {
entity.contentMap.put("a", content);
entity.contentMap.put("b", content2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.contentMap.values()).extracting(c -> c.id).containsExactly(2, 3);
@@ -143,12 +143,12 @@ public void setIdForDeepReference() {
DbAction.Insert> parentInsert = createInsert("single", content, null);
DbAction.Insert> insert = createDeepInsert("single", tag1, null, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.single.id).isEqualTo(2);
@@ -166,13 +166,13 @@ public void setIdForDeepReferenceElementList() {
DbAction.Insert> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -193,13 +193,13 @@ public void setIdForDeepElementSetElementSet() {
DbAction.Insert> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -227,14 +227,14 @@ public void setIdForDeepElementListSingleReference() {
DbAction.Insert> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -260,7 +260,7 @@ public void setIdForDeepElementListElementList() {
DbAction.Insert> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -268,7 +268,7 @@ public void setIdForDeepElementListElementList() {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -298,7 +298,7 @@ public void setIdForDeepElementMapElementMap() {
DbAction.Insert> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
- AggregateChangeWithRoot aggregateChange = MutableAggregateChange.forSave(entity);
+ RootAggregateChange aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -306,7 +306,7 @@ public void setIdForDeepElementMapElementMap() {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
- executor.execute(aggregateChange);
+ executor.executeSave(aggregateChange);
assertSoftly(softly -> {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java
index 85940183f1..173b40c049 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2021 the original author or authors.
+ * Copyright 2017-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@
*
* @author Jens Schauder
* @author Salim Achouche
- * @author Chirag Taylor
+ * @author Chirag Tailor
*/
@ContextConfiguration
@Transactional
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java
index 9195563e7f..88bd07f96e 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java
@@ -46,7 +46,7 @@
* Test for the {@link JdbcAggregateChangeExecutionContext} when operating on immutable classes.
*
* @author Jens Schauder
- * @author Chirag Taylor
+ * @author Chirag Tailor
*/
public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
@@ -70,9 +70,10 @@ public void afterInsertRootIdMaybeUpdated() {
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isNotNull();
+ assertThat(newRoots).hasSize(1);
+ DummyEntity newRoot = newRoots.get(0);
assertThat(newRoot.id).isEqualTo(23L);
}
@@ -83,16 +84,17 @@ public void idGenerationOfChild() {
when(accessStrategy.insert(any(DummyEntity.class), eq(DummyEntity.class), eq(Identifier.empty()),
eq(IdValueSource.GENERATED))).thenReturn(23L);
- when(accessStrategy.insert(any(Content.class), eq(Content.class), eq(createBackRef()), eq(IdValueSource.GENERATED)))
+ when(accessStrategy.insert(any(Content.class), eq(Content.class), eq(createBackRef(23L)), eq(IdValueSource.GENERATED)))
.thenReturn(24L);
DbAction.InsertRoot rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "content", content, null));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isNotNull();
+ assertThat(newRoots).hasSize(1);
+ DummyEntity newRoot = newRoots.get(0);
assertThat(newRoot.id).isEqualTo(23L);
assertThat(newRoot.content.id).isEqualTo(24L);
@@ -112,14 +114,46 @@ public void idGenerationOfChildInList() {
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "list", content, 1));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isNotNull();
+ assertThat(newRoots).hasSize(1);
+ DummyEntity newRoot = newRoots.get(0);
assertThat(newRoot.id).isEqualTo(23L);
assertThat(newRoot.list.get(0).id).isEqualTo(24L);
}
+ @Test // GH-537
+ void populatesIdsIfNecessaryForAllRootsThatWereProcessed() {
+
+ DummyEntity root1 = new DummyEntity().withId(123L);
+ when(accessStrategy.update(root1, DummyEntity.class)).thenReturn(true);
+ DbAction.UpdateRoot rootUpdate1 = new DbAction.UpdateRoot<>(root1, null);
+ executionContext.executeUpdateRoot(rootUpdate1);
+ Content content1 = new Content();
+ when(accessStrategy.insert(content1, Content.class, createBackRef(123L), IdValueSource.GENERATED)).thenReturn(11L);
+ executionContext.executeInsert(createInsert(rootUpdate1, "content", content1, null));
+
+
+ DummyEntity root2 = new DummyEntity();
+ DbAction.InsertRoot rootInsert2 = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
+ when(accessStrategy.insert(root2, DummyEntity.class, Identifier.empty(), IdValueSource.GENERATED)).thenReturn(456L);
+ executionContext.executeInsertRoot(rootInsert2);
+ Content content2 = new Content();
+ when(accessStrategy.insert(content2, Content.class, createBackRef(456L), IdValueSource.GENERATED)).thenReturn(12L);
+ executionContext.executeInsert(createInsert(rootInsert2, "content", content2, null));
+
+ List newRoots = executionContext.populateIdsIfNecessary();
+
+ assertThat(newRoots).hasSize(2);
+ DummyEntity newRoot1 = newRoots.get(0);
+ assertThat(newRoot1.id).isEqualTo(123L);
+ assertThat(newRoot1.content.id).isEqualTo(11L);
+ DummyEntity newRoot2 = newRoots.get(1);
+ assertThat(newRoot2.id).isEqualTo(456L);
+ assertThat(newRoot2.content.id).isEqualTo(12L);
+ }
+
DbAction.Insert> createInsert(DbAction.WithEntity> parent, String propertyName, Object value,
@Nullable Object key) {
@@ -135,8 +169,8 @@ PersistentPropertyPath getPersistentPropertyPath(S
return context.getPersistentPropertyPath(propertyName, DummyEntity.class);
}
- Identifier createBackRef() {
- return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), 23L).build();
+ Identifier createBackRef(long value) {
+ return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), value).build();
}
PersistentPropertyPath toPath(String path) {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java
index 6936bb8d6d..800eb4f1fe 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java
@@ -18,6 +18,7 @@
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
+import static org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder.*;
import lombok.Value;
@@ -31,7 +32,6 @@
import org.springframework.data.jdbc.core.convert.Identifier;
import org.springframework.data.jdbc.core.convert.InsertSubject;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
-import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.PersistentPropertyPaths;
import org.springframework.data.relational.core.conversion.DbAction;
@@ -69,9 +69,9 @@ public void afterInsertRootIdMaybeUpdated() {
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(23L);
}
@@ -81,15 +81,15 @@ public void idGenerationOfChild() {
Content content = new Content();
when(accessStrategy.insert(root, DummyEntity.class, Identifier.empty(), IdValueSource.GENERATED)).thenReturn(23L);
- when(accessStrategy.insert(content, Content.class, createBackRef(), IdValueSource.GENERATED)).thenReturn(24L);
+ when(accessStrategy.insert(content, Content.class, createBackRef(23L), IdValueSource.GENERATED)).thenReturn(24L);
DbAction.InsertRoot rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
executionContext.executeInsertRoot(rootInsert);
- executionContext.executeInsert(createInsert(rootInsert, "content", content, null));
+ executionContext.executeInsert(createInsert(rootInsert, "content", content, null, IdValueSource.GENERATED));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(23L);
assertThat(content.id).isEqualTo(24L);
@@ -106,11 +106,11 @@ public void idGenerationOfChildInList() {
DbAction.InsertRoot rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
executionContext.executeInsertRoot(rootInsert);
- executionContext.executeInsert(createInsert(rootInsert, "list", content, 1));
+ executionContext.executeInsert(createInsert(rootInsert, "list", content, 1, IdValueSource.GENERATED));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(23L);
assertThat(content.id).isEqualTo(24L);
@@ -129,13 +129,13 @@ void batchInsertOperation_withGeneratedIds() {
.withPart(SqlIdentifier.quoted("DUMMY_ENTITY_KEY"), 0, Integer.class);
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(content, identifier)), Content.class,
IdValueSource.GENERATED)).thenReturn(new Object[] { 456L });
- DbAction.InsertBatch> insertBatch = new DbAction.InsertBatch<>(
- singletonList(createInsert(rootInsert, "list", content, 0)), IdValueSource.GENERATED);
- executionContext.executeInsertBatch(insertBatch);
+ DbAction.BatchInsert> batchInsert = new DbAction.BatchInsert<>(
+ singletonList(createInsert(rootInsert, "list", content, 0, IdValueSource.GENERATED)));
+ executionContext.executeBatchInsert(batchInsert);
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(123L);
assertThat(content.id).isEqualTo(456L);
}
@@ -153,42 +153,73 @@ void batchInsertOperation_withoutGeneratedIds() {
.withPart(SqlIdentifier.quoted("DUMMY_ENTITY_KEY"), 0, Integer.class);
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(content, identifier)), Content.class,
IdValueSource.PROVIDED)).thenReturn(new Object[] { null });
- DbAction.InsertBatch> insertBatch = new DbAction.InsertBatch<>(
- singletonList(createInsert(rootInsert, "list", content, 0)), IdValueSource.PROVIDED);
- executionContext.executeInsertBatch(insertBatch);
+ DbAction.BatchInsert> batchInsert = new DbAction.BatchInsert<>(
+ singletonList(createInsert(rootInsert, "list", content, 0, IdValueSource.PROVIDED)));
+ executionContext.executeBatchInsert(batchInsert);
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
+ List newRoots = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(123L);
assertThat(content.id).isNull();
}
@Test // GH-1201
void updates_whenReferencesWithImmutableIdAreInserted() {
- when(accessStrategy.update(any(), any())).thenReturn(true);
+
root.id = 123L;
- DbAction.UpdateRoot rootInsert = new DbAction.UpdateRoot<>(root, null);
- executionContext.executeUpdateRoot(rootInsert);
+ when(accessStrategy.update(root, DummyEntity.class)).thenReturn(true);
+ DbAction.UpdateRoot rootUpdate = new DbAction.UpdateRoot<>(root, null);
+ executionContext.executeUpdateRoot(rootUpdate);
ContentImmutableId contentImmutableId = new ContentImmutableId(null);
root.contentImmutableId = contentImmutableId;
Identifier identifier = Identifier.empty().withPart(SqlIdentifier.quoted("DUMMY_ENTITY"), 123L, Long.class);
when(accessStrategy.insert(contentImmutableId, ContentImmutableId.class, identifier, IdValueSource.GENERATED))
.thenReturn(456L);
- executionContext.executeInsert(createInsert(rootInsert, "contentImmutableId", contentImmutableId, null));
+ executionContext.executeInsert(createInsert(rootUpdate, "contentImmutableId", contentImmutableId, null, IdValueSource.GENERATED));
- DummyEntity newRoot = executionContext.populateIdsIfNecessary();
- assertThat(newRoot).isEqualTo(root);
+ List newRoots = executionContext.populateIdsIfNecessary();
+ assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(123L);
assertThat(root.contentImmutableId.id).isEqualTo(456L);
}
+ @Test // GH-537
+ void populatesIdsIfNecessaryForAllRootsThatWereProcessed() {
+
+ DummyEntity root1 = new DummyEntity();
+ root1.id = 123L;
+ when(accessStrategy.update(root1, DummyEntity.class)).thenReturn(true);
+ DbAction.UpdateRoot rootUpdate1 = new DbAction.UpdateRoot<>(root1, null);
+ executionContext.executeUpdateRoot(rootUpdate1);
+ Content content1 = new Content();
+ when(accessStrategy.insert(content1, Content.class, createBackRef(123L), IdValueSource.GENERATED)).thenReturn(11L);
+ executionContext.executeInsert(createInsert(rootUpdate1, "content", content1, null, IdValueSource.GENERATED));
+
+
+ DummyEntity root2 = new DummyEntity();
+ DbAction.InsertRoot rootInsert2 = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
+ when(accessStrategy.insert(root2, DummyEntity.class, Identifier.empty(), IdValueSource.GENERATED)).thenReturn(456L);
+ executionContext.executeInsertRoot(rootInsert2);
+ Content content2 = new Content();
+ when(accessStrategy.insert(content2, Content.class, createBackRef(456L), IdValueSource.GENERATED)).thenReturn(12L);
+ executionContext.executeInsert(createInsert(rootInsert2, "content", content2, null, IdValueSource.GENERATED));
+
+ List newRoots = executionContext.populateIdsIfNecessary();
+
+ assertThat(newRoots).containsExactly(root1, root2);
+ assertThat(root1.id).isEqualTo(123L);
+ assertThat(content1.id).isEqualTo(11L);
+ assertThat(root2.id).isEqualTo(456L);
+ assertThat(content2.id).isEqualTo(12L);
+ }
+
DbAction.Insert> createInsert(DbAction.WithEntity> parent, String propertyName, Object value,
- @Nullable Object key) {
+ @Nullable Object key, IdValueSource idValueSource) {
return new DbAction.Insert<>(value, getPersistentPropertyPath(propertyName), parent,
- key == null ? emptyMap() : singletonMap(toPath(propertyName), key), IdValueSource.GENERATED);
+ key == null ? emptyMap() : singletonMap(toPath(propertyName), key), idValueSource);
}
PersistentPropertyPathExtension toPathExt(String path) {
@@ -199,8 +230,8 @@ PersistentPropertyPath getPersistentPropertyPath(S
return context.getPersistentPropertyPath(propertyName, DummyEntity.class);
}
- Identifier createBackRef() {
- return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), 23L).build();
+ Identifier createBackRef(long value) {
+ return forBackReferences(converter, toPathExt("content"), value).build();
}
PersistentPropertyPath toPath(String path) {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
index 85f9e1e1f7..a665b114fc 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
@@ -16,6 +16,7 @@
package org.springframework.data.jdbc.repository;
import static java.util.Arrays.*;
+import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.SoftAssertions.*;
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
@@ -50,6 +51,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
+import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.repository.Lock;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
@@ -63,6 +65,7 @@
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.Param;
@@ -80,6 +83,7 @@
*
* @author Jens Schauder
* @author Mark Paluch
+ * @author Chirag Tailor
*/
@Transactional
@TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
@@ -89,6 +93,7 @@ public class JdbcRepositoryIntegrationTests {
@Autowired NamedParameterJdbcTemplate template;
@Autowired DummyEntityRepository repository;
@Autowired MyEventListener eventListener;
+ @Autowired RootRepository rootRepository;
private static DummyEntity createDummyEntity() {
@@ -128,7 +133,7 @@ public void saveAndLoadAnEntity() {
}
@Test // DATAJDBC-97
- public void savesManyEntities() {
+ public void insertsManyEntities() {
DummyEntity entity = createDummyEntity();
DummyEntity other = createDummyEntity();
@@ -282,6 +287,20 @@ public void updateMany() {
.containsExactlyInAnyOrder(entity.getName(), other.getName());
}
+ @Test // GH-537
+ void insertsOrUpdatesManyEntities() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+ entity.setName("something else");
+ DummyEntity other = createDummyEntity();
+ other.setName("others name");
+ repository.saveAll(asList(other, entity));
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getName) //
+ .containsExactlyInAnyOrder(entity.getName(), other.getName());
+ }
+
@Test // DATAJDBC-112
public void findByIdReturnsEmptyWhenNoneFound() {
@@ -575,6 +594,84 @@ void nullStringResult() {
assertThat(repository.returnInput(null)).isNull();
}
+ @Test // GH-537
+ void manyInsertsWithNestedEntities() {
+ Root root1 = createRoot("root1");
+ Root root2 = createRoot("root2");
+
+ List savedRoots = rootRepository.saveAll(asList(root1, root2));
+
+ List reloadedRoots = rootRepository.findAllByOrderByIdAsc();
+ assertThat(reloadedRoots).isEqualTo(savedRoots);
+ assertThat(reloadedRoots).hasSize(2);
+ assertIsEqualToWithNonNullIds(reloadedRoots.get(0), root1);
+ assertIsEqualToWithNonNullIds(reloadedRoots.get(1), root2);
+ }
+
+ @Test // GH-537
+ @EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
+ void manyUpdatesWithNestedEntities() {
+ Root root1 = createRoot("root1");
+ Root root2 = createRoot("root2");
+ List roots = rootRepository.saveAll(asList(root1, root2));
+ Root savedRoot1 = roots.get(0);
+ Root updatedRoot1 = new Root(savedRoot1.id, "updated" + savedRoot1.name,
+ new Intermediate(savedRoot1.intermediate.id, "updated" + savedRoot1.intermediate.name,
+ new Leaf(savedRoot1.intermediate.leaf.id, "updated" + savedRoot1.intermediate.leaf.name), emptyList()),
+ savedRoot1.intermediates);
+ Root savedRoot2 = roots.get(1);
+ Root updatedRoot2 = new Root(savedRoot2.id, "updated" + savedRoot2.name, savedRoot2.intermediate,
+ singletonList(
+ new Intermediate(savedRoot2.intermediates.get(0).id, "updated" + savedRoot2.intermediates.get(0).name, null,
+ singletonList(new Leaf(savedRoot2.intermediates.get(0).leaves.get(0).id,
+ "updated" + savedRoot2.intermediates.get(0).leaves.get(0).name)))));
+
+ List updatedRoots = rootRepository.saveAll(asList(updatedRoot1, updatedRoot2));
+
+ List reloadedRoots = rootRepository.findAllByOrderByIdAsc();
+ assertThat(reloadedRoots).isEqualTo(updatedRoots);
+ assertThat(reloadedRoots).containsExactly(updatedRoot1, updatedRoot2);
+ }
+
+ @Test // GH-537
+ @EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
+ void manyInsertsAndUpdatesWithNestedEntities() {
+ Root root1 = createRoot("root1");
+ Root savedRoot1 = rootRepository.save(root1);
+ Root updatedRoot1 = new Root(savedRoot1.id, "updated" + savedRoot1.name,
+ new Intermediate(savedRoot1.intermediate.id, "updated" + savedRoot1.intermediate.name,
+ new Leaf(savedRoot1.intermediate.leaf.id, "updated" + savedRoot1.intermediate.leaf.name), emptyList()),
+ savedRoot1.intermediates);
+ Root root2 = createRoot("root2");
+ List savedRoots = rootRepository.saveAll(asList(updatedRoot1, root2));
+
+ List reloadedRoots = rootRepository.findAllByOrderByIdAsc();
+ assertThat(reloadedRoots).isEqualTo(savedRoots);
+ assertThat(reloadedRoots.get(0)).isEqualTo(updatedRoot1);
+ assertIsEqualToWithNonNullIds(reloadedRoots.get(1), root2);
+ }
+
+ private Root createRoot(String namePrefix) {
+ return new Root(null, namePrefix,
+ new Intermediate(null, namePrefix + "Intermediate", new Leaf(null, namePrefix + "Leaf"), emptyList()),
+ singletonList(new Intermediate(null, namePrefix + "QualifiedIntermediate", null,
+ singletonList(new Leaf(null, namePrefix + "QualifiedLeaf")))));
+ }
+
+ private void assertIsEqualToWithNonNullIds(Root reloadedRoot1, Root root1) {
+ assertThat(reloadedRoot1.id).isNotNull();
+ assertThat(reloadedRoot1.name).isEqualTo(root1.name);
+ assertThat(reloadedRoot1.intermediate.id).isNotNull();
+ assertThat(reloadedRoot1.intermediate.name).isEqualTo(root1.intermediate.name);
+ assertThat(reloadedRoot1.intermediates.get(0).id).isNotNull();
+ assertThat(reloadedRoot1.intermediates.get(0).name).isEqualTo(root1.intermediates.get(0).name);
+ assertThat(reloadedRoot1.intermediate.leaf.id).isNotNull();
+ assertThat(reloadedRoot1.intermediate.leaf.name).isEqualTo(root1.intermediate.leaf.name);
+ assertThat(reloadedRoot1.intermediates.get(0).leaves.get(0).id).isNotNull();
+ assertThat(reloadedRoot1.intermediates.get(0).leaves.get(0).name)
+ .isEqualTo(root1.intermediates.get(0).leaves.get(0).name);
+ }
+
private Instant createDummyBeforeAndAfterNow() {
Instant now = Instant.now();
@@ -678,6 +775,11 @@ DummyEntityRepository dummyEntityRepository() {
return factory.getRepository(DummyEntityRepository.class);
}
+ @Bean
+ RootRepository rootRepository() {
+ return factory.getRepository(RootRepository.class);
+ }
+
@Bean
NamedQueries namedQueries() throws IOException {
@@ -693,6 +795,32 @@ MyEventListener eventListener() {
}
}
+ interface RootRepository extends ListCrudRepository {
+ List findAllByOrderByIdAsc();
+ }
+
+ @Value
+ static class Root {
+ @Id Long id;
+ String name;
+ Intermediate intermediate;
+ @MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") List intermediates;
+ }
+
+ @Value
+ static class Intermediate {
+ @Id Long id;
+ String name;
+ Leaf leaf;
+ @MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") List leaves;
+ }
+
+ @Value
+ static class Leaf {
+ @Id Long id;
+ String name;
+ }
+
static class MyEventListener implements ApplicationListener> {
private List> events = new ArrayList<>();
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
index 580bc988f6..b992944698 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
@@ -68,6 +68,8 @@
*/
public class SimpleJdbcRepositoryEventsUnitTests {
+ private static final long generatedId = 4711L;
+
CollectingEventPublisher publisher = new CollectingEventPublisher();
DummyEntityRepository repository;
@@ -125,14 +127,14 @@ public void publishesEventsOnSaveMany() {
repository.saveAll(asList(entity1, entity2));
assertThat(publisher.events) //
- .extracting(e -> (Class) e.getClass()) //
+ .extracting(RelationalEvent::getClass, e -> ((DummyEntity) e.getEntity()).getId()) //
.containsExactly( //
- BeforeConvertEvent.class, //
- BeforeSaveEvent.class, //
- AfterSaveEvent.class, //
- BeforeConvertEvent.class, //
- BeforeSaveEvent.class, //
- AfterSaveEvent.class //
+ Tuple.tuple(BeforeConvertEvent.class, null), //
+ Tuple.tuple(BeforeSaveEvent.class, null), //
+ Tuple.tuple(BeforeConvertEvent.class, 23L), //
+ Tuple.tuple(BeforeSaveEvent.class, 23L), //
+ Tuple.tuple(AfterSaveEvent.class, generatedId), //
+ Tuple.tuple(AfterSaveEvent.class, 23L) //
);
}
@@ -284,7 +286,7 @@ private static NamedParameterJdbcOperations createIdGeneratingOperations() {
Answer setIdInKeyHolder = invocation -> {
HashMap keys = new HashMap<>();
- keys.put("id", 4711L);
+ keys.put("id", generatedId);
KeyHolder keyHolder = invocation.getArgument(2);
keyHolder.getKeyList().add(keys);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql
index 34be74ec51..6cf7b106bd 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql
@@ -1,4 +1,7 @@
DROP TABLE dummy_entity;
+DROP TABLE ROOT;
+DROP TABLE INTERMEDIATE;
+DROP TABLE LEAF;
CREATE TABLE dummy_entity
(
@@ -9,3 +12,25 @@ CREATE TABLE dummy_entity
FLAG BOOLEAN,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql
index b3b93bc744..2515db8847 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql
@@ -7,3 +7,25 @@ CREATE TABLE dummy_entity
FLAG BOOLEAN,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
index b3b93bc744..2515db8847 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
@@ -7,3 +7,25 @@ CREATE TABLE dummy_entity
FLAG BOOLEAN,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
index 949e626399..543c0ea57e 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
@@ -7,3 +7,25 @@ CREATE TABLE dummy_entity
FLAG BOOLEAN,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql
index 15f8881327..8405222840 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql
@@ -1,4 +1,8 @@
DROP TABLE IF EXISTS dummy_entity;
+DROP TABLE IF EXISTS ROOT;
+DROP TABLE IF EXISTS INTERMEDIATE;
+DROP TABLE IF EXISTS LEAF;
+
CREATE TABLE dummy_entity
(
id_Prop BIGINT IDENTITY PRIMARY KEY,
@@ -8,3 +12,25 @@ CREATE TABLE dummy_entity
FLAG BIT,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT IDENTITY PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT IDENTITY PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT IDENTITY PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
index e3baa94602..035cf8a85a 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
@@ -10,3 +10,26 @@ CREATE TABLE DUMMY_ENTITY
FLAG BIT(1),
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ ROOT_ID BIGINT,
+ ROOT_KEY INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID BIGINT AUTO_INCREMENT PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ INTERMEDIATE_ID BIGINT,
+ INTERMEDIATE_KEY INTEGER
+);
+
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql
index e71eb63286..348c777649 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql
@@ -1,4 +1,7 @@
DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE;
+DROP TABLE ROOT CASCADE CONSTRAINTS PURGE;
+DROP TABLE INTERMEDIATE CASCADE CONSTRAINTS PURGE;
+DROP TABLE LEAF CASCADE CONSTRAINTS PURGE;
CREATE TABLE DUMMY_ENTITY
(
@@ -9,3 +12,25 @@ CREATE TABLE DUMMY_ENTITY
FLAG NUMBER(1,0),
REF NUMBER
);
+
+CREATE TABLE ROOT
+(
+ ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
+ NAME VARCHAR2(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
+ NAME VARCHAR2(100),
+ ROOT NUMBER,
+ ROOT_ID NUMBER,
+ ROOT_KEY NUMBER
+);
+CREATE TABLE LEAF
+(
+ ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
+ NAME VARCHAR2(100),
+ INTERMEDIATE NUMBER,
+ INTERMEDIATE_ID NUMBER,
+ INTERMEDIATE_KEY NUMBER
+);
diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
index 97fc78c9da..a41b3e522c 100644
--- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
+++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
@@ -1,4 +1,8 @@
DROP TABLE dummy_entity;
+DROP TABLE ROOT;
+DROP TABLE INTERMEDIATE;
+DROP TABLE LEAF;
+
CREATE TABLE dummy_entity
(
id_Prop SERIAL PRIMARY KEY,
@@ -8,3 +12,25 @@ CREATE TABLE dummy_entity
FLAG BOOLEAN,
REF BIGINT
);
+
+CREATE TABLE ROOT
+(
+ ID SERIAL PRIMARY KEY,
+ NAME VARCHAR(100)
+);
+CREATE TABLE INTERMEDIATE
+(
+ ID SERIAL PRIMARY KEY,
+ NAME VARCHAR(100),
+ ROOT BIGINT,
+ "ROOT_ID" BIGINT,
+ "ROOT_KEY" INTEGER
+);
+CREATE TABLE LEAF
+(
+ ID SERIAL PRIMARY KEY,
+ NAME VARCHAR(100),
+ INTERMEDIATE BIGINT,
+ "INTERMEDIATE_ID" BIGINT,
+ "INTERMEDIATE_KEY" INTEGER
+);
diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml
index 22914016c3..99a1658ffa 100644
--- a/spring-data-r2dbc/pom.xml
+++ b/spring-data-r2dbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-r2dbc
- 3.0.0-SNAPSHOT
+ 3.0.0-537-batch-ops-across-aggregates-SNAPSHOT
Spring Data R2DBC
Spring Data module for R2DBC
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 3.0.0-SNAPSHOT
+ 3.0.0-537-batch-ops-across-aggregates-SNAPSHOT
diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml
index 717ac86edf..ea2f2d8b83 100644
--- a/spring-data-relational/pom.xml
+++ b/spring-data-relational/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-relational
- 3.0.0-SNAPSHOT
+ 3.0.0-537-batch-ops-across-aggregates-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 3.0.0-SNAPSHOT
+ 3.0.0-537-batch-ops-across-aggregates-SNAPSHOT
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchingAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchingAggregateChange.java
new file mode 100644
index 0000000000..8f53bf1015
--- /dev/null
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchingAggregateChange.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.relational.core.conversion;
+
+import org.springframework.util.Assert;
+
+/**
+ * Represents the changes happening to one or more aggregates (as used in the context of Domain Driven Design) as a
+ * whole. This change allows additional {@link MutableAggregateChange} of a particular kind to be added to it to
+ * broadly represent the changes to multiple aggregates across all such added changes.
+ *
+ * @author Chirag Tailor
+ * @since 3.0
+ */
+public interface BatchingAggregateChange> extends AggregateChange {
+ /**
+ * Adds a {@code MutableAggregateChange} into this {@code BatchingAggregateChange}.
+ *
+ * @param aggregateChange must not be {@literal null}.
+ */
+ void add(C aggregateChange);
+
+ /**
+ * Factory method to create a {@link BatchingAggregateChange} for saving entities.
+ *
+ * @param entityClass aggregate root type.
+ * @param entity type.
+ * @return the {@link BatchingAggregateChange} for saving root entities.
+ * @since 3.0
+ */
+ static BatchingAggregateChange> forSave(Class entityClass) {
+
+ Assert.notNull(entityClass, "Entity class must not be null");
+
+ return new SaveBatchingAggregateChange<>(entityClass);
+ }
+}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java
index d9de720540..ec3dad3169 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java
@@ -17,8 +17,10 @@
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -345,37 +347,55 @@ public String toString() {
}
/**
- * Represents a batch insert statement for a multiple entities that are not aggregate roots.
+ * Represents a batch of {@link DbAction} that share a common value for a property of the action.
*
* @param type of the entity for which this represents a database interaction.
- * @since 2.4
+ * @since 3.0
*/
- final class InsertBatch implements DbAction {
- private final List> inserts;
- private final IdValueSource idValueSource;
-
- public InsertBatch(List> inserts, IdValueSource idValueSource) {
- Assert.notEmpty(inserts, "Inserts must contains at least one insert");
- this.inserts = inserts;
- this.idValueSource = idValueSource;
+ abstract class BatchWithValue, B> implements DbAction {
+ private final List actions;
+ private final B batchValue;
+
+ public BatchWithValue(List actions, Function batchValueExtractor) {
+ Assert.notEmpty(actions, "Actions must contain at least one action");
+ Iterator actionIterator = actions.iterator();
+ this.batchValue = batchValueExtractor.apply(actionIterator.next());
+ actionIterator.forEachRemaining(action -> {
+ if (!batchValueExtractor.apply(action).equals(batchValue)) {
+ throw new IllegalArgumentException("All actions in the batch must have matching batchValue");
+ }
+ });
+ this.actions = actions;
}
@Override
public Class getEntityType() {
- return inserts.get(0).getEntityType();
+ return actions.get(0).getEntityType();
}
- public List> getInserts() {
- return inserts;
+ public List getActions() {
+ return actions;
}
- public IdValueSource getIdValueSource() {
- return idValueSource;
+ public B getBatchValue() {
+ return batchValue;
}
@Override
public String toString() {
- return "InsertBatch{" + "inserts=" + inserts + ", idValueSource=" + idValueSource + '}';
+ return "BatchWithValue{" + "actions=" + actions + ", batchValue=" + batchValue + '}';
+ }
+ }
+
+ /**
+ * Represents a batch insert statement for a multiple entities that are not aggregate roots.
+ *
+ * @param type of the entity for which this represents a database interaction.
+ * @since 2.4
+ */
+ final class BatchInsert extends BatchWithValue, IdValueSource> {
+ public BatchInsert(List> actions) {
+ super(actions, Insert::getIdValueSource);
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java
similarity index 94%
rename from spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java
rename to spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java
index 60b481469e..b42a4ff7a9 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultAggregateChangeWithRoot.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java
@@ -26,9 +26,9 @@
* Represents the change happening to the aggregate (as used in the context of Domain Driven Design) as a whole.
*
* @author Chirag Tailor
- * @since 2.6
+ * @since 3.0
*/
-class DefaultAggregateChangeWithRoot implements AggregateChangeWithRoot {
+class DefaultRootAggregateChange implements RootAggregateChange {
private final Kind kind;
@@ -42,7 +42,7 @@ class DefaultAggregateChangeWithRoot implements AggregateChangeWithRoot {
/** The previous version assigned to the instance being changed, if available */
@Nullable private final Number previousVersion;
- public DefaultAggregateChangeWithRoot(Kind kind, Class entityType, @Nullable Number previousVersion) {
+ public DefaultRootAggregateChange(Kind kind, Class entityType, @Nullable Number previousVersion) {
this.kind = kind;
this.entityType = entityType;
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java
index a76d17ab7a..f0984be4cd 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java
@@ -30,32 +30,32 @@
public interface MutableAggregateChange extends AggregateChange {
/**
- * Factory method to create an {@link AggregateChangeWithRoot} for saving entities.
+ * Factory method to create a {@link RootAggregateChange} for saving entities.
*
* @param entity aggregate root to save.
* @param entity type.
- * @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}.
+ * @return the {@link RootAggregateChange} for saving the root {@code entity}.
* @since 1.2
*/
- static AggregateChangeWithRoot forSave(T entity) {
+ static RootAggregateChange forSave(T entity) {
return forSave(entity, null);
}
/**
- * Factory method to create an {@link AggregateChangeWithRoot} for saving entities.
+ * Factory method to create a {@link RootAggregateChange} for saving entities.
*
* @param entity aggregate root to save.
* @param previousVersion the previous version assigned to the instance being saved. May be {@literal null}.
* @param entity type.
- * @return the {@link AggregateChangeWithRoot} for saving the root {@code entity}.
+ * @return the {@link RootAggregateChange} for saving the root {@code entity}.
* @since 2.4
*/
@SuppressWarnings("unchecked")
- static AggregateChangeWithRoot forSave(T entity, @Nullable Number previousVersion) {
+ static RootAggregateChange forSave(T entity, @Nullable Number previousVersion) {
Assert.notNull(entity, "Entity must not be null");
- return new DefaultAggregateChangeWithRoot<>(Kind.SAVE, (Class) ClassUtils.getUserClass(entity), previousVersion);
+ return new DefaultRootAggregateChange<>(Kind.SAVE, (Class) ClassUtils.getUserClass(entity), previousVersion);
}
/**
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java
index 0022379db6..e6b6e397bd 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityInsertWriter.java
@@ -19,7 +19,7 @@
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
- * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}. Does not perform any isNew
+ * Converts an aggregate represented by its root into a {@link RootAggregateChange}. Does not perform any isNew
* check.
*
* @author Thomas Lang
@@ -27,7 +27,7 @@
* @author Chirag Tailor
* @since 1.1
*/
-public class RelationalEntityInsertWriter implements EntityWriter> {
+public class RelationalEntityInsertWriter implements EntityWriter> {
private final RelationalMappingContext context;
@@ -36,7 +36,7 @@ public RelationalEntityInsertWriter(RelationalMappingContext context) {
}
@Override
- public void write(T root, AggregateChangeWithRoot aggregateChange) {
+ public void write(T root, RootAggregateChange aggregateChange) {
new WritingContext<>(context, root, aggregateChange).insert();
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java
index 4f9ebd1b87..6ff1c7d238 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityUpdateWriter.java
@@ -19,7 +19,7 @@
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
- * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}. Does not perform any isNew
+ * Converts an aggregate represented by its root into a {@link RootAggregateChange}. Does not perform any isNew
* check.
*
* @author Thomas Lang
@@ -27,7 +27,7 @@
* @author Chirag Tailor
* @since 1.1
*/
-public class RelationalEntityUpdateWriter implements EntityWriter> {
+public class RelationalEntityUpdateWriter implements EntityWriter> {
private final RelationalMappingContext context;
@@ -36,7 +36,7 @@ public RelationalEntityUpdateWriter(RelationalMappingContext context) {
}
@Override
- public void write(T root, AggregateChangeWithRoot aggregateChange) {
+ public void write(T root, RootAggregateChange aggregateChange) {
new WritingContext<>(context, root, aggregateChange).update();
}
}
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java
index b551a21f09..087debe368 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityWriter.java
@@ -19,13 +19,13 @@
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
/**
- * Converts an aggregate represented by its root into an {@link AggregateChangeWithRoot}.
+ * Converts an aggregate represented by its root into a {@link RootAggregateChange}.
*
* @author Jens Schauder
* @author Mark Paluch
* @author Chirag Tailor
*/
-public class RelationalEntityWriter implements EntityWriter> {
+public class RelationalEntityWriter implements EntityWriter> {
private final RelationalMappingContext context;
@@ -34,7 +34,7 @@ public RelationalEntityWriter(RelationalMappingContext context) {
}
@Override
- public void write(T root, AggregateChangeWithRoot