Add SaveMergedAggregateChange which merges AggregateChangeWithRoot changes into one.

Remove behavior from WritingContext for creating InsertBatch in favor of SaveMergedAggregateChange.

Update all save paths to use SaveMergedAggregateChange.

+ Update #populateIdsIfNecessary return type from T to List<T>

Pull out an abstract BatchWithValue class from InsertBatch to use it for batching root inserts as well.

Rename InsertBatch to BatchInsert
Rename AggregateChangeWithRoot to RootAggregateChange.

Original pull request #1211
This commit is contained in:
Chirag Tailor
2022-03-28 15:29:32 -05:00
committed by Jens Schauder
parent d3d05039da
commit 6b02a4e627
36 changed files with 1218 additions and 403 deletions

View File

@@ -18,11 +18,12 @@ package org.springframework.data.jdbc.core;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.AggregateChangeWithRoot;
import org.springframework.data.relational.core.conversion.DbAction;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
import java.util.List;
/**
* Executes an {@link MutableAggregateChange}.
*
@@ -43,15 +44,15 @@ class AggregateChangeExecutor {
}
/**
* Execute an aggregate change which has a root entity. It returns the root entity, with all changes that might apply.
* This might be the original instance or a new instance, depending on its mutability.
* Execute a save aggregate change. It returns the resulting root entities, with all changes that might apply. This
* might be the original instances or new instances, depending on their mutability.
*
* @param aggregateChange the aggregate change to be executed. Must not be {@literal null}.
* @param <T> the type of the aggregate root.
* @return the potentially modified aggregate root. Guaranteed to be not {@literal null}.
* @return the aggregate roots resulting from the change, if there are any. May be empty.
* @since 3.0
*/
<T> T execute(AggregateChangeWithRoot<T> aggregateChange) {
<T> List<T> executeSave(AggregateChange<T> aggregateChange) {
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
@@ -62,13 +63,13 @@ class AggregateChangeExecutor {
}
/**
* Execute an aggregate change without a root entity.
* Execute a delete aggregate change.
*
* @param aggregateChange the aggregate change to be executed. Must not be {@literal null}.
* @param <T> the type of the aggregate root.
* @since 3.0
*/
<T> void execute(AggregateChange<T> aggregateChange) {
<T> void executeDelete(AggregateChange<T> aggregateChange) {
JdbcAggregateChangeExecutionContext executionContext = new JdbcAggregateChangeExecutionContext(converter,
accessStrategy);
@@ -83,8 +84,8 @@ class AggregateChangeExecutor {
executionContext.executeInsertRoot((DbAction.InsertRoot<?>) action);
} else if (action instanceof DbAction.Insert) {
executionContext.executeInsert((DbAction.Insert<?>) action);
} else if (action instanceof DbAction.InsertBatch) {
executionContext.executeInsertBatch((DbAction.InsertBatch<?>) action);
} else if (action instanceof DbAction.BatchInsert) {
executionContext.executeBatchInsert((DbAction.BatchInsert<?>) action);
} else if (action instanceof DbAction.UpdateRoot) {
executionContext.executeUpdateRoot((DbAction.UpdateRoot<?>) action);
} else if (action instanceof DbAction.Delete) {

View File

@@ -83,14 +83,14 @@ class JdbcAggregateChangeExecutionContext {
add(new DbActionExecutionResult(insert, id));
}
<T> void executeInsertBatch(DbAction.InsertBatch<T> insertBatch) {
<T> void executeBatchInsert(DbAction.BatchInsert<T> batchInsert) {
List<DbAction.Insert<T>> inserts = insertBatch.getInserts();
List<DbAction.Insert<T>> inserts = batchInsert.getActions();
List<InsertSubject<T>> insertSubjects = inserts.stream()
.map(insert -> InsertSubject.describedBy(insert.getEntity(), getParentKeys(insert, converter)))
.collect(Collectors.toList());
Object[] ids = accessStrategy.insert(insertSubjects, insertBatch.getEntityType(), insertBatch.getIdValueSource());
Object[] ids = accessStrategy.insert(insertSubjects, batchInsert.getEntityType(), batchInsert.getBatchValue());
for (int i = 0; i < inserts.size(); i++) {
add(new DbActionExecutionResult(inserts.get(i), ids.length > 0 ? ids[i] : null));
@@ -216,7 +216,7 @@ class JdbcAggregateChangeExecutionContext {
return identifier;
}
<T> T populateIdsIfNecessary() {
<T> List<T> populateIdsIfNecessary() {
// have the results so that the inserts on the leaves come first.
List<DbActionExecutionResult> reverseResults = new ArrayList<>(results.values());
@@ -224,6 +224,8 @@ class JdbcAggregateChangeExecutionContext {
StagedValues cascadingValues = new StagedValues();
List<T> roots = new ArrayList<>(reverseResults.size());
for (DbActionExecutionResult result : reverseResults) {
DbAction.WithEntity<?> action = result.getAction();
@@ -232,7 +234,7 @@ class JdbcAggregateChangeExecutionContext {
if (action instanceof DbAction.InsertRoot || action instanceof DbAction.UpdateRoot) {
// noinspection unchecked
return (T) newEntity;
roots.add((T) newEntity);
}
// the id property was immutable so we have to propagate changes up the tree
@@ -246,9 +248,15 @@ class JdbcAggregateChangeExecutionContext {
}
}
throw new IllegalStateException(
String.format("Cannot retrieve the resulting instance unless a %s or %s action was successfully executed.",
DbAction.InsertRoot.class.getName(), DbAction.UpdateRoot.class.getName()));
if (roots.isEmpty()) {
throw new IllegalStateException(
String.format("Cannot retrieve the resulting instance(s) unless a %s or %s action was successfully executed.",
DbAction.InsertRoot.class.getName(), DbAction.UpdateRoot.class.getName()));
}
Collections.reverse(roots);
return roots;
}
private <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @Nullable Object generatedId,

View File

@@ -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.
@@ -26,6 +26,7 @@ import org.springframework.lang.Nullable;
* @author Jens Schauder
* @author Thomas Lang
* @author Milan Milanov
* @author Chirag Tailor
*/
public interface JdbcAggregateOperations {
@@ -38,6 +39,16 @@ public interface JdbcAggregateOperations {
*/
<T> T save(T instance);
/**
* Saves all aggregate instances, including all the members of each aggregate instance.
*
* @param instances the aggregate roots to be saved. Must not be {@code null}.
* @param <T> the type of the aggregate root.
* @return the saved instances.
* @since 3.0
*/
<T> Iterable<T> saveAll(Iterable<T> instances);
/**
* Dedicated insert function. This skips the test if the aggregate root is new and makes an insert.
* <p>

View File

@@ -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.jdbc.core.convert.JdbcConverter;
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.relational.core.mapping.event.*;
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.
@@ -141,13 +144,15 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(instance, "Aggregate instance must not be null!");
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(instance.getClass());
return performSave(instance, changeCreatorSelectorForSave(instance));
}
Function<T, AggregateChangeWithRoot<T>> changeCreator = persistentEntity.isNew(instance)
? entity -> createInsertChange(prepareVersionForInsert(entity))
: entity -> createUpdateChange(prepareVersionForUpdate(entity));
@Override
public <T> Iterable<T> saveAll(Iterable<T> instances) {
return store(instance, changeCreator, persistentEntity);
Assert.isTrue(instances.iterator().hasNext(), "Aggregate instances must not be empty!");
return performSaveAll(instances);
}
/**
@@ -162,9 +167,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
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)));
}
/**
@@ -179,9 +182,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
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
@@ -280,29 +281,33 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(domainType, "Domain type must not be null!");
MutableAggregateChange<?> change = createDeletingChange(domainType);
executor.execute(change);
executor.executeDelete(change);
}
private <T> T store(T aggregateRoot, Function<T, AggregateChangeWithRoot<T>> changeCreator,
RelationalPersistentEntity<?> persistentEntity) {
private <T> T afterExecute(AggregateChange<T> 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> RootAggregateChange<T> beforeExecute(T aggregateRoot,
Function<T, RootAggregateChange<T>> changeCreator) {
Assert.notNull(aggregateRoot, "Aggregate instance must not be null!");
aggregateRoot = triggerBeforeConvert(aggregateRoot);
AggregateChangeWithRoot<T> change = changeCreator.apply(aggregateRoot);
RootAggregateChange<T> 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 <T> void deleteTree(Object id, @Nullable T entity, Class<T> domainType) {
@@ -311,23 +316,70 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
entity = triggerBeforeDelete(entity, id, change);
executor.execute(change);
executor.executeDelete(change);
triggerAfterDelete(entity, id, change);
}
private <T> AggregateChangeWithRoot<T> createInsertChange(T instance) {
private <T> T performSave(T instance, Function<T, RootAggregateChange<T>> changeCreator) {
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(instance);
// noinspection unchecked
BatchingAggregateChange<T, RootAggregateChange<T>> batchingAggregateChange = //
BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(instance));
batchingAggregateChange.add(beforeExecute(instance, changeCreator));
Iterator<T> afterExecutionIterator = executor.executeSave(batchingAggregateChange).iterator();
Assert.isTrue(afterExecutionIterator.hasNext(), "Instances after execution must not be empty!");
return afterExecute(batchingAggregateChange, afterExecutionIterator.next());
}
private <T> List<T> performSaveAll(Iterable<T> instances) {
Iterator<T> iterator = instances.iterator();
T firstInstance = iterator.next();
// noinspection unchecked
BatchingAggregateChange<T, RootAggregateChange<T>> batchingAggregateChange = //
BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(firstInstance));
batchingAggregateChange.add(beforeExecute(firstInstance, changeCreatorSelectorForSave(firstInstance)));
while (iterator.hasNext()) {
T instance = iterator.next();
batchingAggregateChange.add(beforeExecute(instance, changeCreatorSelectorForSave(instance)));
}
List<T> instancesAfterExecution = executor.executeSave(batchingAggregateChange);
ArrayList<T> results = new ArrayList<>(instancesAfterExecution.size());
for (T instance : instancesAfterExecution) {
results.add(afterExecute(batchingAggregateChange, instance));
}
return results;
}
private <T> Function<T, RootAggregateChange<T>> changeCreatorSelectorForSave(T instance) {
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
? entity -> createInsertChange(prepareVersionForInsert(entity))
: entity -> createUpdateChange(prepareVersionForUpdate(entity));
}
private <T> RootAggregateChange<T> createInsertChange(T instance) {
RootAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(instance);
new RelationalEntityInsertWriter<T>(context).write(instance, aggregateChange);
return aggregateChange;
}
private <T> AggregateChangeWithRoot<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
private <T> RootAggregateChange<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
AggregateChangeWithRoot<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
RootAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
entityAndVersion.version);
new RelationalEntityUpdateWriter<T>(context).write(entityAndVersion.entity, aggregateChange);
new RelationalEntityUpdateWriter<T>(context).write(entityAndVersion.entity,
aggregateChange);
return aggregateChange;
}

View File

@@ -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.jdbc.core.JdbcAggregateOperations;
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 @@ import org.springframework.util.Assert;
* @author Jens Schauder
* @author Oliver Gierke
* @author Milan Milanov
* @author Chirag Tailor
*/
@Transactional(readOnly = true)
public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, PagingAndSortingRepository<T, ID> {
@@ -60,10 +59,7 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, Paging
@Transactional
@Override
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
return Streamable.of(entities).stream() //
.map(this::save) //
.collect(Collectors.toList());
return entityOperations.saveAll(entities);
}
@Override

View File

@@ -39,7 +39,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
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<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
}
@@ -93,11 +94,12 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withSingle(content);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("single", content, null));
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -111,12 +113,13 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentList(asList(content, content2));
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentList", content, 0));
aggregateChange.addAction(createInsert("contentList", content2, 1));
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -130,12 +133,13 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
entity = entity.withContentMap(createContentMap("a", content, "b", content2));
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(createInsert("contentMap", content, "a"));
aggregateChange.addAction(createInsert("contentMap", content2, "b"));
entity = executor.execute(aggregateChange);
List<DummyEntity> 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 class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.single.id).isEqualTo(2);
@@ -172,13 +177,14 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -198,13 +204,14 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -231,14 +238,15 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -262,7 +270,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -270,7 +278,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -297,7 +306,7 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -305,7 +314,8 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -336,14 +346,15 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
aggregateChange.addAction(insert1);
aggregateChange.addAction(insert2);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertSoftly(softly -> {
@@ -362,11 +373,12 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
DbAction.Insert<?> parentInsert = createInsert("embedded.single", tag1, null);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert);
entity = executor.execute(aggregateChange);
List<DummyEntity> result = executor.executeSave(aggregateChange);
entity = result.get(0);
assertThat(entity.rootId).isEqualTo(1);
assertThat(entity.embedded.single.id).isEqualTo(2);

View File

@@ -36,7 +36,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
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<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
executor.execute(aggregateChange);
executor.executeSave(aggregateChange);
assertThat(entity.rootId).isEqualTo(1);
}
@@ -84,11 +84,11 @@ public class AggregateChangeIdGenerationUnitTests {
entity.single = content;
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
entity.contentList.add(content);
entity.contentList.add(content2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
entity.contentMap.put("a", content);
entity.contentMap.put("b", content2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> parentInsert = createInsert("single", content, null);
DbAction.Insert<?> insert = createDeepInsert("single", tag1, null, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagList", tag1, 0, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 1, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("tagSet", tag1, null, parentInsert);
DbAction.Insert<?> insert2 = createDeepInsert("tagSet", tag2, null, parentInsert);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert1 = createDeepInsert("single", tag1, null, parentInsert1);
DbAction.Insert<?> insert2 = createDeepInsert("single", tag2, null, parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> 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 class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagList", tag2, 0, parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagList", tag3, 1, parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -268,7 +268,7 @@ public class AggregateChangeIdGenerationUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
executor.execute(aggregateChange);
executor.executeSave(aggregateChange);
assertSoftly(softly -> {
@@ -298,7 +298,7 @@ public class AggregateChangeIdGenerationUnitTests {
DbAction.Insert<?> insert2 = createDeepInsert("tagMap", tag2, "222", parentInsert2);
DbAction.Insert<?> insert3 = createDeepInsert("tagMap", tag3, "333", parentInsert2);
AggregateChangeWithRoot<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
RootAggregateChange<DummyEntity> aggregateChange = MutableAggregateChange.forSave(entity);
aggregateChange.setRootAction(rootInsert);
aggregateChange.addAction(parentInsert1);
aggregateChange.addAction(parentInsert2);
@@ -306,7 +306,7 @@ public class AggregateChangeIdGenerationUnitTests {
aggregateChange.addAction(insert2);
aggregateChange.addAction(insert3);
executor.execute(aggregateChange);
executor.executeSave(aggregateChange);
assertSoftly(softly -> {

View File

@@ -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 @@ import org.springframework.transaction.annotation.Transactional;
*
* @author Jens Schauder
* @author Salim Achouche
* @author Chirag Taylor
* @author Chirag Tailor
*/
@ContextConfiguration
@Transactional

View File

@@ -46,7 +46,7 @@ import org.springframework.lang.Nullable;
* 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 class JdbcAggregateChangeExecutorContextImmutableUnitTests {
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
List<DummyEntity> 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 class JdbcAggregateChangeExecutorContextImmutableUnitTests {
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<DummyEntity> rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "content", content, null));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
List<DummyEntity> 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 class JdbcAggregateChangeExecutorContextImmutableUnitTests {
executionContext.executeInsertRoot(rootInsert);
executionContext.executeInsert(createInsert(rootInsert, "list", content, 1));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
List<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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 @@ public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
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<RelationalPersistentProperty> toPath(String path) {

View File

@@ -18,6 +18,7 @@ package org.springframework.data.jdbc.core;
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.DataAccessStrategy;
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 class JdbcAggregateChangeExecutorContextUnitTests {
executionContext.executeInsertRoot(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED));
DummyEntity newRoot = executionContext.populateIdsIfNecessary();
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
assertThat(newRoot).isEqualTo(root);
assertThat(newRoots).containsExactly(root);
assertThat(root.id).isEqualTo(23L);
}
@@ -81,15 +81,15 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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<DummyEntity> 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<DummyEntity> 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 class JdbcAggregateChangeExecutorContextUnitTests {
DbAction.InsertRoot<DummyEntity> 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<DummyEntity> 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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
.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<DummyEntity> 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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
.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<DummyEntity> 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<DummyEntity> rootInsert = new DbAction.UpdateRoot<>(root, null);
executionContext.executeUpdateRoot(rootInsert);
when(accessStrategy.update(root, DummyEntity.class)).thenReturn(true);
DbAction.UpdateRoot<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
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<RelationalPersistentProperty> toPath(String path) {

View File

@@ -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.*;
@@ -47,6 +48,8 @@ import org.springframework.data.domain.PageRequest;
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;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
@@ -59,6 +62,7 @@ import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.repository.Lock;
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;
@@ -90,6 +94,7 @@ public class JdbcRepositoryIntegrationTests {
@Autowired NamedParameterJdbcTemplate template;
@Autowired DummyEntityRepository repository;
@Autowired MyEventListener eventListener;
@Autowired RootRepository rootRepository;
private static DummyEntity createDummyEntity() {
@@ -129,7 +134,7 @@ public class JdbcRepositoryIntegrationTests {
}
@Test // DATAJDBC-97
public void savesManyEntities() {
public void insertsManyEntities() {
DummyEntity entity = createDummyEntity();
DummyEntity other = createDummyEntity();
@@ -283,6 +288,20 @@ public class JdbcRepositoryIntegrationTests {
.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() {
@@ -608,6 +627,84 @@ public class JdbcRepositoryIntegrationTests {
.containsExactlyInAnyOrder(Direction.CENTER);
}
@Test // GH-537
void manyInsertsWithNestedEntities() {
Root root1 = createRoot("root1");
Root root2 = createRoot("root2");
List<Root> savedRoots = rootRepository.saveAll(asList(root1, root2));
List<Root> 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<Root> 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<Root> updatedRoots = rootRepository.saveAll(asList(updatedRoot1, updatedRoot2));
List<Root> 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<Root> savedRoots = rootRepository.saveAll(asList(updatedRoot1, root2));
List<Root> 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();
@@ -717,6 +814,11 @@ public class JdbcRepositoryIntegrationTests {
return factory.getRepository(DummyEntityRepository.class);
}
@Bean
RootRepository rootRepository() {
return factory.getRepository(RootRepository.class);
}
@Bean
NamedQueries namedQueries() throws IOException {
@@ -732,6 +834,32 @@ public class JdbcRepositoryIntegrationTests {
}
}
interface RootRepository extends ListCrudRepository<Root, Long> {
List<Root> findAllByOrderByIdAsc();
}
@Value
static class Root {
@Id Long id;
String name;
Intermediate intermediate;
@MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") List<Intermediate> intermediates;
}
@Value
static class Intermediate {
@Id Long id;
String name;
Leaf leaf;
@MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") List<Leaf> leaves;
}
@Value
static class Leaf {
@Id Long id;
String name;
}
static class MyEventListener implements ApplicationListener<AbstractRelationalEvent<?>> {
private List<AbstractRelationalEvent<?>> events = new ArrayList<>();

View File

@@ -68,6 +68,8 @@ import org.springframework.lang.Nullable;
*/
public class SimpleJdbcRepositoryEventsUnitTests {
private static final long generatedId = 4711L;
CollectingEventPublisher publisher = new CollectingEventPublisher();
DummyEntityRepository repository;
@@ -125,14 +127,14 @@ public class SimpleJdbcRepositoryEventsUnitTests {
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) //
);
}
@@ -275,7 +277,7 @@ public class SimpleJdbcRepositoryEventsUnitTests {
Answer<Integer> setIdInKeyHolder = invocation -> {
HashMap<String, Object> keys = new HashMap<>();
keys.put("id", 4711L);
keys.put("id", generatedId);
KeyHolder keyHolder = invocation.getArgument(2);
keyHolder.getKeyList().add(keys);

View File

@@ -1,4 +1,7 @@
DROP TABLE dummy_entity;
DROP TABLE ROOT;
DROP TABLE INTERMEDIATE;
DROP TABLE LEAF;
CREATE TABLE dummy_entity
(
@@ -10,3 +13,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -8,3 +8,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -8,3 +8,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -8,3 +8,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -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,
@@ -9,3 +13,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -11,3 +11,26 @@ CREATE TABLE DUMMY_ENTITY
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);

View File

@@ -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
(
@@ -10,3 +13,25 @@ CREATE TABLE DUMMY_ENTITY
REF NUMBER,
DIRECTION VARCHAR2(100)
);
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
);

View File

@@ -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,
@@ -9,3 +13,25 @@ CREATE TABLE dummy_entity
REF BIGINT,
DIRECTION VARCHAR(100)
);
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
);