Browse Source

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
pull/2110/head
mipo256 7 months ago committed by Jens Schauder
parent
commit
5b3fa3d995
No known key found for this signature in database
GPG Key ID: 2BE5D185CD2A1CE6
  1. 87
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java
  2. 58
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java
  3. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql
  4. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql
  5. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql
  6. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql
  7. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql
  8. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql
  9. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql
  10. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql

87
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

@ -27,6 +27,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page; 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.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; 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.relational.core.query.Query;
import org.springframework.data.support.PageableExecutionUtils; 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.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -173,19 +185,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
@Override @Override
public <T> List<T> saveAll(Iterable<T> instances) { public <T> List<T> saveAll(Iterable<T> instances) {
return saveInBatch(instances, instance -> changeCreatorSelectorForSave(instance));
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);
} }
/** /**
@ -206,21 +206,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
@Override @Override
public <T> List<T> insertAll(Iterable<T> instances) { public <T> List<T> insertAll(Iterable<T> instances) {
return doInBatch(instances, entity -> createInsertChange(prepareVersionForInsert(entity)));
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);
} }
/** /**
@ -241,6 +227,10 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
@Override @Override
public <T> List<T> updateAll(Iterable<T> instances) { 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"); Assert.notNull(instances, "Aggregate instances must not be null");
@ -249,11 +239,27 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
} }
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>(); List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
for (T instance : instances) { 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");
Function<T, RootAggregateChange<T>> changeCreator = entity -> createUpdateChange(prepareVersionForUpdate(entity)); if (!instances.iterator().hasNext()) {
EntityAndChangeCreator<T> entityChange = new EntityAndChangeCreator<>(instance, changeCreator); return Collections.emptyList();
entityAndChangeCreators.add(entityChange); }
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
for (T instance : instances) {
verifyIdProperty(instance);
entityAndChangeCreators.add(new EntityAndChangeCreator<T>(instance, changeCreatorFunction));
} }
return performSaveAll(entityAndChangeCreators); return performSaveAll(entityAndChangeCreators);
} }
@ -473,7 +479,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
T aggregateRoot = triggerBeforeConvert(instance.entity); T aggregateRoot = triggerBeforeConvert(instance.entity);
RootAggregateChange<T> change = instance.changeCreator.apply(aggregateRoot); RootAggregateChange<T> change = instance.changeCreator.createAggregateChange(aggregateRoot);
aggregateRoot = triggerBeforeSave(change.getRoot(), change); aggregateRoot = triggerBeforeSave(change.getRoot(), change);
@ -531,7 +537,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
return results; 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) return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
? entity -> createInsertChange(prepareVersionForInsert(entity)) ? entity -> createInsertChange(prepareVersionForInsert(entity))
@ -671,6 +677,13 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private record EntityAndPreviousVersion<T> (T entity, @Nullable Number version) { 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);
}
} }
} }

58
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java

@ -28,9 +28,11 @@ import java.util.ArrayList;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.SoftAssertions; import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.TestClass;
import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.jdbc.testing.TestDatabaseFeatures; 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.mapping.context.InvalidPersistentPropertyPath;
import org.springframework.data.relational.core.conversion.DbActionExecutionException; import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.data.relational.core.mapping.Column; 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.MappedCollection;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table; 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.Criteria;
import org.springframework.data.relational.core.query.CriteriaDefinition; import org.springframework.data.relational.core.query.CriteriaDefinition;
import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Query;
@ -1328,6 +1332,22 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
assertThat(enumMapOwners).containsExactly(enumMapOwner); 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 @Test // GH-1684
void oneToOneWithIdenticalIdColumnName() { 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") @Table("VERSIONED_AGGREGATE")
static class AggregateWithPrimitiveShortVersion extends VersionedAggregate { static class AggregateWithPrimitiveShortVersion extends VersionedAggregate {
@ -2226,9 +2272,17 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
} }
@Bean @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) { DataAccessStrategy dataAccessStrategy, JdbcConverter converter) {
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); return new JdbcAggregateTemplate(applicationContext, context, converter, dataAccessStrategy);
} }
} }

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql

@ -59,6 +59,8 @@ DROP TABLE THIRD;
DROP TABLE SEC; DROP TABLE SEC;
DROP TABLE FIRST; DROP TABLE FIRST;
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH;
CREATE TABLE LEGO_SET CREATE TABLE LEGO_SET
( (
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, "id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@ -468,3 +470,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR(36) PRIMARY KEY NOT NULL,
NAME VARCHAR(20)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql

@ -418,3 +418,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR PRIMARY KEY,
NAME VARCHAR
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql

@ -420,3 +420,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR PRIMARY KEY,
NAME VARCHAR
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql

@ -392,3 +392,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR(36) PRIMARY KEY,
NAME VARCHAR(20)
);

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql

@ -442,3 +442,11 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) 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
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql

@ -398,3 +398,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR(36) PRIMARY KEY,
NAME VARCHAR(20)
);

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql

@ -49,6 +49,8 @@ DROP TABLE THIRD CASCADE CONSTRAINTS PURGE;
DROP TABLE SEC CASCADE CONSTRAINTS PURGE; DROP TABLE SEC CASCADE CONSTRAINTS PURGE;
DROP TABLE FIRST CASCADE CONSTRAINTS PURGE; DROP TABLE FIRST CASCADE CONSTRAINTS PURGE;
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH CASCADE CONSTRAINTS PURGE;
CREATE TABLE LEGO_SET CREATE TABLE LEGO_SET
( (
"id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, "id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
@ -448,3 +450,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
(
ID VARCHAR PRIMARY KEY,
NAME VARCHAR
);

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql

@ -52,6 +52,8 @@ DROP TABLE THIRD;
DROP TABLE SEC; DROP TABLE SEC;
DROP TABLE FIRST; DROP TABLE FIRST;
DROP TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH";
CREATE TABLE LEGO_SET CREATE TABLE LEGO_SET
( (
"id1" SERIAL PRIMARY KEY, "id1" SERIAL PRIMARY KEY,
@ -471,3 +473,9 @@ CREATE TABLE THIRD
NAME VARCHAR(20) NOT NULL, NAME VARCHAR(20) NOT NULL,
FOREIGN KEY (SEC) REFERENCES SEC (ID) FOREIGN KEY (SEC) REFERENCES SEC (ID)
); );
CREATE TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH"
(
ID VARCHAR PRIMARY KEY,
NAME VARCHAR
);

Loading…
Cancel
Save