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:
committed by
Jens Schauder
parent
d3d05039da
commit
6b02a4e627
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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<T, C extends MutableAggregateChange<T>> extends AggregateChange<T> {
|
||||
/**
|
||||
* 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 <T> entity type.
|
||||
* @return the {@link BatchingAggregateChange} for saving root entities.
|
||||
* @since 3.0
|
||||
*/
|
||||
static <T> BatchingAggregateChange<T, RootAggregateChange<T>> forSave(Class<T> entityClass) {
|
||||
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return new SaveBatchingAggregateChange<>(entityClass);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,10 @@ package org.springframework.data.relational.core.conversion;
|
||||
|
||||
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;
|
||||
@@ -344,38 +346,56 @@ public interface DbAction<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a batch of {@link DbAction} that share a common value for a property of the action.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class BatchWithValue<T, A extends DbAction<T>, B> implements DbAction<T> {
|
||||
private final List<A> actions;
|
||||
private final B batchValue;
|
||||
|
||||
public BatchWithValue(List<A> actions, Function<A, B> batchValueExtractor) {
|
||||
Assert.notEmpty(actions, "Actions must contain at least one action");
|
||||
Iterator<A> 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<T> getEntityType() {
|
||||
return actions.get(0).getEntityType();
|
||||
}
|
||||
|
||||
public List<A> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public B getBatchValue() {
|
||||
return batchValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BatchWithValue{" + "actions=" + actions + ", batchValue=" + batchValue + '}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a batch insert statement for a multiple entities that are not aggregate roots.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
* @since 2.4
|
||||
*/
|
||||
final class InsertBatch<T> implements DbAction<T> {
|
||||
private final List<Insert<T>> inserts;
|
||||
private final IdValueSource idValueSource;
|
||||
|
||||
public InsertBatch(List<Insert<T>> inserts, IdValueSource idValueSource) {
|
||||
Assert.notEmpty(inserts, "Inserts must contains at least one insert");
|
||||
this.inserts = inserts;
|
||||
this.idValueSource = idValueSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getEntityType() {
|
||||
return inserts.get(0).getEntityType();
|
||||
}
|
||||
|
||||
public List<Insert<T>> getInserts() {
|
||||
return inserts;
|
||||
}
|
||||
|
||||
public IdValueSource getIdValueSource() {
|
||||
return idValueSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InsertBatch{" + "inserts=" + inserts + ", idValueSource=" + idValueSource + '}';
|
||||
final class BatchInsert<T> extends BatchWithValue<T, Insert<T>, IdValueSource> {
|
||||
public BatchInsert(List<Insert<T>> actions) {
|
||||
super(actions, Insert::getIdValueSource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ import org.springframework.util.Assert;
|
||||
* 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<T> implements AggregateChangeWithRoot<T> {
|
||||
class DefaultRootAggregateChange<T> implements RootAggregateChange<T> {
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
@@ -42,7 +42,7 @@ class DefaultAggregateChangeWithRoot<T> implements AggregateChangeWithRoot<T> {
|
||||
/** The previous version assigned to the instance being changed, if available */
|
||||
@Nullable private final Number previousVersion;
|
||||
|
||||
public DefaultAggregateChangeWithRoot(Kind kind, Class<T> entityType, @Nullable Number previousVersion) {
|
||||
public DefaultRootAggregateChange(Kind kind, Class<T> entityType, @Nullable Number previousVersion) {
|
||||
|
||||
this.kind = kind;
|
||||
this.entityType = entityType;
|
||||
@@ -30,32 +30,32 @@ import org.springframework.util.ClassUtils;
|
||||
public interface MutableAggregateChange<T> extends AggregateChange<T> {
|
||||
|
||||
/**
|
||||
* 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 <T> 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 <T> AggregateChangeWithRoot<T> forSave(T entity) {
|
||||
static <T> RootAggregateChange<T> 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 <T> 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 <T> AggregateChangeWithRoot<T> forSave(T entity, @Nullable Number previousVersion) {
|
||||
static <T> RootAggregateChange<T> forSave(T entity, @Nullable Number previousVersion) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
return new DefaultAggregateChangeWithRoot<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), previousVersion);
|
||||
return new DefaultRootAggregateChange<>(Kind.SAVE, (Class<T>) ClassUtils.getUserClass(entity), previousVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.springframework.data.convert.EntityWriter;
|
||||
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 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
* @author Chirag Tailor
|
||||
* @since 1.1
|
||||
*/
|
||||
public class RelationalEntityInsertWriter<T> implements EntityWriter<T, AggregateChangeWithRoot<T>> {
|
||||
public class RelationalEntityInsertWriter<T> implements EntityWriter<T, RootAggregateChange<T>> {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class RelationalEntityInsertWriter<T> implements EntityWriter<T, Aggregat
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T root, AggregateChangeWithRoot<T> aggregateChange) {
|
||||
public void write(T root, RootAggregateChange<T> aggregateChange) {
|
||||
new WritingContext<>(context, root, aggregateChange).insert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.springframework.data.convert.EntityWriter;
|
||||
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 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
* @author Chirag Tailor
|
||||
* @since 1.1
|
||||
*/
|
||||
public class RelationalEntityUpdateWriter<T> implements EntityWriter<T, AggregateChangeWithRoot<T>> {
|
||||
public class RelationalEntityUpdateWriter<T> implements EntityWriter<T, RootAggregateChange<T>> {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class RelationalEntityUpdateWriter<T> implements EntityWriter<T, Aggregat
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T root, AggregateChangeWithRoot<T> aggregateChange) {
|
||||
public void write(T root, RootAggregateChange<T> aggregateChange) {
|
||||
new WritingContext<>(context, root, aggregateChange).update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import org.springframework.data.convert.EntityWriter;
|
||||
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<T> implements EntityWriter<T, AggregateChangeWithRoot<T>> {
|
||||
public class RelationalEntityWriter<T> implements EntityWriter<T, RootAggregateChange<T>> {
|
||||
|
||||
private final RelationalMappingContext context;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class RelationalEntityWriter<T> implements EntityWriter<T, AggregateChang
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(T root, AggregateChangeWithRoot<T> aggregateChange) {
|
||||
public void write(T root, RootAggregateChange<T> aggregateChange) {
|
||||
new WritingContext<>(context, root, aggregateChange).save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package org.springframework.data.relational.core.conversion;
|
||||
* 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
|
||||
*/
|
||||
public interface AggregateChangeWithRoot<T> extends MutableAggregateChange<T> {
|
||||
public interface RootAggregateChange<T> extends MutableAggregateChange<T> {
|
||||
|
||||
/**
|
||||
* The root object to which this {@link AggregateChange} relates. Guaranteed to be not {@code null}.
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 static java.util.Collections.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link BatchingAggregateChange} implementation for save changes that can contain actions for any mix of insert and
|
||||
* update operations. When consumed, actions are yielded in the appropriate entity tree order with inserts carried out
|
||||
* from root to leaves and deletes in reverse. All insert operations are grouped into batches to offer the ability for
|
||||
* an optimized batch operation to be used.
|
||||
*
|
||||
* @author Chirag Tailor
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T, RootAggregateChange<T>> {
|
||||
|
||||
private static final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> pathLengthComparator = //
|
||||
Comparator.comparing(PersistentPropertyPath::getLength);
|
||||
|
||||
private final Class<T> entityType;
|
||||
private final List<DbAction.WithRoot<?>> rootActions = new ArrayList<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
|
||||
new HashMap<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<?>>> deleteActions = //
|
||||
new HashMap<>();
|
||||
|
||||
public SaveBatchingAggregateChange(Class<T> entityType) {
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.SAVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getEntityType() {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
|
||||
|
||||
Assert.notNull(consumer, "Consumer must not be null.");
|
||||
|
||||
rootActions.forEach(consumer);
|
||||
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
|
||||
.forEach((entry) -> entry.getValue().forEach(consumer));
|
||||
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator))
|
||||
.forEach((entry) -> entry.getValue()
|
||||
.forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(RootAggregateChange<T> aggregateChange) {
|
||||
|
||||
aggregateChange.forEachAction(action -> {
|
||||
if (action instanceof DbAction.WithRoot<?> rootAction) {
|
||||
rootActions.add(rootAction);
|
||||
} else if (action instanceof DbAction.Insert<?>) {
|
||||
// noinspection unchecked
|
||||
addInsert((DbAction.Insert<Object>) action);
|
||||
} else if (action instanceof DbAction.Delete<?> deleteAction) {
|
||||
addDelete(deleteAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addInsert(DbAction.Insert<Object> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
insertActions.merge(propertyPath,
|
||||
new HashMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))),
|
||||
(map, mapDefaultValue) -> {
|
||||
map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)),
|
||||
(actions, listDefaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
}
|
||||
|
||||
private void addDelete(DbAction.Delete<?> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
deleteActions.merge(propertyPath, new ArrayList<>(singletonList(action)), (actions, defaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
@@ -52,9 +51,9 @@ class WritingContext<T> {
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>();
|
||||
private final IdValueSource rootIdValueSource;
|
||||
@Nullable private final Number previousVersion;
|
||||
private final AggregateChangeWithRoot<T> aggregateChange;
|
||||
private final RootAggregateChange<T> aggregateChange;
|
||||
|
||||
WritingContext(RelationalMappingContext context, T root, AggregateChangeWithRoot<T> aggregateChange) {
|
||||
WritingContext(RelationalMappingContext context, T root, RootAggregateChange<T> aggregateChange) {
|
||||
|
||||
this.context = context;
|
||||
this.root = root;
|
||||
@@ -150,15 +149,7 @@ class WritingContext<T> {
|
||||
inserts.add(insert);
|
||||
previousActions.put(node, insert);
|
||||
});
|
||||
return inserts.stream().collect(Collectors.groupingBy(DbAction.Insert::getIdValueSource)).entrySet().stream()
|
||||
.filter(entry -> (!entry.getValue().isEmpty())).map(entry -> {
|
||||
|
||||
List<DbAction.Insert<Object>> batch = entry.getValue();
|
||||
if (batch.size() > 1) {
|
||||
return new DbAction.InsertBatch<>(batch, entry.getKey());
|
||||
}
|
||||
return batch.get(0);
|
||||
}).collect(Collectors.toList());
|
||||
return inserts;
|
||||
}
|
||||
|
||||
private List<DbAction<?>> deleteReferenced() {
|
||||
|
||||
@@ -56,8 +56,8 @@ class DbActionTestSupport {
|
||||
return ((DbAction.InsertRoot<?>) action).getIdValueSource();
|
||||
} else if (action instanceof DbAction.Insert) {
|
||||
return ((DbAction.Insert<?>) action).getIdValueSource();
|
||||
} else if (action instanceof DbAction.InsertBatch) {
|
||||
return ((DbAction.InsertBatch<?>) action).getIdValueSource();
|
||||
} else if (action instanceof DbAction.BatchInsert) {
|
||||
return ((DbAction.BatchInsert<?>) action).getBatchValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Taylor
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RelationalEntityDeleteWriterUnitTests {
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
* Unit tests for the {@link RelationalEntityInsertWriter}
|
||||
*
|
||||
* @author Thomas Lang
|
||||
* @author Chirag Taylor
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RelationalEntityInsertWriterUnitTests {
|
||||
@@ -45,7 +45,7 @@ public class RelationalEntityInsertWriterUnitTests {
|
||||
public void newEntityGetsConvertedToOneInsert() {
|
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(null);
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityInsertWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class RelationalEntityInsertWriterUnitTests {
|
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
|
||||
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityInsertWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
*
|
||||
* @author Thomas Lang
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Taylor
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RelationalEntityUpdateWriterUnitTests {
|
||||
@@ -46,7 +46,7 @@ public class RelationalEntityUpdateWriterUnitTests {
|
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
|
||||
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityUpdateWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -38,7 +36,6 @@ import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertBatch;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.UpdateRoot;
|
||||
import org.springframework.data.relational.core.mapping.Embedded;
|
||||
@@ -84,7 +81,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
public void newEntityGetsConvertedToOneInsert() {
|
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(null);
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -105,7 +102,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
void newEntityWithPrimitiveLongId_insertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
PrimitiveLongIdEntity entity = new PrimitiveLongIdEntity();
|
||||
|
||||
AggregateChangeWithRoot<PrimitiveLongIdEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<PrimitiveLongIdEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<PrimitiveLongIdEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -126,7 +123,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
void newEntityWithPrimitiveIntId_insertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
PrimitiveIntIdEntity entity = new PrimitiveIntIdEntity();
|
||||
|
||||
AggregateChangeWithRoot<PrimitiveIntIdEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<PrimitiveIntIdEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<PrimitiveIntIdEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -149,7 +146,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
EmbeddedReferenceEntity entity = new EmbeddedReferenceEntity(null);
|
||||
entity.other = new Element(2L);
|
||||
|
||||
AggregateChangeWithRoot<EmbeddedReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<EmbeddedReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<EmbeddedReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -172,7 +169,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(null);
|
||||
entity.other = new Element(null);
|
||||
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -197,7 +194,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
entity.primitiveLongIdEntity = new PrimitiveLongIdEntity();
|
||||
entity.primitiveIntIdEntity = new PrimitiveIntIdEntity();
|
||||
|
||||
AggregateChangeWithRoot<EntityWithReferencesToPrimitiveIdEntity> aggregateChange = MutableAggregateChange
|
||||
RootAggregateChange<EntityWithReferencesToPrimitiveIdEntity> aggregateChange = MutableAggregateChange
|
||||
.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<EntityWithReferencesToPrimitiveIdEntity>(context).write(entity, aggregateChange);
|
||||
@@ -224,7 +221,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
|
||||
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
|
||||
new RelationalEntityWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -247,7 +244,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID);
|
||||
entity.other = new Element(null);
|
||||
|
||||
AggregateChangeWithRoot<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
RootAggregateChange<SingleReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
|
||||
new RelationalEntityWriter<SingleReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -269,7 +266,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
public void newEntityWithEmptySetResultsInSingleInsert() {
|
||||
|
||||
SetContainer entity = new SetContainer(null);
|
||||
AggregateChangeWithRoot<SetContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SetContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<SetContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -285,13 +282,13 @@ public class RelationalEntityWriterUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-113
|
||||
public void newEntityWithSetContainingMultipleElementsResultsInAnInsertForTheBatch() {
|
||||
public void newEntityWithSetContainingMultipleElementsResultsInAnInsertForEach() {
|
||||
|
||||
SetContainer entity = new SetContainer(null);
|
||||
entity.elements.add(new Element(null));
|
||||
entity.elements.add(new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<SetContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<SetContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
new RelationalEntityWriter<SetContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(aggregateChange);
|
||||
@@ -303,16 +300,6 @@ public class RelationalEntityWriterUnitTests {
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, SetContainer.class, "", SetContainer.class, false, IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, "", null, false, IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> batchedInsertActions = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(batchedInsertActions).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, Element.class, "elements", Element.class, true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "elements", Element.class, true, IdValueSource.GENERATED) //
|
||||
);
|
||||
@@ -333,7 +320,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
new Element(null)) //
|
||||
);
|
||||
|
||||
AggregateChangeWithRoot<CascadingReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<CascadingReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<CascadingReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -347,31 +334,10 @@ public class RelationalEntityWriterUnitTests {
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false,
|
||||
IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, CascadingReferenceMiddleElement.class, "", null, false, IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, "", null, false, IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<CascadingReferenceMiddleElement>> middleElementInserts = getInsertBatchAction(actions,
|
||||
CascadingReferenceMiddleElement.class).getInserts();
|
||||
assertThat(middleElementInserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
|
||||
true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
|
||||
true, IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> leafElementInserts = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(leafElementInserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
@@ -394,7 +360,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
new Element(null)) //
|
||||
);
|
||||
|
||||
AggregateChangeWithRoot<CascadingReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
RootAggregateChange<CascadingReferenceEntity> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
|
||||
new RelationalEntityWriter<CascadingReferenceEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -409,31 +375,10 @@ public class RelationalEntityWriterUnitTests {
|
||||
tuple(UpdateRoot.class, CascadingReferenceEntity.class, "", CascadingReferenceEntity.class, false, null), //
|
||||
tuple(Delete.class, Element.class, "other.element", null, false, null),
|
||||
tuple(Delete.class, CascadingReferenceMiddleElement.class, "other", null, false, null),
|
||||
tuple(InsertBatch.class, CascadingReferenceMiddleElement.class, "", null, false, IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, "", null, false, IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<CascadingReferenceMiddleElement>> middleElementInserts = getInsertBatchAction(actions,
|
||||
CascadingReferenceMiddleElement.class).getInserts();
|
||||
assertThat(middleElementInserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
|
||||
true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, CascadingReferenceMiddleElement.class, "other", CascadingReferenceMiddleElement.class,
|
||||
true, IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> elementInserts = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(elementInserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "other.element", Element.class, true, IdValueSource.GENERATED), //
|
||||
@@ -445,7 +390,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
public void newEntityWithEmptyMapResultsInSingleInsert() {
|
||||
|
||||
MapContainer entity = new MapContainer(null);
|
||||
AggregateChangeWithRoot<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<MapContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -464,28 +409,25 @@ public class RelationalEntityWriterUnitTests {
|
||||
entity.elements.put("one", new Element(null));
|
||||
entity.elements.put("two", new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
new RelationalEntityWriter<MapContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(aggregateChange);
|
||||
assertThat(actions).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getMapKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, null, "", IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> inserts = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(inserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getMapKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactlyInAnyOrder( //
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "one", "elements", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "two", "elements", IdValueSource.GENERATED) //
|
||||
).containsSubsequence( // container comes before the elements
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "two", "elements", IdValueSource.GENERATED) //
|
||||
).containsSubsequence( // container comes before the elements
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "one", "elements", IdValueSource.GENERATED) //
|
||||
);
|
||||
}
|
||||
|
||||
@@ -507,26 +449,17 @@ public class RelationalEntityWriterUnitTests {
|
||||
entity.elements.put("a", new Element(null));
|
||||
entity.elements.put("b", new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
new RelationalEntityWriter<MapContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(aggregateChange);
|
||||
assertThat(actions).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getMapKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, null, "", IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> inserts = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(inserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getMapKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactlyInAnyOrder( //
|
||||
tuple(InsertRoot.class, MapContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "1", "elements", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "2", "elements", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, "3", "elements", IdValueSource.GENERATED), //
|
||||
@@ -546,7 +479,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
public void newEntityWithEmptyListResultsInSingleInsert() {
|
||||
|
||||
ListContainer entity = new ListContainer(null);
|
||||
AggregateChangeWithRoot<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<ListContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -565,7 +498,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
entity.elements.add(new Element(null));
|
||||
entity.elements.add(new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
new RelationalEntityWriter<ListContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(aggregateChange);
|
||||
@@ -576,15 +509,6 @@ public class RelationalEntityWriterUnitTests {
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, ListContainer.class, null, "", IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, null, "", IdValueSource.GENERATED) //
|
||||
);
|
||||
List<Insert<Element>> inserts = getInsertBatchAction(actions, Element.class).getInserts();
|
||||
assertThat(inserts).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getListKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, Element.class, 0, "elements", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, 1, "elements", IdValueSource.GENERATED) //
|
||||
);
|
||||
@@ -596,7 +520,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
MapContainer entity = new MapContainer(SOME_ENTITY_ID);
|
||||
entity.elements.put("one", new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
RootAggregateChange<MapContainer> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
|
||||
new RelationalEntityWriter<MapContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -619,7 +543,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
ListContainer entity = new ListContainer(SOME_ENTITY_ID);
|
||||
entity.elements.add(new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
RootAggregateChange<ListContainer> aggregateChange = MutableAggregateChange.forSave(entity, 1L);
|
||||
|
||||
new RelationalEntityWriter<ListContainer>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -643,7 +567,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
listMapContainer.maps.add(new MapContainer(SOME_ENTITY_ID));
|
||||
listMapContainer.maps.get(0).elements.put("one", new Element(null));
|
||||
|
||||
AggregateChangeWithRoot<ListMapContainer> aggregateChange = MutableAggregateChange.forSave(listMapContainer, 1L);
|
||||
RootAggregateChange<ListMapContainer> aggregateChange = MutableAggregateChange.forSave(listMapContainer, 1L);
|
||||
|
||||
new RelationalEntityWriter<ListMapContainer>(context).write(listMapContainer, aggregateChange);
|
||||
|
||||
@@ -670,7 +594,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
listMapContainer.maps.add(new NoIdMapContainer());
|
||||
listMapContainer.maps.get(0).elements.put("one", new NoIdElement());
|
||||
|
||||
AggregateChangeWithRoot<NoIdListMapContainer> aggregateChange = MutableAggregateChange.forSave(listMapContainer,
|
||||
RootAggregateChange<NoIdListMapContainer> aggregateChange = MutableAggregateChange.forSave(listMapContainer,
|
||||
1L);
|
||||
|
||||
new RelationalEntityWriter<NoIdListMapContainer>(context).write(listMapContainer, aggregateChange);
|
||||
@@ -697,7 +621,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
EmbeddedReferenceChainEntity entity = new EmbeddedReferenceChainEntity(null);
|
||||
// the embedded is null !!!
|
||||
|
||||
AggregateChangeWithRoot<EmbeddedReferenceChainEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<EmbeddedReferenceChainEntity> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<EmbeddedReferenceChainEntity>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -721,7 +645,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
root.other = new EmbeddedReferenceChainEntity(null);
|
||||
// the embedded is null !!!
|
||||
|
||||
AggregateChangeWithRoot<RootWithEmbeddedReferenceChainEntity> aggregateChange = MutableAggregateChange
|
||||
RootAggregateChange<RootWithEmbeddedReferenceChainEntity> aggregateChange = MutableAggregateChange
|
||||
.forSave(root);
|
||||
|
||||
new RelationalEntityWriter<RootWithEmbeddedReferenceChainEntity>(context).write(root, aggregateChange);
|
||||
@@ -742,61 +666,13 @@ public class RelationalEntityWriterUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void newEntityWithCollectionWhereSomeElementsHaveIdSet_producesABatchInsertEachForElementsWithIdAndWithout() {
|
||||
|
||||
ListContainer root = new ListContainer(null);
|
||||
root.elements.add(new Element(null));
|
||||
root.elements.add(new Element(1L));
|
||||
root.elements.add(new Element(null));
|
||||
root.elements.add(new Element(2L));
|
||||
AggregateChangeWithRoot<ListContainer> aggregateChange = MutableAggregateChange.forSave(root);
|
||||
|
||||
new RelationalEntityWriter<ListContainer>(context).write(root, aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(aggregateChange);
|
||||
assertThat(actions).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsSubsequence(
|
||||
tuple(InsertRoot.class, ListContainer.class, "", ListContainer.class, false, IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, "", null, false, IdValueSource.PROVIDED) //
|
||||
).containsSubsequence( //
|
||||
tuple(InsertRoot.class, ListContainer.class, "", ListContainer.class, false, IdValueSource.GENERATED), //
|
||||
tuple(InsertBatch.class, Element.class, "", null, false, IdValueSource.GENERATED) //
|
||||
);
|
||||
InsertBatch<Element> insertBatchWithoutId = getInsertBatchAction(actions, Element.class, IdValueSource.GENERATED);
|
||||
assertThat(insertBatchWithoutId.getInserts()).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getListKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, Element.class, 0, "elements", IdValueSource.GENERATED), //
|
||||
tuple(Insert.class, Element.class, 2, "elements", IdValueSource.GENERATED) //
|
||||
);
|
||||
InsertBatch<Element> insertBatchWithId = getInsertBatchAction(actions, Element.class, IdValueSource.PROVIDED);
|
||||
assertThat(insertBatchWithId.getInserts()).extracting(DbAction::getClass, //
|
||||
DbAction::getEntityType, //
|
||||
this::getListKey, //
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::insertIdValueSource) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, Element.class, 1, "elements", IdValueSource.PROVIDED), //
|
||||
tuple(Insert.class, Element.class, 3, "elements", IdValueSource.PROVIDED) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void newEntityWithCollection_whenElementHasPrimitiveId_batchInsertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
void newEntityWithCollection_whenElementHasPrimitiveId_doesNotIncludeId_whenIdValueIsZero() {
|
||||
|
||||
EntityWithReferencesToPrimitiveIdEntity entity = new EntityWithReferencesToPrimitiveIdEntity(null);
|
||||
entity.primitiveLongIdEntities.add(new PrimitiveLongIdEntity());
|
||||
entity.primitiveIntIdEntities.add(new PrimitiveIntIdEntity());
|
||||
|
||||
AggregateChangeWithRoot<EntityWithReferencesToPrimitiveIdEntity> aggregateChange = MutableAggregateChange
|
||||
RootAggregateChange<EntityWithReferencesToPrimitiveIdEntity> aggregateChange = MutableAggregateChange
|
||||
.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<EntityWithReferencesToPrimitiveIdEntity>(context).write(entity, aggregateChange);
|
||||
@@ -824,7 +700,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
WithReadOnlyReference entity = new WithReadOnlyReference(null);
|
||||
entity.readOnly = new Element(SOME_ENTITY_ID);
|
||||
|
||||
AggregateChangeWithRoot<WithReadOnlyReference> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<WithReadOnlyReference> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<WithReadOnlyReference>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -844,7 +720,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
WithReadOnlyReference entity = new WithReadOnlyReference(SOME_ENTITY_ID);
|
||||
entity.readOnly = new Element(SOME_ENTITY_ID);
|
||||
|
||||
AggregateChangeWithRoot<WithReadOnlyReference> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
RootAggregateChange<WithReadOnlyReference> aggregateChange = MutableAggregateChange.forSave(entity);
|
||||
|
||||
new RelationalEntityWriter<WithReadOnlyReference>(context).write(entity, aggregateChange);
|
||||
|
||||
@@ -865,29 +741,6 @@ public class RelationalEntityWriterUnitTests {
|
||||
return actions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <T> InsertBatch<T> getInsertBatchAction(List<DbAction<?>> actions, Class<T> entityType) {
|
||||
return getInsertBatchActions(actions, entityType).stream().findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("No InsertBatch action found!"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <T> InsertBatch<T> getInsertBatchAction(List<DbAction<?>> actions, Class<T> entityType,
|
||||
IdValueSource idValueSource) {
|
||||
return getInsertBatchActions(actions, entityType).stream()
|
||||
.filter(insertBatch -> insertBatch.getIdValueSource() == idValueSource).findFirst().orElseThrow(
|
||||
() -> new RuntimeException(String.format("No InsertBatch with includeId '%s' found!", idValueSource)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <T> List<InsertBatch<T>> getInsertBatchActions(List<DbAction<?>> actions, Class<T> entityType) {
|
||||
// noinspection unchecked
|
||||
return actions.stream() //
|
||||
.filter(dbAction -> dbAction instanceof InsertBatch) //
|
||||
.filter(dbAction -> dbAction.getEntityType().equals(entityType)) //
|
||||
.map(dbAction -> (InsertBatch<T>) dbAction).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private CascadingReferenceMiddleElement createMiddleElement(Element first, Element second) {
|
||||
|
||||
CascadingReferenceMiddleElement middleElement1 = new CascadingReferenceMiddleElement(null);
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright 2020-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 static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.groups.Tuple;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SaveBatchingAggregateChange}.
|
||||
*
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
class SaveBatchingAggregateChangeTest {
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
|
||||
@Test
|
||||
void startsWithNoActions() {
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
|
||||
assertThat(extractActions(change)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsRootActions() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(root1Insert);
|
||||
Root root2 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
|
||||
aggregateChange2.setRootAction(root2Insert);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
assertThat(extractActions(change)).containsExactly(root1Insert, root2Insert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsRootActionsBeforeDeleteActions() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
DbAction.UpdateRoot<Root> root1Update = new DbAction.UpdateRoot<>(root1, null);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(root1Update);
|
||||
DbAction.Delete<?> root1IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange1.addAction(root1IntermediateDelete);
|
||||
Root root2 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
|
||||
aggregateChange2.setRootAction(root2Insert);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
assertThat(extractActions(change)).extracting(DbAction::getClass, DbAction::getEntityType).containsExactly( //
|
||||
Tuple.tuple(DbAction.UpdateRoot.class, Root.class), //
|
||||
Tuple.tuple(DbAction.InsertRoot.class, Root.class), //
|
||||
Tuple.tuple(DbAction.Delete.class, Intermediate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsNestedDeleteActionsInTreeOrderFromLeavesToRoot() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(new DbAction.UpdateRoot<>(root1, null));
|
||||
DbAction.Delete<?> root1IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange1.addAction(root1IntermediateDelete);
|
||||
|
||||
Root root2 = new Root(1L, null);
|
||||
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
|
||||
aggregateChange2.setRootAction(new DbAction.UpdateRoot<>(root2, null));
|
||||
DbAction.Delete<?> root2LeafDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate.leaf", Root.class));
|
||||
aggregateChange2.addAction(root2LeafDelete);
|
||||
DbAction.Delete<?> root2IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange2.addAction(root2IntermediateDelete);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
assertThat(extractActions(change)).containsSubsequence(root2LeafDelete, root1IntermediateDelete,
|
||||
root2IntermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsDeleteActionsBeforeInsertActions() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(root1Insert);
|
||||
Intermediate root1Intermediate = new Intermediate(null, "root1Intermediate", null);
|
||||
DbAction.Insert<?> root1IntermediateInsert = new DbAction.Insert<>(root1Intermediate,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class), root1Insert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange1.addAction(root1IntermediateInsert);
|
||||
|
||||
Root root2 = new Root(1L, null);
|
||||
DbAction.UpdateRoot<Root> root2Update = new DbAction.UpdateRoot<>(root2, null);
|
||||
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
|
||||
aggregateChange2.setRootAction(root2Update);
|
||||
DbAction.Delete<?> root2IntermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange2.addAction(root2IntermediateDelete);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
assertThat(extractActions(change)).extracting(DbAction::getClass, DbAction::getEntityType).containsSubsequence( //
|
||||
Tuple.tuple(DbAction.Delete.class, Intermediate.class), //
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsInsertActionsAsBatchInserts_groupedByIdValueSource() {
|
||||
|
||||
Root root = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> rootInsert = new DbAction.InsertRoot<>(root, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange = MutableAggregateChange.forSave(root);
|
||||
aggregateChange.setRootAction(rootInsert);
|
||||
Intermediate intermediateGeneratedId = new Intermediate(null, "intermediateGeneratedId", null);
|
||||
DbAction.Insert<Intermediate> intermediateInsertGeneratedId = new DbAction.Insert<>(intermediateGeneratedId,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class), rootInsert, emptyMap(), IdValueSource.GENERATED);
|
||||
aggregateChange.addAction(intermediateInsertGeneratedId);
|
||||
Intermediate intermediateProvidedId = new Intermediate(123L, "intermediateProvidedId", null);
|
||||
DbAction.Insert<Intermediate> intermediateInsertProvidedId = new DbAction.Insert<>(intermediateProvidedId,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class), rootInsert, emptyMap(), IdValueSource.PROVIDED);
|
||||
aggregateChange.addAction(intermediateInsertProvidedId);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions)
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource) //
|
||||
.containsSubsequence( //
|
||||
Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.GENERATED), //
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.PROVIDED)) //
|
||||
.containsSubsequence( //
|
||||
Tuple.tuple(DbAction.InsertRoot.class, Root.class, IdValueSource.GENERATED), //
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED)) //
|
||||
.doesNotContain(Tuple.tuple(DbAction.Insert.class, Intermediate.class));
|
||||
assertThat(getBatchInsertAction(actions, Intermediate.class, IdValueSource.GENERATED).getActions())
|
||||
.containsExactly(intermediateInsertGeneratedId);
|
||||
assertThat(getBatchInsertAction(actions, Intermediate.class, IdValueSource.PROVIDED).getActions())
|
||||
.containsExactly(intermediateInsertProvidedId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsNestedInsertActionsInTreeOrderFromRootToLeaves() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root1Insert = new DbAction.InsertRoot<>(root1, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange1 = MutableAggregateChange.forSave(root1);
|
||||
aggregateChange1.setRootAction(root1Insert);
|
||||
Intermediate root1Intermediate = new Intermediate(null, "root1Intermediate", null);
|
||||
DbAction.Insert<Intermediate> root1IntermediateInsert = new DbAction.Insert<>(root1Intermediate,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class), root1Insert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange1.addAction(root1IntermediateInsert);
|
||||
Leaf root1Leaf = new Leaf(null, "root1Leaf");
|
||||
DbAction.Insert<Leaf> root1LeafInsert = new DbAction.Insert<>(root1Leaf,
|
||||
context.getPersistentPropertyPath("intermediate.leaf", Root.class), root1IntermediateInsert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange1.addAction(root1LeafInsert);
|
||||
|
||||
Root root2 = new Root(null, null);
|
||||
DbAction.InsertRoot<Root> root2Insert = new DbAction.InsertRoot<>(root2, IdValueSource.GENERATED);
|
||||
RootAggregateChange<Root> aggregateChange2 = MutableAggregateChange.forSave(root2);
|
||||
aggregateChange2.setRootAction(root2Insert);
|
||||
Intermediate root2Intermediate = new Intermediate(null, "root2Intermediate", null);
|
||||
DbAction.Insert<Intermediate> root2IntermediateInsert = new DbAction.Insert<>(root2Intermediate,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class), root2Insert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange2.addAction(root2IntermediateInsert);
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
change.add(aggregateChange1);
|
||||
change.add(aggregateChange2);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions)
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
|
||||
.containsSubsequence( //
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED),
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Leaf.class, IdValueSource.GENERATED));
|
||||
assertThat(getBatchInsertAction(actions, Intermediate.class).getActions()) //
|
||||
.containsExactly(root1IntermediateInsert, root2IntermediateInsert);
|
||||
assertThat(getBatchInsertAction(actions, Leaf.class).getActions()) //
|
||||
.containsExactly(root1LeafInsert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yieldsInsertsWithSameLengthReferences_asSeparateInserts() {
|
||||
|
||||
RootWithSameLengthReferences root = new RootWithSameLengthReferences(null, null, null);
|
||||
DbAction.InsertRoot<RootWithSameLengthReferences> rootInsert = new DbAction.InsertRoot<>(root,
|
||||
IdValueSource.GENERATED);
|
||||
RootAggregateChange<RootWithSameLengthReferences> aggregateChange = MutableAggregateChange.forSave(root);
|
||||
aggregateChange.setRootAction(rootInsert);
|
||||
Intermediate one = new Intermediate(null, "one", null);
|
||||
DbAction.Insert<Intermediate> oneInsert = new DbAction.Insert<>(one,
|
||||
context.getPersistentPropertyPath("one", RootWithSameLengthReferences.class), rootInsert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange.addAction(oneInsert);
|
||||
Intermediate two = new Intermediate(null, "two", null);
|
||||
DbAction.Insert<Intermediate> twoInsert = new DbAction.Insert<>(two,
|
||||
context.getPersistentPropertyPath("two", RootWithSameLengthReferences.class), rootInsert, emptyMap(),
|
||||
IdValueSource.GENERATED);
|
||||
aggregateChange.addAction(twoInsert);
|
||||
|
||||
BatchingAggregateChange<RootWithSameLengthReferences, RootAggregateChange<RootWithSameLengthReferences>> change = //
|
||||
BatchingAggregateChange.forSave(RootWithSameLengthReferences.class);
|
||||
change.add(aggregateChange);
|
||||
|
||||
List<DbAction<?>> actions = extractActions(change);
|
||||
assertThat(actions)
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, DbActionTestSupport::insertIdValueSource)
|
||||
.containsSubsequence( //
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED),
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class, IdValueSource.GENERATED));
|
||||
List<DbAction.BatchInsert<Intermediate>> batchInsertActions = getBatchInsertActions(actions, Intermediate.class);
|
||||
assertThat(batchInsertActions).hasSize(2);
|
||||
assertThat(batchInsertActions.get(0).getActions()).containsExactly(oneInsert);
|
||||
assertThat(batchInsertActions.get(1).getActions()).containsExactly(twoInsert);
|
||||
}
|
||||
|
||||
private <T> DbAction.BatchInsert<T> getBatchInsertAction(List<DbAction<?>> actions, Class<T> entityType,
|
||||
IdValueSource idValueSource) {
|
||||
return getBatchInsertActions(actions, entityType).stream()
|
||||
.filter(batchInsert -> batchInsert.getBatchValue() == idValueSource).findFirst().orElseThrow(
|
||||
() -> new RuntimeException(String.format("No BatchInsert with batch value '%s' found!", idValueSource)));
|
||||
}
|
||||
|
||||
private <T> DbAction.BatchInsert<T> getBatchInsertAction(List<DbAction<?>> actions, Class<T> entityType) {
|
||||
return getBatchInsertActions(actions, entityType).stream().findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("No BatchInsert action found!"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<DbAction.BatchInsert<T>> getBatchInsertActions(List<DbAction<?>> actions, Class<T> entityType) {
|
||||
|
||||
return actions.stream() //
|
||||
.filter(dbAction -> dbAction instanceof DbAction.BatchInsert) //
|
||||
.filter(dbAction -> dbAction.getEntityType().equals(entityType)) //
|
||||
.map(dbAction -> (DbAction.BatchInsert<T>) dbAction).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> List<DbAction<?>> extractActions(BatchingAggregateChange<T, RootAggregateChange<T>> change) {
|
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>();
|
||||
change.forEachAction(actions::add);
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class RootWithSameLengthReferences {
|
||||
@Id Long id;
|
||||
Intermediate one;
|
||||
Intermediate two;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Root {
|
||||
@Id Long id;
|
||||
Intermediate intermediate;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Intermediate {
|
||||
@Id Long id;
|
||||
String name;
|
||||
Leaf leaf;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Leaf {
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user