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. 10
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql
  4. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql
  5. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql
  6. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql
  7. 10
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql
  8. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql
  9. 10
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql
  10. 10
      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);
} }
} }

10
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,
@ -467,4 +469,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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)
);

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

@ -417,4 +417,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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-hsql.sql

@ -419,4 +419,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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-mariadb.sql

@ -391,4 +391,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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)
);

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

@ -441,4 +441,12 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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
);

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

@ -397,4 +397,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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)
);

10
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,
@ -447,4 +449,10 @@ CREATE TABLE THIRD
SEC NUMBER NOT NULL, SEC NUMBER NOT NULL,
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
);

10
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,
@ -470,4 +472,10 @@ CREATE TABLE THIRD
SEC BIGINT NOT NULL, SEC BIGINT NOT NULL,
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