Polishing.
Introduces the BatchedActions abstraction to encapsulate the different ways singular actions get combined into batched actions. Original pull request #1230 Original pull request #1229 Original pull request #1228 Original pull request #1211
This commit is contained in:
@@ -50,6 +50,7 @@ import org.springframework.util.Assert;
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
class JdbcAggregateChangeExecutionContext {
|
||||
|
||||
private static final String UPDATE_FAILED = "Failed to update entity [%s]. Id [%s] not found in database.";
|
||||
@@ -192,7 +193,7 @@ class JdbcAggregateChangeExecutionContext {
|
||||
private DbAction.WithEntity<?> getIdOwningAction(DbAction.WithEntity<?> action,
|
||||
PersistentPropertyPathExtension idPath) {
|
||||
|
||||
if (!(action instanceof DbAction.WithDependingOn)) {
|
||||
if (!(action instanceof DbAction.WithDependingOn<?> withDependingOn)) {
|
||||
|
||||
Assert.state(idPath.getLength() == 0,
|
||||
"When the id path is not empty the id providing action should be of type WithDependingOn");
|
||||
@@ -200,8 +201,6 @@ class JdbcAggregateChangeExecutionContext {
|
||||
return action;
|
||||
}
|
||||
|
||||
DbAction.WithDependingOn<?> withDependingOn = (DbAction.WithDependingOn<?>) action;
|
||||
|
||||
if (idPath.matches(withDependingOn.getPropertyPath())) {
|
||||
return action;
|
||||
}
|
||||
@@ -257,9 +256,8 @@ class JdbcAggregateChangeExecutionContext {
|
||||
roots.add((T) newEntity);
|
||||
}
|
||||
|
||||
// the id property was immutable so we have to propagate changes up the tree
|
||||
if (newEntity != action.getEntity() && action instanceof DbAction.Insert) {
|
||||
DbAction.Insert<?> insert = (DbAction.Insert<?>) action;
|
||||
// the id property was immutable, so we have to propagate changes up the tree
|
||||
if (newEntity != action.getEntity() && action instanceof DbAction.Insert<?> insert) {
|
||||
|
||||
Pair<?, ?> qualifier = insert.getQualifier();
|
||||
|
||||
@@ -463,6 +461,7 @@ class JdbcAggregateChangeExecutionContext {
|
||||
public List add(@Nullable List list, @Nullable Object qualifier, Object value) {
|
||||
|
||||
Assert.notNull(list, "List must not be null.");
|
||||
Assert.notNull(qualifier, "ListAggregator can't handle a null qualifier.");
|
||||
|
||||
int index = (int) qualifier;
|
||||
if (index >= list.size()) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Specifies a operations one can perform on a database, based on an <em>Domain Type</em>.
|
||||
* Specifies operations one can perform on a database, based on an <em>Domain Type</em>.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Thomas Lang
|
||||
@@ -90,7 +90,7 @@ public interface JdbcAggregateOperations {
|
||||
<T> void deleteAllById(Iterable<?> ids, Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Delete an aggregate identified by it's aggregate root.
|
||||
* Delete an aggregate identified by its aggregate root.
|
||||
*
|
||||
* @param aggregateRoot to delete. Must not be {@code null}.
|
||||
* @param domainType the type of the aggregate root. Must not be {@code null}.
|
||||
|
||||
@@ -287,6 +287,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
.forDelete(domainType);
|
||||
|
||||
ids.forEach(id -> {
|
||||
|
||||
DeleteAggregateChange<T> change = createDeletingChange(id, null, domainType);
|
||||
triggerBeforeDelete(null, id, change);
|
||||
batchingAggregateChange.add(change);
|
||||
@@ -316,6 +317,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
Map<Object, T> instancesBeforeExecute = new LinkedHashMap<>();
|
||||
|
||||
instances.forEach(instance -> {
|
||||
|
||||
Object id = context.getRequiredPersistentEntity(domainType).getIdentifierAccessor(instance)
|
||||
.getRequiredIdentifier();
|
||||
DeleteAggregateChange<T> change = createDeletingChange(id, instance, domainType);
|
||||
@@ -384,6 +386,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
for (T instance : instances) {
|
||||
if (batchingAggregateChange == null) {
|
||||
// noinspection unchecked
|
||||
batchingAggregateChange = BatchingAggregateChange.forSave((Class<T>) ClassUtils.getUserClass(instance));
|
||||
}
|
||||
batchingAggregateChange.add(beforeExecute(instance, changeCreatorSelectorForSave(instance)));
|
||||
@@ -540,15 +543,6 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class EntityAndPreviousVersion<T> {
|
||||
|
||||
private final T entity;
|
||||
private final Number version;
|
||||
|
||||
EntityAndPreviousVersion(T entity, @Nullable Number version) {
|
||||
|
||||
this.entity = entity;
|
||||
this.version = version;
|
||||
}
|
||||
private record EntityAndPreviousVersion<T> (T entity, @Nullable Number version) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
|
||||
import static java.lang.Boolean.*;
|
||||
|
||||
/**
|
||||
* Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does
|
||||
* not throw an exception.
|
||||
@@ -155,7 +157,6 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
private <T> T collect(Function<DataAccessStrategy, T> function) {
|
||||
|
||||
// Keep <T> as Eclipse fails to compile if <> is used.
|
||||
return strategies.stream().collect(new FunctionCollector<>(function));
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
collect(das -> {
|
||||
consumer.accept(das);
|
||||
return null;
|
||||
return TRUE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,8 +183,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
RelationalPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(getBaseType(propertyPath));
|
||||
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
@@ -199,8 +198,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public void delete(Iterable<Object> rootIds, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentEntity<?> rootEntity = context
|
||||
.getRequiredPersistentEntity(propertyPath.getBaseProperty().getOwner().getType());
|
||||
RelationalPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(getBaseType(propertyPath));
|
||||
|
||||
RelationalPersistentProperty referencingProperty = propertyPath.getLeafProperty();
|
||||
|
||||
@@ -220,8 +218,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
operations.getJdbcOperations()
|
||||
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
|
||||
operations.getJdbcOperations().update(sql(getBaseType(propertyPath)).createDeleteAllSql(propertyPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -365,4 +362,13 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
return Optional.ofNullable(context.getRequiredPersistentEntity(domainType).getIdProperty())
|
||||
.map(RelationalPersistentProperty::getColumnName).orElse(null);
|
||||
}
|
||||
|
||||
private Class<?> getBaseType(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentProperty baseProperty = propertyPath.getBaseProperty();
|
||||
|
||||
Assert.notNull(baseProperty, "The base property must not be null");
|
||||
|
||||
return baseProperty.getOwner().getType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ class SqlGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a IN-condition based on a {@link Select Sub-Select} which selects the ids (or stand ins for ids) of the
|
||||
* Construct an IN-condition based on a {@link Select Sub-Select} which selects the ids (or stand-ins for ids) of the
|
||||
* given {@literal path} to those that reference the root entities specified by the {@literal rootCondition}.
|
||||
*
|
||||
* @param path specifies the table and id to select
|
||||
@@ -135,7 +135,7 @@ class SqlGenerator {
|
||||
innerCondition = rootCondition.apply(selectFilterColumn);
|
||||
} else {
|
||||
|
||||
// otherwise we need another layer of subselect
|
||||
// otherwise, we need another layer of subselect
|
||||
innerCondition = getSubselectCondition(parentPath, rootCondition, selectFilterColumn);
|
||||
}
|
||||
|
||||
@@ -502,7 +502,7 @@ class SqlGenerator {
|
||||
@Nullable
|
||||
Column getColumn(PersistentPropertyPathExtension path) {
|
||||
|
||||
// an embedded itself doesn't give an column, its members will though.
|
||||
// an embedded itself doesn't give a column, its members will though.
|
||||
// if there is a collection or map on the path it won't get selected at all, but it will get loaded with a separate
|
||||
// select
|
||||
// only the parent path is considered in order to handle arrays that get stored as BINARY properly
|
||||
@@ -512,7 +512,7 @@ class SqlGenerator {
|
||||
|
||||
if (path.isEntity()) {
|
||||
|
||||
// Simple entities without id include there backreference as an synthetic id in order to distinguish null entities
|
||||
// Simple entities without id include there backreference as a synthetic id in order to distinguish null entities
|
||||
// from entities with only null values.
|
||||
|
||||
if (path.isQualified() //
|
||||
@@ -622,7 +622,7 @@ class SqlGenerator {
|
||||
|
||||
Table table = getTable();
|
||||
|
||||
List<AssignValue> assignments = columns.getUpdateableColumns() //
|
||||
List<AssignValue> assignments = columns.getUpdatableColumns() //
|
||||
.stream() //
|
||||
.map(columnName -> Assignments.value( //
|
||||
table.column(columnName), //
|
||||
@@ -807,7 +807,7 @@ class SqlGenerator {
|
||||
private final List<SqlIdentifier> nonIdColumnNames = new ArrayList<>();
|
||||
private final Set<SqlIdentifier> readOnlyColumnNames = new HashSet<>();
|
||||
private final Set<SqlIdentifier> insertableColumns;
|
||||
private final Set<SqlIdentifier> updateableColumns;
|
||||
private final Set<SqlIdentifier> updatableColumns;
|
||||
|
||||
Columns(RelationalPersistentEntity<?> entity,
|
||||
MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext,
|
||||
@@ -823,12 +823,12 @@ class SqlGenerator {
|
||||
|
||||
this.insertableColumns = Collections.unmodifiableSet(insertable);
|
||||
|
||||
Set<SqlIdentifier> updateable = new LinkedHashSet<>(columnNames);
|
||||
Set<SqlIdentifier> updatable = new LinkedHashSet<>(columnNames);
|
||||
|
||||
updateable.removeAll(idColumnNames);
|
||||
updateable.removeAll(readOnlyColumnNames);
|
||||
updatable.removeAll(idColumnNames);
|
||||
updatable.removeAll(readOnlyColumnNames);
|
||||
|
||||
this.updateableColumns = Collections.unmodifiableSet(updateable);
|
||||
this.updatableColumns = Collections.unmodifiableSet(updatable);
|
||||
}
|
||||
|
||||
private void populateColumnNameCache(RelationalPersistentEntity<?> entity, String prefix) {
|
||||
@@ -881,8 +881,8 @@ class SqlGenerator {
|
||||
/**
|
||||
* @return Column names that can be used for {@code UPDATE}.
|
||||
*/
|
||||
Set<SqlIdentifier> getUpdateableColumns() {
|
||||
return updateableColumns;
|
||||
Set<SqlIdentifier> getUpdatableColumns() {
|
||||
return updatableColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
@@ -63,11 +60,9 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(MyBatisDataAccessStrategy.class);
|
||||
private static final String VERSION_SQL_PARAMETER_NAME_OLD = "___oldOptimisticLockingVersion";
|
||||
|
||||
private final SqlSession sqlSession;
|
||||
private final IdentifierProcessing identifierProcessing;
|
||||
private NamespaceStrategy namespaceStrategy = NamespaceStrategy.DEFAULT_INSTANCE;
|
||||
|
||||
/**
|
||||
@@ -133,7 +128,6 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
public MyBatisDataAccessStrategy(SqlSession sqlSession, IdentifierProcessing identifierProcessing) {
|
||||
|
||||
this.sqlSession = sqlSession;
|
||||
this.identifierProcessing = identifierProcessing;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +204,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public void delete(Object rootId, PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
Class<?> ownerType = propertyPath.getBaseProperty().getOwner().getType();
|
||||
Class<?> ownerType = getOwnerTyp(propertyPath);
|
||||
String statement = namespace(ownerType) + ".delete-" + toDashPath(propertyPath);
|
||||
Class<?> leafType = propertyPath.getRequiredLeafProperty().getTypeInformation().getType();
|
||||
MyBatisContext parameter = new MyBatisContext(rootId, null, leafType, Collections.emptyMap());
|
||||
@@ -234,10 +228,9 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
@Override
|
||||
public void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
Class<?> baseType = propertyPath.getBaseProperty().getOwner().getType();
|
||||
Class<?> leafType = propertyPath.getRequiredLeafProperty().getTypeInformation().getType();
|
||||
|
||||
String statement = namespace(baseType) + ".deleteAll-" + toDashPath(propertyPath);
|
||||
String statement = namespace(getOwnerTyp(propertyPath)) + ".deleteAll-" + toDashPath(propertyPath);
|
||||
MyBatisContext parameter = new MyBatisContext(null, null, leafType, Collections.emptyMap());
|
||||
sqlSession().delete(statement, parameter);
|
||||
}
|
||||
@@ -291,8 +284,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
public Iterable<Object> findAllByPath(Identifier identifier,
|
||||
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
|
||||
|
||||
String statementName = namespace(path.getBaseProperty().getOwner().getType()) + ".findAllByPath-"
|
||||
+ path.toDotPath();
|
||||
String statementName = namespace(getOwnerTyp(path)) + ".findAllByPath-" + path.toDotPath();
|
||||
|
||||
return sqlSession().selectList(statementName,
|
||||
new MyBatisContext(identifier, null, path.getRequiredLeafProperty().getType()));
|
||||
@@ -333,12 +325,6 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
return sqlSession().selectOne(statement, parameter);
|
||||
}
|
||||
|
||||
private Map<String, Object> convertToParameterMap(Map<SqlIdentifier, Object> additionalParameters) {
|
||||
|
||||
return additionalParameters.entrySet().stream() //
|
||||
.collect(Collectors.toMap(e -> e.getKey().toSql(identifierProcessing), Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private String namespace(Class<?> domainType) {
|
||||
return this.namespaceStrategy.getNamespace(domainType);
|
||||
}
|
||||
@@ -348,6 +334,20 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
}
|
||||
|
||||
private static String toDashPath(PersistentPropertyPath<RelationalPersistentProperty> propertyPath) {
|
||||
return propertyPath.toDotPath().replaceAll("\\.", "-");
|
||||
|
||||
String dotPath = propertyPath.toDotPath();
|
||||
if (dotPath == null) {
|
||||
return "";
|
||||
}
|
||||
return dotPath.replaceAll("\\.", "-");
|
||||
}
|
||||
|
||||
private Class<?> getOwnerTyp(PersistentPropertyPath<? extends RelationalPersistentProperty> propertyPath) {
|
||||
|
||||
RelationalPersistentProperty baseProperty = propertyPath.getBaseProperty();
|
||||
|
||||
Assert.notNull(baseProperty, "BaseProperty must not be null.");
|
||||
|
||||
return baseProperty.getOwner().getType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,11 +406,9 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
|
||||
|
||||
DbAction.Insert<?> createInsert(String propertyName, Object value, @Nullable Object key) {
|
||||
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value,
|
||||
return new DbAction.Insert<>(value,
|
||||
context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert,
|
||||
singletonMap(toPath(propertyName), key), IdValueSource.GENERATED);
|
||||
|
||||
return insert;
|
||||
}
|
||||
|
||||
DbAction.Insert<?> createDeepInsert(String propertyName, Object value, Object key,
|
||||
@@ -418,10 +416,9 @@ public class AggregateChangeIdGenerationImmutableUnitTests {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = toPath(
|
||||
parentInsert.getPropertyPath().toDotPath() + "." + propertyName);
|
||||
DbAction.Insert<Object> insert = new DbAction.Insert<>(value, propertyPath, parentInsert,
|
||||
singletonMap(propertyPath, key), IdValueSource.GENERATED);
|
||||
|
||||
return insert;
|
||||
return new DbAction.Insert<>(value, propertyPath, parentInsert,
|
||||
singletonMap(propertyPath, key), IdValueSource.GENERATED);
|
||||
}
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> toPath(String path) {
|
||||
|
||||
@@ -380,11 +380,11 @@ public class AggregateChangeIdGenerationUnitTests {
|
||||
@Id Integer id;
|
||||
}
|
||||
|
||||
private static class IncrementingIds implements Answer {
|
||||
private static class IncrementingIds implements Answer<Object> {
|
||||
long id = 1;
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
|
||||
if (!invocation.getMethod().getReturnType().equals(Object.class)) {
|
||||
throw new UnsupportedOperationException("This mock does not support this invocation: " + invocation);
|
||||
@@ -392,16 +392,5 @@ public class AggregateChangeIdGenerationUnitTests {
|
||||
|
||||
return id++;
|
||||
}
|
||||
|
||||
private DbAction<?> findAction(Object[] arguments) {
|
||||
|
||||
for (Object argument : arguments) {
|
||||
|
||||
if (argument instanceof DbAction) {
|
||||
return (DbAction<?>) argument;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ 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(23L)), eq(IdValueSource.GENERATED)))
|
||||
.thenReturn(24L);
|
||||
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);
|
||||
@@ -134,7 +134,6 @@ public class JdbcAggregateChangeExecutorContextImmutableUnitTests {
|
||||
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);
|
||||
|
||||
@@ -167,9 +167,10 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
|
||||
@Test // GH-537
|
||||
void batchInsertRootOperation_withGeneratedIds() {
|
||||
|
||||
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.GENERATED))
|
||||
.thenReturn(new Object[] { 123L });
|
||||
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED))));
|
||||
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class,
|
||||
IdValueSource.GENERATED)).thenReturn(new Object[] { 123L });
|
||||
executionContext.executeBatchInsertRoot(
|
||||
new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.GENERATED))));
|
||||
|
||||
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
|
||||
|
||||
@@ -180,9 +181,10 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
|
||||
@Test // GH-537
|
||||
void batchInsertRootOperation_withoutGeneratedIds() {
|
||||
|
||||
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class, IdValueSource.PROVIDED))
|
||||
.thenReturn(new Object[] { null });
|
||||
executionContext.executeBatchInsertRoot(new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.PROVIDED))));
|
||||
when(accessStrategy.insert(singletonList(InsertSubject.describedBy(root, Identifier.empty())), DummyEntity.class,
|
||||
IdValueSource.PROVIDED)).thenReturn(new Object[] { null });
|
||||
executionContext.executeBatchInsertRoot(
|
||||
new DbAction.BatchInsertRoot<>(singletonList(new DbAction.InsertRoot<>(root, IdValueSource.PROVIDED))));
|
||||
|
||||
List<DummyEntity> newRoots = executionContext.populateIdsIfNecessary();
|
||||
|
||||
@@ -242,7 +244,7 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
|
||||
}
|
||||
|
||||
DbAction.Insert<?> createInsert(DbAction.WithEntity<?> parent, String propertyName, Object value,
|
||||
@Nullable Object key, IdValueSource idValueSource) {
|
||||
@Nullable Object key, IdValueSource idValueSource) {
|
||||
|
||||
return new DbAction.Insert<>(value, getPersistentPropertyPath(propertyName), parent,
|
||||
key == null ? emptyMap() : singletonMap(toPath(propertyName), key), idValueSource);
|
||||
@@ -269,6 +271,7 @@ public class JdbcAggregateChangeExecutorContextUnitTests {
|
||||
.orElseThrow(() -> new IllegalArgumentException("No matching path found"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class DummyEntity {
|
||||
|
||||
@Id Long id;
|
||||
|
||||
@@ -49,7 +49,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.PersistenceCreator;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -205,14 +205,13 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
assertThat(reloadedLegoSet.manual).isNotNull();
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(reloadedLegoSet.manual.getId()) //
|
||||
.isEqualTo(legoSet.getManual().getId()) //
|
||||
.isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(legoSet.getManual().getContent());
|
||||
});
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual.getId()) //
|
||||
.isEqualTo(legoSet.getManual().getId()) //
|
||||
.isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(legoSet.getManual().getContent());
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@@ -307,12 +306,11 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
template.delete(legoSet, LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
softly.assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
softly.assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
softly.assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@@ -323,44 +321,46 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
template.deleteAll(LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
softly.assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
softly.assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
});
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
|
||||
void saveAndDeleteAllByAggregateRootsWithReferencedEntity() {
|
||||
|
||||
LegoSet legoSet1 = template.save(legoSet);
|
||||
LegoSet legoSet2 = template.save(createLegoSet("Some Name"));
|
||||
template.save(createLegoSet("Some other Name"));
|
||||
|
||||
template.deleteAll(List.of(legoSet1, legoSet2), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
softly.assertThat(template.findAll(LegoSet.class)).extracting(l -> l.name).containsExactly("Some other Name");
|
||||
softly.assertThat(template.findAll(Manual.class)).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
|
||||
void saveAndDeleteAllByIdsWithReferencedEntity() {
|
||||
|
||||
LegoSet legoSet1 = template.save(legoSet);
|
||||
LegoSet legoSet2 = template.save(createLegoSet("Some Name"));
|
||||
template.save(createLegoSet("Some other Name"));
|
||||
|
||||
template.deleteAllById(List.of(legoSet1.id, legoSet2.id), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
softly.assertThat(template.findAll(LegoSet.class)).extracting(l -> l.name).containsExactly("Some other Name");
|
||||
softly.assertThat(template.findAll(Manual.class)).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@@ -427,12 +427,11 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual.content).isEqualTo("other content");
|
||||
softly.assertThat(template.findAll(Manual.class)).describedAs("There should be only one manual").hasSize(1);
|
||||
|
||||
softly.assertAll();
|
||||
softly.assertThat(reloadedLegoSet.manual.content).isEqualTo("other content");
|
||||
softly.assertThat(template.findAll(Manual.class)).describedAs("There should be only one manual").hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@@ -524,14 +523,14 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions).isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.id).isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.id).isNotEqualTo(reloadedLegoSet.manual.id);
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.content)
|
||||
.isEqualTo(reloadedLegoSet.alternativeInstructions.content);
|
||||
assertSoftly(softly -> {
|
||||
|
||||
softly.assertAll();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions).isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.id).isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.id).isNotEqualTo(reloadedLegoSet.manual.id);
|
||||
softly.assertThat(reloadedLegoSet.alternativeInstructions.content)
|
||||
.isEqualTo(reloadedLegoSet.alternativeInstructions.content);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-276
|
||||
@@ -559,7 +558,7 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
ElementNoId element = new ElementNoId();
|
||||
element.content = "content";
|
||||
ListParentAllArgs entity = new ListParentAllArgs("name", asList(element));
|
||||
ListParentAllArgs entity = new ListParentAllArgs("name", singletonList(element));
|
||||
|
||||
entity = template.save(entity);
|
||||
|
||||
@@ -1103,6 +1102,7 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class OneToOneParent {
|
||||
|
||||
@Column("id3") @Id private Long id;
|
||||
@@ -1116,6 +1116,7 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
@Table("LIST_PARENT")
|
||||
@SuppressWarnings("unused")
|
||||
static class ListParent {
|
||||
|
||||
@Column("id4") @Id private Long id;
|
||||
@@ -1130,7 +1131,7 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
private final String name;
|
||||
@MappedCollection(idColumn = "LIST_PARENT") private final List<ElementNoId> content = new ArrayList<>();
|
||||
|
||||
@PersistenceConstructor
|
||||
@PersistenceCreator
|
||||
ListParentAllArgs(Long id, String name, List<ElementNoId> content) {
|
||||
|
||||
this.id = id;
|
||||
@@ -1150,23 +1151,27 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
/**
|
||||
* One may think of ChainN as a chain with N further elements
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
static class Chain0 {
|
||||
@Id Long zero;
|
||||
String zeroValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Chain1 {
|
||||
@Id Long one;
|
||||
String oneValue;
|
||||
Chain0 chain0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Chain2 {
|
||||
@Id Long two;
|
||||
String twoValue;
|
||||
Chain1 chain1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Chain3 {
|
||||
@Id Long three;
|
||||
String threeValue;
|
||||
@@ -1273,6 +1278,7 @@ class JdbcAggregateTemplateIntegrationTests {
|
||||
Map<String, NoIdMapChain3> chain3 = new HashMap<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class WithReadOnly {
|
||||
@Id Long id;
|
||||
String name;
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.springframework.data.relational.core.sql.Aliased;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link SqlGenerator}.
|
||||
@@ -65,16 +66,17 @@ import org.springframework.data.relational.core.sql.Table;
|
||||
* @author Mikhail Polivakha
|
||||
* @author Chirag Tailor
|
||||
*/
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
class SqlGeneratorUnitTests {
|
||||
|
||||
private static final Identifier BACKREF = Identifier.of(unquoted("backref"), "some-value", String.class);
|
||||
|
||||
private SqlGenerator sqlGenerator;
|
||||
private NamingStrategy namingStrategy = new PrefixingNamingStrategy();
|
||||
private RelationalMappingContext context = new JdbcMappingContext(namingStrategy);
|
||||
private JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
|
||||
private final NamingStrategy namingStrategy = new PrefixingNamingStrategy();
|
||||
private final RelationalMappingContext context = new JdbcMappingContext(namingStrategy);
|
||||
private final JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
private SqlGenerator sqlGenerator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -267,7 +269,8 @@ class SqlGeneratorUnitTests {
|
||||
|
||||
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, PostgresDialect.INSTANCE);
|
||||
|
||||
String sql = sqlGenerator.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
|
||||
String sql = sqlGenerator
|
||||
.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
|
||||
|
||||
assertThat(sql).contains("ORDER BY \"dummy_entity\".\"x_name\" ASC NULLS LAST");
|
||||
}
|
||||
@@ -277,7 +280,8 @@ class SqlGeneratorUnitTests {
|
||||
|
||||
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, SqlServerDialect.INSTANCE);
|
||||
|
||||
String sql = sqlGenerator.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
|
||||
String sql = sqlGenerator
|
||||
.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
|
||||
|
||||
assertThat(sql).endsWith("ORDER BY dummy_entity.x_name ASC");
|
||||
}
|
||||
@@ -695,6 +699,7 @@ class SqlGeneratorUnitTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SqlGenerator.Join generateJoin(String path, Class<?> type) {
|
||||
return createSqlGenerator(type, AnsiDialect.INSTANCE)
|
||||
.getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context)));
|
||||
@@ -733,6 +738,7 @@ class SqlGeneratorUnitTests {
|
||||
SqlIdentifier.quoted("child"), SqlIdentifier.quoted("CHILD_PARENT_OF_NO_ID_CHILD"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SqlIdentifier getAlias(Object maybeAliased) {
|
||||
|
||||
if (maybeAliased instanceof Aliased) {
|
||||
@@ -741,6 +747,7 @@ class SqlGeneratorUnitTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class<?> type) {
|
||||
|
||||
return createSqlGenerator(type, AnsiDialect.INSTANCE)
|
||||
@@ -762,6 +769,7 @@ class SqlGeneratorUnitTests {
|
||||
AggregateReference<OtherAggregate, Long> other;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class VersionedEntity extends DummyEntity {
|
||||
@Version Integer version;
|
||||
}
|
||||
@@ -781,6 +789,7 @@ class SqlGeneratorUnitTests {
|
||||
String something;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Element {
|
||||
@Id Long id;
|
||||
String content;
|
||||
@@ -795,6 +804,7 @@ class SqlGeneratorUnitTests {
|
||||
|
||||
private static class NoIdChild {}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class OtherAggregate {
|
||||
@Id Long id;
|
||||
String name;
|
||||
@@ -823,6 +833,7 @@ class SqlGeneratorUnitTests {
|
||||
@ReadOnlyProperty String readOnlyValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class EntityWithQuotedColumnName {
|
||||
|
||||
// these column names behave like single double quote in the name since the get quoted and then doubling the double
|
||||
@@ -865,36 +876,43 @@ class SqlGeneratorUnitTests {
|
||||
Chain3 chain3;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoIdChain0 {
|
||||
String zeroValue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoIdChain1 {
|
||||
String oneValue;
|
||||
NoIdChain0 chain0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoIdChain2 {
|
||||
String twoValue;
|
||||
NoIdChain1 chain1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoIdChain3 {
|
||||
String threeValue;
|
||||
NoIdChain2 chain2;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class NoIdChain4 {
|
||||
@Id Long four;
|
||||
String fourValue;
|
||||
NoIdChain3 chain3;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class IdNoIdChain {
|
||||
@Id Long id;
|
||||
NoIdChain4 chain4;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class IdIdNoIdChain {
|
||||
@Id Long id;
|
||||
IdNoIdChain idNoIdChain;
|
||||
|
||||
@@ -21,6 +21,10 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.SoftAssertions.*;
|
||||
import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.ResultSet;
|
||||
import java.time.Instant;
|
||||
@@ -48,8 +52,6 @@ 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;
|
||||
@@ -57,6 +59,7 @@ import org.springframework.data.jdbc.testing.AssumeFeatureTestExecutionListener;
|
||||
import org.springframework.data.jdbc.testing.EnabledOnFeature;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
|
||||
import org.springframework.data.relational.core.mapping.MappedCollection;
|
||||
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
@@ -75,10 +78,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.jdbc.JdbcTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* Very simple use cases for creation and usage of JdbcRepositories.
|
||||
*
|
||||
@@ -607,8 +606,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
repository.saveAll(asList(dummyA, dummyB, dummyC));
|
||||
|
||||
assertThat(repository.findByEnumTypeIn(Set.of(Direction.LEFT, Direction.RIGHT)))
|
||||
.extracting(DummyEntity::getDirection)
|
||||
.containsExactlyInAnyOrder(Direction.LEFT, Direction.RIGHT);
|
||||
.extracting(DummyEntity::getDirection).containsExactlyInAnyOrder(Direction.LEFT, Direction.RIGHT);
|
||||
}
|
||||
|
||||
@Test // GH-1212
|
||||
@@ -622,13 +620,13 @@ public class JdbcRepositoryIntegrationTests {
|
||||
dummyC.setDirection(Direction.RIGHT);
|
||||
repository.saveAll(asList(dummyA, dummyB, dummyC));
|
||||
|
||||
assertThat(repository.findByEnumType(Direction.CENTER))
|
||||
.extracting(DummyEntity::getDirection)
|
||||
assertThat(repository.findByEnumType(Direction.CENTER)).extracting(DummyEntity::getDirection)
|
||||
.containsExactlyInAnyOrder(Direction.CENTER);
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void manyInsertsWithNestedEntities() {
|
||||
|
||||
Root root1 = createRoot("root1");
|
||||
Root root2 = createRoot("root2");
|
||||
|
||||
@@ -644,6 +642,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
@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));
|
||||
@@ -669,6 +668,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
@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,
|
||||
@@ -685,6 +685,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -692,6 +693,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
}
|
||||
|
||||
private void assertIsEqualToWithNonNullIds(Root reloadedRoot1, Root root1) {
|
||||
|
||||
assertThat(reloadedRoot1.id).isNotNull();
|
||||
assertThat(reloadedRoot1.name).isEqualTo(root1.name);
|
||||
assertThat(reloadedRoot1.intermediate.id).isNotNull();
|
||||
@@ -840,6 +842,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
|
||||
@Value
|
||||
static class Root {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
Intermediate intermediate;
|
||||
@@ -848,6 +851,7 @@ public class JdbcRepositoryIntegrationTests {
|
||||
|
||||
@Value
|
||||
static class Intermediate {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
Leaf leaf;
|
||||
@@ -856,13 +860,14 @@ public class JdbcRepositoryIntegrationTests {
|
||||
|
||||
@Value
|
||||
static class Leaf {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
static class MyEventListener implements ApplicationListener<AbstractRelationalEvent<?>> {
|
||||
|
||||
private List<AbstractRelationalEvent<?>> events = new ArrayList<>();
|
||||
private final List<AbstractRelationalEvent<?>> events = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractRelationalEvent<?> event) {
|
||||
@@ -892,13 +897,11 @@ public class JdbcRepositoryIntegrationTests {
|
||||
}
|
||||
|
||||
interface DummyProjection {
|
||||
|
||||
String getName();
|
||||
}
|
||||
|
||||
@Value
|
||||
static class DtoProjection {
|
||||
|
||||
String name;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.groups.Tuple.tuple;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@@ -28,7 +29,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.groups.Tuple;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
@@ -45,8 +45,15 @@ import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.dialect.H2Dialect;
|
||||
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.event.*;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
import org.springframework.data.relational.core.mapping.event.RelationalEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.WithId;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
@@ -129,12 +136,12 @@ public class SimpleJdbcRepositoryEventsUnitTests {
|
||||
assertThat(publisher.events) //
|
||||
.extracting(RelationalEvent::getClass, e -> ((DummyEntity) e.getEntity()).getId()) //
|
||||
.containsExactly( //
|
||||
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) //
|
||||
tuple(BeforeConvertEvent.class, null), //
|
||||
tuple(BeforeSaveEvent.class, null), //
|
||||
tuple(BeforeConvertEvent.class, 23L), //
|
||||
tuple(BeforeSaveEvent.class, 23L), //
|
||||
tuple(AfterSaveEvent.class, generatedId), //
|
||||
tuple(AfterSaveEvent.class, 23L) //
|
||||
);
|
||||
}
|
||||
|
||||
@@ -150,8 +157,8 @@ public class SimpleJdbcRepositoryEventsUnitTests {
|
||||
this::getEntity, //
|
||||
this::getId //
|
||||
).containsExactly( //
|
||||
Tuple.tuple(BeforeDeleteEvent.class, entity, Identifier.of(23L)), //
|
||||
Tuple.tuple(AfterDeleteEvent.class, entity, Identifier.of(23L)) //
|
||||
tuple(BeforeDeleteEvent.class, entity, Identifier.of(23L)), //
|
||||
tuple(AfterDeleteEvent.class, entity, Identifier.of(23L)) //
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ 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,
|
||||
@@ -27,6 +28,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
|
||||
@@ -14,6 +14,7 @@ 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,
|
||||
@@ -22,6 +23,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
|
||||
@@ -14,6 +14,7 @@ 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,
|
||||
@@ -22,6 +23,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
|
||||
@@ -14,6 +14,7 @@ CREATE TABLE ROOT
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE INTERMEDIATE
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -22,6 +23,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
@@ -19,6 +19,7 @@ CREATE TABLE ROOT
|
||||
ID BIGINT IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE INTERMEDIATE
|
||||
(
|
||||
ID BIGINT IDENTITY PRIMARY KEY,
|
||||
@@ -27,6 +28,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT IDENTITY PRIMARY KEY,
|
||||
|
||||
@@ -17,6 +17,7 @@ CREATE TABLE ROOT
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE INTERMEDIATE
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -25,6 +26,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID BIGINT,
|
||||
ROOT_KEY INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
@@ -19,6 +19,7 @@ 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,
|
||||
@@ -27,6 +28,7 @@ CREATE TABLE INTERMEDIATE
|
||||
ROOT_ID NUMBER,
|
||||
ROOT_KEY NUMBER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
|
||||
|
||||
@@ -19,6 +19,7 @@ CREATE TABLE ROOT
|
||||
ID SERIAL PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE INTERMEDIATE
|
||||
(
|
||||
ID SERIAL PRIMARY KEY,
|
||||
@@ -27,6 +28,7 @@ CREATE TABLE INTERMEDIATE
|
||||
"ROOT_ID" BIGINT,
|
||||
"ROOT_KEY" INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE LEAF
|
||||
(
|
||||
ID SERIAL PRIMARY KEY,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
* Collects actions of a certain type and allows to consume them in a batched fashion, i.e. "similar" actions get
|
||||
* combined into a batched action variant.
|
||||
*
|
||||
* @param <S> type of the s</b>ingular action.
|
||||
* @param <B> type of the <b>b</b>atched action.
|
||||
* @param <C> type of the <b>c</b>ontainer used for gathering singular actions.
|
||||
* @author Jens Schauder
|
||||
* @since 3.0
|
||||
*/
|
||||
class BatchedActions<S extends DbAction.WithPropertyPath, B extends DbAction.BatchWithValue, C> {
|
||||
|
||||
private static final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> PATH_LENGTH_COMPARATOR = //
|
||||
Comparator.comparing(PersistentPropertyPath::getLength);
|
||||
private static final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> REVERSE_PATH_LENGTH_COMPARATOR = //
|
||||
PATH_LENGTH_COMPARATOR.reversed();
|
||||
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, C> actionMap = new HashMap<>();
|
||||
|
||||
private final Combiner<S, C, B> combiner;
|
||||
private final Comparator<PersistentPropertyPath<RelationalPersistentProperty>> sorting;
|
||||
|
||||
static BatchedActions<DbAction.Delete, DbAction.BatchDelete, List<DbAction.Delete>> batchedDeletes() {
|
||||
return new BatchedActions<>(DeleteCombiner.INSTANCE, REVERSE_PATH_LENGTH_COMPARATOR);
|
||||
}
|
||||
|
||||
static BatchedActions<DbAction.Insert, DbAction.BatchInsert, Map<IdValueSource, List<DbAction.Insert>>> batchedInserts() {
|
||||
return new BatchedActions<>(InsertCombiner.INSTANCE, PATH_LENGTH_COMPARATOR);
|
||||
}
|
||||
|
||||
private BatchedActions(Combiner<S, C, B> combiner,
|
||||
Comparator<PersistentPropertyPath<RelationalPersistentProperty>> sorting) {
|
||||
|
||||
this.combiner = combiner;
|
||||
this.sorting = sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an action that might get combined with other actions into a batch.
|
||||
*
|
||||
* @param action the action to combine with other actions.
|
||||
*/
|
||||
void add(S action) {
|
||||
combiner.merge(actionMap, action.getPropertyPath(), action);
|
||||
}
|
||||
|
||||
void forEach(Consumer<? super DbAction> consumer) {
|
||||
|
||||
combiner.forEach( //
|
||||
actionMap.entrySet().stream() //
|
||||
.sorted(Map.Entry.comparingByKey(sorting)), //
|
||||
consumer);
|
||||
}
|
||||
|
||||
interface Combiner<S, C, M> {
|
||||
|
||||
/**
|
||||
* Merges an additional entry into the map of actions, which groups the actions by property path.
|
||||
*
|
||||
* @param actionMap the map of actions into which the new action is to be merged.
|
||||
* @param propertyPath the property map under which to add the action.
|
||||
* @param action the action to be merged.
|
||||
*/
|
||||
void merge(Map<PersistentPropertyPath<RelationalPersistentProperty>, C> actionMap,
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath, S action);
|
||||
|
||||
/**
|
||||
* Invokes the consumer for the actions in the sorted stream. Before passed to the consumer compatible actions will
|
||||
* get combined into batched actions.
|
||||
*
|
||||
* @param sorted
|
||||
* @param consumer
|
||||
*/
|
||||
void forEach(Stream<Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, C>> sorted,
|
||||
Consumer<? super DbAction> consumer);
|
||||
}
|
||||
|
||||
enum DeleteCombiner implements Combiner<DbAction.Delete, List<DbAction.Delete>, DbAction.BatchDelete> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void merge(Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete>> actionMap,
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath, DbAction.Delete action) {
|
||||
|
||||
actionMap.merge( //
|
||||
propertyPath, //
|
||||
new ArrayList<>(singletonList(action)), //
|
||||
(actions, defaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(
|
||||
Stream<Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete>>> sorted,
|
||||
Consumer<? super DbAction> consumer) {
|
||||
|
||||
sorted.forEach((entry) -> {
|
||||
|
||||
List<DbAction.Delete> actions = entry.getValue();
|
||||
if (actions.size() > 1) {
|
||||
singletonList(new DbAction.BatchDelete(actions)).forEach(consumer);
|
||||
} else {
|
||||
actions.forEach(consumer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum InsertCombiner
|
||||
implements Combiner<DbAction.Insert, Map<IdValueSource, List<DbAction.Insert>>, DbAction.BatchInsert> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void merge(
|
||||
Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert>>> actionMap,
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath, DbAction.Insert action) {
|
||||
|
||||
actionMap.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;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(
|
||||
Stream<Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert>>>> sorted,
|
||||
Consumer<? super DbAction> consumer) {
|
||||
|
||||
sorted.forEach((entry) -> entry.getValue() //
|
||||
.forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert(inserts))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -64,7 +62,7 @@ public interface DbAction<T> {
|
||||
this.entity = entity;
|
||||
this.propertyPath = propertyPath;
|
||||
this.dependingOn = dependingOn;
|
||||
this.qualifiers = Collections.unmodifiableMap(new HashMap<>(qualifiers));
|
||||
this.qualifiers = Map.copyOf(qualifiers);
|
||||
this.idValueSource = idValueSource;
|
||||
}
|
||||
|
||||
@@ -358,9 +356,9 @@ public interface DbAction<T> {
|
||||
private final B batchValue;
|
||||
|
||||
/**
|
||||
* Creates a {@link BatchWithValue} instance from the given actions and the value which can be extracted by applying #batchValueExtractor on any of the actions.
|
||||
*
|
||||
* All actions must result in the same value when #batchValueExtractor is applied.
|
||||
* Creates a {@link BatchWithValue} instance from the given actions and the value which can be extracted by applying
|
||||
* #batchValueExtractor on any of the actions. All actions must result in the same value when #batchValueExtractor
|
||||
* is applied.
|
||||
*
|
||||
* @param actions the actions forming the batch.
|
||||
* @param batchValueExtractor function for extracting the {@link #batchValue} from an action.
|
||||
@@ -371,10 +369,8 @@ public interface DbAction<T> {
|
||||
|
||||
Iterator<A> actionIterator = actions.iterator();
|
||||
this.batchValue = batchValueExtractor.apply(actionIterator.next());
|
||||
actionIterator.forEachRemaining(action -> {
|
||||
Assert.isTrue(batchValueExtractor.apply(action).equals(batchValue),
|
||||
"All actions in the batch must have matching batchValue");
|
||||
});
|
||||
actionIterator.forEachRemaining(action -> Assert.isTrue(batchValueExtractor.apply(action).equals(batchValue),
|
||||
"All actions in the batch must have matching batchValue"));
|
||||
|
||||
this.actions = actions;
|
||||
}
|
||||
@@ -423,12 +419,14 @@ public interface DbAction<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a batch delete statement for multiple entities that are reachable via a given path from the aggregate root.
|
||||
* Represents a batch delete statement for multiple entities that are reachable via a given path from the aggregate
|
||||
* root.
|
||||
*
|
||||
* @param <T> type of the entity for which this represents a database interaction.
|
||||
* @since 3.0
|
||||
*/
|
||||
final class BatchDelete<T> extends BatchWithValue<T, Delete<T>, PersistentPropertyPath<RelationalPersistentProperty>> {
|
||||
final class BatchDelete<T>
|
||||
extends BatchWithValue<T, Delete<T>, PersistentPropertyPath<RelationalPersistentProperty>> {
|
||||
public BatchDelete(List<Delete<T>> actions) {
|
||||
super(actions, Delete::getPropertyPath);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class DefaultRootAggregateChange<T> implements RootAggregateChange<T> {
|
||||
/** The previous version assigned to the instance being changed, if available */
|
||||
@Nullable private final Number previousVersion;
|
||||
|
||||
public DefaultRootAggregateChange(Kind kind, Class<T> entityType, @Nullable Number previousVersion) {
|
||||
DefaultRootAggregateChange(Kind kind, Class<T> entityType, @Nullable Number previousVersion) {
|
||||
|
||||
this.kind = kind;
|
||||
this.entityType = entityType;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class DeleteAggregateChange<T> implements MutableAggregateChange<T> {
|
||||
/** The previous version assigned to the instance being changed, if available */
|
||||
@Nullable private final Number previousVersion;
|
||||
|
||||
public DeleteAggregateChange(Class<T> entityType, @Nullable Number previousVersion) {
|
||||
DeleteAggregateChange(Class<T> entityType, @Nullable Number previousVersion) {
|
||||
this.entityType = entityType;
|
||||
this.previousVersion = previousVersion;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package org.springframework.data.relational.core.conversion;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
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 static java.util.Collections.*;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
* A {@link BatchingAggregateChange} implementation for delete changes that can contain actions for one or more delete
|
||||
@@ -29,10 +25,9 @@ public class DeleteBatchingAggregateChange<T> implements BatchingAggregateChange
|
||||
private final Class<T> entityType;
|
||||
private final List<DbAction.DeleteRoot<T>> rootActions = new ArrayList<>();
|
||||
private final List<DbAction.AcquireLockRoot<?>> lockActions = new ArrayList<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<Object>>> deleteActions = //
|
||||
new HashMap<>();
|
||||
private final BatchedActions deleteActions = BatchedActions.batchedDeletes();
|
||||
|
||||
public DeleteBatchingAggregateChange(Class<T> entityType) {
|
||||
DeleteBatchingAggregateChange(Class<T> entityType) {
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
@@ -50,15 +45,7 @@ public class DeleteBatchingAggregateChange<T> implements BatchingAggregateChange
|
||||
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
|
||||
|
||||
lockActions.forEach(consumer);
|
||||
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
|
||||
.forEach((entry) -> {
|
||||
List<DbAction.Delete<Object>> deletes = entry.getValue();
|
||||
if (deletes.size() > 1) {
|
||||
consumer.accept(new DbAction.BatchDelete<>(deletes));
|
||||
} else {
|
||||
deletes.forEach(consumer);
|
||||
}
|
||||
});
|
||||
deleteActions.forEach(consumer);
|
||||
rootActions.forEach(consumer);
|
||||
}
|
||||
|
||||
@@ -67,23 +54,12 @@ public class DeleteBatchingAggregateChange<T> implements BatchingAggregateChange
|
||||
|
||||
aggregateChange.forEachAction(action -> {
|
||||
if (action instanceof DbAction.DeleteRoot<?> deleteRootAction) {
|
||||
//noinspection unchecked
|
||||
rootActions.add((DbAction.DeleteRoot<T>) deleteRootAction);
|
||||
} else if (action instanceof DbAction.Delete<?> deleteAction) {
|
||||
// noinspection unchecked
|
||||
addDelete((DbAction.Delete<Object>) deleteAction);
|
||||
deleteActions.add(deleteAction);
|
||||
} else if (action instanceof DbAction.AcquireLockRoot<?> lockRootAction) {
|
||||
lockActions.add(lockRootAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addDelete(DbAction.Delete<Object> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
deleteActions.merge(propertyPath, new ArrayList<>(singletonList(action)), (actions, defaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,9 @@
|
||||
*/
|
||||
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;
|
||||
@@ -49,9 +45,8 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
* into a single batch.
|
||||
*/
|
||||
private final List<DbAction.InsertRoot<T>> insertRootBatchCandidates = new ArrayList<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, Map<IdValueSource, List<DbAction.Insert<Object>>>> insertActions = //
|
||||
new HashMap<>();
|
||||
private final Map<PersistentPropertyPath<RelationalPersistentProperty>, List<DbAction.Delete<Object>>> deleteActions = new HashMap<>();
|
||||
private final BatchedActions insertActions = BatchedActions.batchedInserts();
|
||||
private final BatchedActions deleteActions = BatchedActions.batchedDeletes();
|
||||
|
||||
SaveBatchingAggregateChange(Class<T> entityType) {
|
||||
this.entityType = entityType;
|
||||
@@ -70,7 +65,7 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
@Override
|
||||
public void forEachAction(Consumer<? super DbAction<?>> consumer) {
|
||||
|
||||
Assert.notNull(consumer, "Consumer must not be null.");
|
||||
Assert.notNull(consumer, "Consumer must not be null");
|
||||
|
||||
rootActions.forEach(consumer);
|
||||
if (insertRootBatchCandidates.size() > 1) {
|
||||
@@ -78,17 +73,8 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
} else {
|
||||
insertRootBatchCandidates.forEach(consumer);
|
||||
}
|
||||
deleteActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator.reversed()))
|
||||
.forEach((entry) -> {
|
||||
List<DbAction.Delete<Object>> deletes = entry.getValue();
|
||||
if (deletes.size() > 1) {
|
||||
consumer.accept(new DbAction.BatchDelete<>(deletes));
|
||||
} else {
|
||||
deletes.forEach(consumer);
|
||||
}
|
||||
});
|
||||
insertActions.entrySet().stream().sorted(Map.Entry.comparingByKey(pathLengthComparator)).forEach((entry) -> entry
|
||||
.getValue().forEach((idValueSource, inserts) -> consumer.accept(new DbAction.BatchInsert<>(inserts))));
|
||||
deleteActions.forEach(consumer);
|
||||
insertActions.forEach(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,12 +95,9 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
// noinspection unchecked
|
||||
insertRootBatchCandidates.add((DbAction.InsertRoot<T>) rootAction);
|
||||
} else if (action instanceof DbAction.Insert<?> insertAction) {
|
||||
|
||||
// noinspection unchecked
|
||||
addInsert((DbAction.Insert<Object>) insertAction);
|
||||
insertActions.add(insertAction);
|
||||
} else if (action instanceof DbAction.Delete<?> deleteAction) {
|
||||
// noinspection unchecked
|
||||
addDelete((DbAction.Delete<Object>) deleteAction);
|
||||
deleteActions.add(deleteAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -133,26 +116,4 @@ public class SaveBatchingAggregateChange<T> implements BatchingAggregateChange<T
|
||||
insertRootBatchCandidates.clear();
|
||||
}
|
||||
|
||||
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<Object> action) {
|
||||
|
||||
PersistentPropertyPath<RelationalPersistentProperty> propertyPath = action.getPropertyPath();
|
||||
deleteActions.merge(propertyPath, new ArrayList<>(singletonList(action)), (actions, defaultValue) -> {
|
||||
actions.add(action);
|
||||
return actions;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.DbAction.BatchDelete;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
public class BatchedActionsUnitTests {
|
||||
|
||||
BatchedActions deletes = BatchedActions.batchedDeletes();
|
||||
|
||||
LoggingConsumer consumer = new LoggingConsumer();
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
|
||||
DbAction.Delete<Object> firstOneDelete = new DbAction.Delete(23L, path("one"));
|
||||
DbAction.Delete<Object> secondOneDelete = new DbAction.Delete(24L, path("one"));
|
||||
DbAction.Delete<Object> firstTwoDelete = new DbAction.Delete(25L, path("two"));
|
||||
DbAction.Delete<Object> secondTwoDelete = new DbAction.Delete(26L, path("two"));
|
||||
|
||||
@Test // GH-537
|
||||
void emptyBatchedDeletesDoesNotInvokeConsumer() {
|
||||
|
||||
deletes.forEach(consumer);
|
||||
|
||||
assertThat(consumer.log).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void singleActionGetsPassedToConsumer() {
|
||||
|
||||
deletes.add(firstOneDelete);
|
||||
|
||||
deletes.forEach(consumer);
|
||||
|
||||
assertThat(consumer.log).containsExactly(firstOneDelete);
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void multipleUnbatchableActionsGetsPassedToConsumerIndividually() {
|
||||
|
||||
deletes.add(firstOneDelete);
|
||||
deletes.add(firstTwoDelete);
|
||||
|
||||
deletes.forEach(consumer);
|
||||
|
||||
assertThat(consumer.log).containsExactlyInAnyOrder(firstOneDelete, firstTwoDelete);
|
||||
}
|
||||
|
||||
@Test // GH-537
|
||||
void batchableActionsGetPassedToConsumerAsOne() {
|
||||
|
||||
deletes.add(firstOneDelete);
|
||||
deletes.add(secondOneDelete);
|
||||
|
||||
deletes.forEach(consumer);
|
||||
|
||||
assertThat(consumer.log).extracting(a -> ((Class)a.getClass())).containsExactly(BatchDelete.class);
|
||||
}
|
||||
|
||||
private PersistentPropertyPath<RelationalPersistentProperty> path(String path) {
|
||||
return context.getPersistentPropertyPath(path, DummyEntity.class);
|
||||
}
|
||||
|
||||
private static class LoggingConsumer implements Consumer<DbAction<?>> {
|
||||
List<DbAction<?>> log = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void accept(DbAction dbAction) {
|
||||
log.add(dbAction);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyEntity {
|
||||
OtherEntity one;
|
||||
OtherEntity two;
|
||||
}
|
||||
|
||||
private static class OtherEntity {
|
||||
String one;
|
||||
String two;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import org.springframework.lang.Nullable;
|
||||
@UtilityClass
|
||||
class DbActionTestSupport {
|
||||
|
||||
static String extractPath(DbAction action) {
|
||||
static String extractPath(DbAction<?> action) {
|
||||
|
||||
if (action instanceof DbAction.WithPropertyPath) {
|
||||
return ((DbAction.WithPropertyPath<?>) action).getPropertyPath().toDotPath();
|
||||
@@ -37,14 +37,15 @@ class DbActionTestSupport {
|
||||
return "";
|
||||
}
|
||||
|
||||
static boolean isWithDependsOn(DbAction dbAction) {
|
||||
static boolean isWithDependsOn(DbAction<?> dbAction) {
|
||||
return dbAction instanceof DbAction.WithDependingOn;
|
||||
}
|
||||
|
||||
static Class<?> actualEntityType(DbAction a) {
|
||||
@Nullable
|
||||
static Class<?> actualEntityType(DbAction<?> a) {
|
||||
|
||||
if (a instanceof DbAction.WithEntity) {
|
||||
return ((DbAction.WithEntity) a).getEntity().getClass();
|
||||
return ((DbAction.WithEntity<?>) a).getEntity().getClass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class DeleteBatchingAggregateChangeTest {
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteActions() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
@@ -37,7 +37,7 @@ class DeleteBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(intermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsNestedDeleteActionsInTreeOrderFromLeavesToRoot() {
|
||||
|
||||
Root root = new Root(2L, null);
|
||||
@@ -45,6 +45,7 @@ class DeleteBatchingAggregateChangeTest {
|
||||
DbAction.Delete<Intermediate> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
|
||||
DbAction.Delete<?> leafDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate.leaf", Root.class));
|
||||
aggregateChange.addAction(leafDelete);
|
||||
@@ -56,16 +57,18 @@ class DeleteBatchingAggregateChangeTest {
|
||||
assertThat(actions).containsExactly(leafDelete, intermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteActionsAsBatchDeletes_groupedByPath_whenGroupContainsMultipleDeletes() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(root);
|
||||
|
||||
DbAction.Delete<Intermediate> intermediateDelete1 = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete1);
|
||||
|
||||
DbAction.Delete<Intermediate> intermediateDelete2 = new DbAction.Delete<>(2L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete1);
|
||||
aggregateChange.addAction(intermediateDelete2);
|
||||
|
||||
BatchingAggregateChange<Root, DeleteAggregateChange<Root>> change = BatchingAggregateChange.forDelete(Root.class);
|
||||
@@ -78,7 +81,7 @@ class DeleteBatchingAggregateChangeTest {
|
||||
.containsExactly(intermediateDelete1, intermediateDelete2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteRootActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
@@ -91,12 +94,14 @@ class DeleteBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(deleteRoot);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteRootActionsAfterDeleteActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
|
||||
DbAction.DeleteRoot<Root> deleteRoot = new DbAction.DeleteRoot<>(1L, Root.class, null);
|
||||
aggregateChange.addAction(deleteRoot);
|
||||
|
||||
DbAction.Delete<?> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
@@ -107,10 +112,11 @@ class DeleteBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(intermediateDelete, deleteRoot);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsLockRootActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
|
||||
DbAction.AcquireLockRoot<Root> lockRootAction = new DbAction.AcquireLockRoot<>(1L, Root.class);
|
||||
aggregateChange.addAction(lockRootAction);
|
||||
|
||||
@@ -120,13 +126,15 @@ class DeleteBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(lockRootAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsLockRootActionsBeforeDeleteActions() {
|
||||
|
||||
DeleteAggregateChange<Root> aggregateChange = MutableAggregateChange.forDelete(new Root(null, null));
|
||||
|
||||
DbAction.Delete<?> intermediateDelete = new DbAction.Delete<>(1L,
|
||||
context.getPersistentPropertyPath("intermediate", Root.class));
|
||||
aggregateChange.addAction(intermediateDelete);
|
||||
|
||||
DbAction.AcquireLockRoot<Root> lockRootAction = new DbAction.AcquireLockRoot<>(1L, Root.class);
|
||||
aggregateChange.addAction(lockRootAction);
|
||||
|
||||
@@ -150,14 +158,6 @@ class DeleteBatchingAggregateChangeTest {
|
||||
.orElseThrow(() -> new RuntimeException("No BatchWithValue action found!"));
|
||||
}
|
||||
|
||||
private <T, A> DbAction.BatchWithValue<T, DbAction<T>, Object> getBatchWithValueAction(List<DbAction<?>> actions,
|
||||
Class<T> entityType, Class<A> batchActionType, Object batchValue) {
|
||||
|
||||
return getBatchWithValueActions(actions, entityType, batchActionType).stream()
|
||||
.filter(batchWithValue -> batchWithValue.getBatchValue() == batchValue).findFirst().orElseThrow(
|
||||
() -> new RuntimeException(String.format("No BatchWithValue with batch value '%s' found!", batchValue)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T, A> List<DbAction.BatchWithValue<T, DbAction<T>, Object>> getBatchWithValueActions(
|
||||
List<DbAction<?>> actions, Class<T> entityType, Class<A> batchActionType) {
|
||||
@@ -170,12 +170,14 @@ class DeleteBatchingAggregateChangeTest {
|
||||
|
||||
@Value
|
||||
static class Root {
|
||||
|
||||
@Id Long id;
|
||||
Intermediate intermediate;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Intermediate {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
Leaf leaf;
|
||||
@@ -183,6 +185,7 @@ class DeleteBatchingAggregateChangeTest {
|
||||
|
||||
@Value
|
||||
static class Leaf {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-1159
|
||||
void newEntityWithPrimitiveLongId_insertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
PrimitiveLongIdEntity entity = new PrimitiveLongIdEntity();
|
||||
|
||||
@@ -119,7 +119,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-1159
|
||||
void newEntityWithPrimitiveIntId_insertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
PrimitiveIntIdEntity entity = new PrimitiveIntIdEntity();
|
||||
|
||||
@@ -187,7 +187,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-1159
|
||||
void newEntityWithReference_whenReferenceHasPrimitiveId_insertDoesNotIncludeId_whenIdValueIsZero() {
|
||||
|
||||
EntityWithReferencesToPrimitiveIdEntity entity = new EntityWithReferencesToPrimitiveIdEntity(null);
|
||||
@@ -665,7 +665,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-1159
|
||||
void newEntityWithCollection_whenElementHasPrimitiveId_doesNotIncludeId_whenIdValueIsZero() {
|
||||
|
||||
EntityWithReferencesToPrimitiveIdEntity entity = new EntityWithReferencesToPrimitiveIdEntity(null);
|
||||
|
||||
@@ -39,7 +39,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
|
||||
RelationalMappingContext context = new RelationalMappingContext();
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void startsWithNoActions() {
|
||||
|
||||
BatchingAggregateChange<Root, RootAggregateChange<Root>> change = BatchingAggregateChange.forSave(Root.class);
|
||||
@@ -49,7 +49,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
|
||||
@Nested
|
||||
class RootActionsTests {
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsUpdateRoot() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
@@ -63,7 +63,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(rootUpdate);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsSingleInsertRoot_followedByUpdateRoot_asIndividualActions() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -87,7 +87,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
Tuple.tuple(DbAction.UpdateRoot.class, Root.class, IdValueSource.PROVIDED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsMultipleMatchingInsertRoot_followedByUpdateRoot_asBatchInsertRootAction() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -120,7 +120,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(root1Insert, root2Insert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsInsertRoot() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
@@ -134,7 +134,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(rootInsert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsSingleInsertRoot_followedByNonMatchingInsertRoot_asIndividualActions() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -154,7 +154,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
assertThat(extractActions(change)).containsExactly(root1Insert, root2Insert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsMultipleMatchingInsertRoot_followedByNonMatchingInsertRoot_asBatchInsertRootAction() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -187,7 +187,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(root1Insert, root2Insert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsMultipleMatchingInsertRoot_asBatchInsertRootAction() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -212,7 +212,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(root1Insert, root2Insert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsPreviouslyYieldedInsertRoot_asBatchInsertRootAction_whenAdditionalMatchingInsertRootIsAdded() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -244,7 +244,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsRootActionsBeforeDeleteActions() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
@@ -271,7 +271,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
Tuple.tuple(DbAction.Delete.class, Intermediate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsNestedDeleteActionsInTreeOrderFromLeavesToRoot() {
|
||||
|
||||
Root root1 = new Root(1L, null);
|
||||
@@ -305,7 +305,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(root1IntermediateDelete, root2IntermediateDelete);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteActionsAsBatchDeletes_groupedByPath_whenGroupContainsMultipleDeletes() {
|
||||
|
||||
Root root = new Root(1L, null);
|
||||
@@ -331,7 +331,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(intermediateDelete1, intermediateDelete2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsDeleteActionsBeforeInsertActions() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
@@ -361,7 +361,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
Tuple.tuple(DbAction.BatchInsert.class, Intermediate.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsInsertActionsAsBatchInserts_groupedByIdValueSource() {
|
||||
|
||||
Root root = new Root(null, null);
|
||||
@@ -398,7 +398,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.getActions()).containsExactly(intermediateInsertProvidedId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsNestedInsertActionsInTreeOrderFromRootToLeaves() {
|
||||
|
||||
Root root1 = new Root(null, null);
|
||||
@@ -445,7 +445,7 @@ class SaveBatchingAggregateChangeTest {
|
||||
.containsExactly(root1LeafInsert);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-537
|
||||
void yieldsInsertsWithSameLengthReferences_asSeparateInserts() {
|
||||
|
||||
RootWithSameLengthReferences root = new RootWithSameLengthReferences(null, null, null);
|
||||
|
||||
Reference in New Issue
Block a user