Applies proper event handling before saving in batch.
Signed-off-by: mipo256 <mikhailpolivakha@gmail.com> Commit message edited by Jens Schauder. Original pull request #2065 Closes #2064
This commit is contained in:
@@ -27,6 +27,7 @@ import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -49,11 +50,22 @@ import org.springframework.data.relational.core.conversion.RootAggregateChange;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.mapping.event.*;
|
||||
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterDeleteCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.Identifier;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -173,19 +185,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
@Override
|
||||
public <T> List<T> saveAll(Iterable<T> instances) {
|
||||
|
||||
Assert.notNull(instances, "Aggregate instances must not be null");
|
||||
|
||||
if (!instances.iterator().hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
|
||||
for (T instance : instances) {
|
||||
verifyIdProperty(instance);
|
||||
entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changeCreatorSelectorForSave(instance)));
|
||||
}
|
||||
return performSaveAll(entityAndChangeCreators);
|
||||
return saveInBatch(instances, instance -> changeCreatorSelectorForSave(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,21 +206,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
@Override
|
||||
public <T> List<T> insertAll(Iterable<T> instances) {
|
||||
|
||||
Assert.notNull(instances, "Aggregate instances must not be null");
|
||||
|
||||
if (!instances.iterator().hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
|
||||
for (T instance : instances) {
|
||||
|
||||
Function<T, RootAggregateChange<T>> changeCreator = entity -> createInsertChange(prepareVersionForInsert(entity));
|
||||
EntityAndChangeCreator<T> entityChange = new EntityAndChangeCreator<>(instance, changeCreator);
|
||||
entityAndChangeCreators.add(entityChange);
|
||||
}
|
||||
return performSaveAll(entityAndChangeCreators);
|
||||
return doInBatch(instances, entity -> createInsertChange(prepareVersionForInsert(entity)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,6 +227,28 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
@Override
|
||||
public <T> List<T> updateAll(Iterable<T> instances) {
|
||||
return doInBatch(instances, entity -> createUpdateChange(prepareVersionForUpdate(entity)));
|
||||
}
|
||||
|
||||
private <T> List<T> saveInBatch(Iterable<T> instances, Function<T, AggregateChangeCreator<T>> changes) {
|
||||
|
||||
Assert.notNull(instances, "Aggregate instances must not be null");
|
||||
|
||||
if (!instances.iterator().hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
|
||||
|
||||
for (T instance : instances) {
|
||||
verifyIdProperty(instance);
|
||||
entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changes.apply(instance)));
|
||||
}
|
||||
|
||||
return performSaveAll(entityAndChangeCreators);
|
||||
}
|
||||
|
||||
private <T> List<T> doInBatch(Iterable<T> instances, AggregateChangeCreator<T> changeCreatorFunction) {
|
||||
|
||||
Assert.notNull(instances, "Aggregate instances must not be null");
|
||||
|
||||
@@ -250,10 +258,8 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
|
||||
for (T instance : instances) {
|
||||
|
||||
Function<T, RootAggregateChange<T>> changeCreator = entity -> createUpdateChange(prepareVersionForUpdate(entity));
|
||||
EntityAndChangeCreator<T> entityChange = new EntityAndChangeCreator<>(instance, changeCreator);
|
||||
entityAndChangeCreators.add(entityChange);
|
||||
verifyIdProperty(instance);
|
||||
entityAndChangeCreators.add(new EntityAndChangeCreator<T>(instance, changeCreatorFunction));
|
||||
}
|
||||
return performSaveAll(entityAndChangeCreators);
|
||||
}
|
||||
@@ -473,7 +479,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
T aggregateRoot = triggerBeforeConvert(instance.entity);
|
||||
|
||||
RootAggregateChange<T> change = instance.changeCreator.apply(aggregateRoot);
|
||||
RootAggregateChange<T> change = instance.changeCreator.createAggregateChange(aggregateRoot);
|
||||
|
||||
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
|
||||
|
||||
@@ -531,7 +537,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return results;
|
||||
}
|
||||
|
||||
private <T> Function<T, RootAggregateChange<T>> changeCreatorSelectorForSave(T instance) {
|
||||
private <T> AggregateChangeCreator<T> changeCreatorSelectorForSave(T instance) {
|
||||
|
||||
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
|
||||
? entity -> createInsertChange(prepareVersionForInsert(entity))
|
||||
@@ -671,6 +677,13 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
private record EntityAndPreviousVersion<T> (T entity, @Nullable Number version) {
|
||||
}
|
||||
|
||||
private record EntityAndChangeCreator<T> (T entity, Function<T, RootAggregateChange<T>> changeCreator) {
|
||||
private record EntityAndChangeCreator<T> (T entity, AggregateChangeCreator<T> changeCreator) {
|
||||
}
|
||||
|
||||
private interface AggregateChangeCreator<T> extends Function<T, RootAggregateChange<T>> {
|
||||
|
||||
default RootAggregateChange<T> createAggregateChange(T instance) {
|
||||
return this.apply(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -52,6 +54,7 @@ import org.springframework.data.jdbc.testing.IntegrationTest;
|
||||
import org.springframework.data.jdbc.testing.TestClass;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
@@ -60,6 +63,7 @@ import org.springframework.data.relational.core.mapping.InsertOnlyProperty;
|
||||
import org.springframework.data.relational.core.mapping.MappedCollection;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
@@ -1328,6 +1332,22 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
assertThat(enumMapOwners).containsExactly(enumMapOwner);
|
||||
}
|
||||
|
||||
@Test //GH-2064
|
||||
void saveAllBeforeConvertCallback() {
|
||||
var first = new BeforeConvertCallbackForSaveBatch("first");
|
||||
var second = new BeforeConvertCallbackForSaveBatch("second");
|
||||
var third = new BeforeConvertCallbackForSaveBatch("third");
|
||||
|
||||
template.saveAll(List.of(first, second, third));
|
||||
|
||||
var allEntriesInTable = template.findAll(BeforeConvertCallbackForSaveBatch.class);
|
||||
|
||||
Assertions.assertThat(allEntriesInTable)
|
||||
.hasSize(3)
|
||||
.extracting(BeforeConvertCallbackForSaveBatch::getName)
|
||||
.containsOnly("first", "second", "third");
|
||||
}
|
||||
|
||||
@Test // GH-1684
|
||||
void oneToOneWithIdenticalIdColumnName() {
|
||||
|
||||
@@ -2139,6 +2159,32 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Table("BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH")
|
||||
static class BeforeConvertCallbackForSaveBatch {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
public BeforeConvertCallbackForSaveBatch(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public BeforeConvertCallbackForSaveBatch setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@Table("VERSIONED_AGGREGATE")
|
||||
static class AggregateWithPrimitiveShortVersion extends VersionedAggregate {
|
||||
|
||||
@@ -2226,9 +2272,17 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context,
|
||||
BeforeConvertCallback<BeforeConvertCallbackForSaveBatch> callback() {
|
||||
return aggregate -> {
|
||||
aggregate.setId(UUID.randomUUID().toString());
|
||||
return aggregate;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcAggregateOperations operations(ApplicationContext applicationContext, RelationalMappingContext context,
|
||||
DataAccessStrategy dataAccessStrategy, JdbcConverter converter) {
|
||||
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
|
||||
return new JdbcAggregateTemplate(applicationContext, context, converter, dataAccessStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ DROP TABLE THIRD;
|
||||
DROP TABLE SEC;
|
||||
DROP TABLE FIRST;
|
||||
|
||||
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH;
|
||||
|
||||
CREATE TABLE LEGO_SET
|
||||
(
|
||||
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
|
||||
@@ -467,4 +469,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR(36) PRIMARY KEY NOT NULL,
|
||||
NAME VARCHAR(20)
|
||||
);
|
||||
|
||||
@@ -417,4 +417,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR PRIMARY KEY,
|
||||
NAME VARCHAR
|
||||
);
|
||||
|
||||
@@ -419,4 +419,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR PRIMARY KEY,
|
||||
NAME VARCHAR
|
||||
);
|
||||
|
||||
@@ -391,4 +391,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR(36) PRIMARY KEY,
|
||||
NAME VARCHAR(20)
|
||||
);
|
||||
|
||||
@@ -441,4 +441,12 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH;
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR PRIMARY KEY,
|
||||
NAME VARCHAR
|
||||
);
|
||||
|
||||
@@ -397,4 +397,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR(36) PRIMARY KEY,
|
||||
NAME VARCHAR(20)
|
||||
);
|
||||
|
||||
@@ -49,6 +49,8 @@ DROP TABLE THIRD CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE SEC CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE FIRST CASCADE CONSTRAINTS PURGE;
|
||||
|
||||
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH CASCADE CONSTRAINTS PURGE;
|
||||
|
||||
CREATE TABLE LEGO_SET
|
||||
(
|
||||
"id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
|
||||
@@ -447,4 +449,10 @@ CREATE TABLE THIRD
|
||||
SEC NUMBER NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
|
||||
(
|
||||
ID VARCHAR PRIMARY KEY,
|
||||
NAME VARCHAR
|
||||
);
|
||||
|
||||
@@ -52,6 +52,8 @@ DROP TABLE THIRD;
|
||||
DROP TABLE SEC;
|
||||
DROP TABLE FIRST;
|
||||
|
||||
DROP TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH";
|
||||
|
||||
CREATE TABLE LEGO_SET
|
||||
(
|
||||
"id1" SERIAL PRIMARY KEY,
|
||||
@@ -470,4 +472,10 @@ CREATE TABLE THIRD
|
||||
SEC BIGINT NOT NULL,
|
||||
NAME VARCHAR(20) NOT NULL,
|
||||
FOREIGN KEY (SEC) REFERENCES SEC (ID)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH"
|
||||
(
|
||||
ID VARCHAR PRIMARY KEY,
|
||||
NAME VARCHAR
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user