Skip to content

Commit 5b3fa3d

Browse files
mipo256schauder
authored andcommitted
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
1 parent 1e50d5a commit 5b3fa3d

10 files changed

+170
-47
lines changed

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

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.stream.Collectors;
2828
import java.util.stream.StreamSupport;
2929

30+
import org.jspecify.annotations.Nullable;
3031
import org.springframework.context.ApplicationContext;
3132
import org.springframework.context.ApplicationEventPublisher;
3233
import org.springframework.data.domain.Page;
@@ -49,11 +50,22 @@
4950
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
5051
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
5152
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
52-
import org.springframework.data.relational.core.mapping.event.*;
53+
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
54+
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
55+
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
56+
import org.springframework.data.relational.core.mapping.event.AfterDeleteCallback;
57+
import org.springframework.data.relational.core.mapping.event.AfterDeleteEvent;
58+
import org.springframework.data.relational.core.mapping.event.AfterSaveCallback;
59+
import org.springframework.data.relational.core.mapping.event.AfterSaveEvent;
60+
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
61+
import org.springframework.data.relational.core.mapping.event.BeforeConvertEvent;
62+
import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback;
63+
import org.springframework.data.relational.core.mapping.event.BeforeDeleteEvent;
64+
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
65+
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
66+
import org.springframework.data.relational.core.mapping.event.Identifier;
5367
import org.springframework.data.relational.core.query.Query;
5468
import org.springframework.data.support.PageableExecutionUtils;
55-
import org.springframework.data.util.Streamable;
56-
import org.springframework.lang.Nullable;
5769
import org.springframework.util.Assert;
5870
import org.springframework.util.ClassUtils;
5971

@@ -173,19 +185,7 @@ public <T> T save(T instance) {
173185

174186
@Override
175187
public <T> List<T> saveAll(Iterable<T> instances) {
176-
177-
Assert.notNull(instances, "Aggregate instances must not be null");
178-
179-
if (!instances.iterator().hasNext()) {
180-
return Collections.emptyList();
181-
}
182-
183-
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
184-
for (T instance : instances) {
185-
verifyIdProperty(instance);
186-
entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changeCreatorSelectorForSave(instance)));
187-
}
188-
return performSaveAll(entityAndChangeCreators);
188+
return saveInBatch(instances, instance -> changeCreatorSelectorForSave(instance));
189189
}
190190

191191
/**
@@ -206,21 +206,7 @@ public <T> T insert(T instance) {
206206

207207
@Override
208208
public <T> List<T> insertAll(Iterable<T> instances) {
209-
210-
Assert.notNull(instances, "Aggregate instances must not be null");
211-
212-
if (!instances.iterator().hasNext()) {
213-
return Collections.emptyList();
214-
}
215-
216-
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
217-
for (T instance : instances) {
218-
219-
Function<T, RootAggregateChange<T>> changeCreator = entity -> createInsertChange(prepareVersionForInsert(entity));
220-
EntityAndChangeCreator<T> entityChange = new EntityAndChangeCreator<>(instance, changeCreator);
221-
entityAndChangeCreators.add(entityChange);
222-
}
223-
return performSaveAll(entityAndChangeCreators);
209+
return doInBatch(instances, entity -> createInsertChange(prepareVersionForInsert(entity)));
224210
}
225211

226212
/**
@@ -241,6 +227,10 @@ public <T> T update(T instance) {
241227

242228
@Override
243229
public <T> List<T> updateAll(Iterable<T> instances) {
230+
return doInBatch(instances, entity -> createUpdateChange(prepareVersionForUpdate(entity)));
231+
}
232+
233+
private <T> List<T> saveInBatch(Iterable<T> instances, Function<T, AggregateChangeCreator<T>> changes) {
244234

245235
Assert.notNull(instances, "Aggregate instances must not be null");
246236

@@ -249,11 +239,27 @@ public <T> List<T> updateAll(Iterable<T> instances) {
249239
}
250240

251241
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
242+
252243
for (T instance : instances) {
244+
verifyIdProperty(instance);
245+
entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changes.apply(instance)));
246+
}
247+
248+
return performSaveAll(entityAndChangeCreators);
249+
}
250+
251+
private <T> List<T> doInBatch(Iterable<T> instances, AggregateChangeCreator<T> changeCreatorFunction) {
252+
253+
Assert.notNull(instances, "Aggregate instances must not be null");
253254

254-
Function<T, RootAggregateChange<T>> changeCreator = entity -> createUpdateChange(prepareVersionForUpdate(entity));
255-
EntityAndChangeCreator<T> entityChange = new EntityAndChangeCreator<>(instance, changeCreator);
256-
entityAndChangeCreators.add(entityChange);
255+
if (!instances.iterator().hasNext()) {
256+
return Collections.emptyList();
257+
}
258+
259+
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
260+
for (T instance : instances) {
261+
verifyIdProperty(instance);
262+
entityAndChangeCreators.add(new EntityAndChangeCreator<T>(instance, changeCreatorFunction));
257263
}
258264
return performSaveAll(entityAndChangeCreators);
259265
}
@@ -473,7 +479,7 @@ private <T> RootAggregateChange<T> beforeExecute(EntityAndChangeCreator<T> insta
473479

474480
T aggregateRoot = triggerBeforeConvert(instance.entity);
475481

476-
RootAggregateChange<T> change = instance.changeCreator.apply(aggregateRoot);
482+
RootAggregateChange<T> change = instance.changeCreator.createAggregateChange(aggregateRoot);
477483

478484
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
479485

@@ -531,7 +537,7 @@ private <T> List<T> performSaveAll(Iterable<EntityAndChangeCreator<T>> instances
531537
return results;
532538
}
533539

534-
private <T> Function<T, RootAggregateChange<T>> changeCreatorSelectorForSave(T instance) {
540+
private <T> AggregateChangeCreator<T> changeCreatorSelectorForSave(T instance) {
535541

536542
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
537543
? entity -> createInsertChange(prepareVersionForInsert(entity))
@@ -671,6 +677,13 @@ private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableA
671677
private record EntityAndPreviousVersion<T> (T entity, @Nullable Number version) {
672678
}
673679

674-
private record EntityAndChangeCreator<T> (T entity, Function<T, RootAggregateChange<T>> changeCreator) {
680+
private record EntityAndChangeCreator<T> (T entity, AggregateChangeCreator<T> changeCreator) {
681+
}
682+
683+
private interface AggregateChangeCreator<T> extends Function<T, RootAggregateChange<T>> {
684+
685+
default RootAggregateChange<T> createAggregateChange(T instance) {
686+
return this.apply(instance);
687+
}
675688
}
676689
}

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import java.util.function.Function;
2929
import java.util.stream.IntStream;
3030

31+
import org.assertj.core.api.Assertions;
3132
import org.assertj.core.api.SoftAssertions;
3233
import org.junit.jupiter.api.Test;
3334
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.context.ApplicationContext;
3436
import org.springframework.context.ApplicationEventPublisher;
3537
import org.springframework.context.annotation.Bean;
3638
import org.springframework.context.annotation.Configuration;
@@ -52,6 +54,7 @@
5254
import org.springframework.data.jdbc.testing.TestClass;
5355
import org.springframework.data.jdbc.testing.TestConfiguration;
5456
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
57+
import org.springframework.data.mapping.callback.EntityCallbacks;
5558
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
5659
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
5760
import org.springframework.data.relational.core.mapping.Column;
@@ -60,6 +63,7 @@
6063
import org.springframework.data.relational.core.mapping.MappedCollection;
6164
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
6265
import org.springframework.data.relational.core.mapping.Table;
66+
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
6367
import org.springframework.data.relational.core.query.Criteria;
6468
import org.springframework.data.relational.core.query.CriteriaDefinition;
6569
import org.springframework.data.relational.core.query.Query;
@@ -1328,6 +1332,22 @@ void mapWithEnumKey() {
13281332
assertThat(enumMapOwners).containsExactly(enumMapOwner);
13291333
}
13301334

1335+
@Test //GH-2064
1336+
void saveAllBeforeConvertCallback() {
1337+
var first = new BeforeConvertCallbackForSaveBatch("first");
1338+
var second = new BeforeConvertCallbackForSaveBatch("second");
1339+
var third = new BeforeConvertCallbackForSaveBatch("third");
1340+
1341+
template.saveAll(List.of(first, second, third));
1342+
1343+
var allEntriesInTable = template.findAll(BeforeConvertCallbackForSaveBatch.class);
1344+
1345+
Assertions.assertThat(allEntriesInTable)
1346+
.hasSize(3)
1347+
.extracting(BeforeConvertCallbackForSaveBatch::getName)
1348+
.containsOnly("first", "second", "third");
1349+
}
1350+
13311351
@Test // GH-1684
13321352
void oneToOneWithIdenticalIdColumnName() {
13331353

@@ -2139,6 +2159,32 @@ public Short getVersion() {
21392159
}
21402160
}
21412161

2162+
@Table("BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH")
2163+
static class BeforeConvertCallbackForSaveBatch {
2164+
2165+
@Id
2166+
private String id;
2167+
2168+
private String name;
2169+
2170+
public BeforeConvertCallbackForSaveBatch(String name) {
2171+
this.name = name;
2172+
}
2173+
2174+
public String getId() {
2175+
return id;
2176+
}
2177+
2178+
public BeforeConvertCallbackForSaveBatch setId(String id) {
2179+
this.id = id;
2180+
return this;
2181+
}
2182+
2183+
public String getName() {
2184+
return name;
2185+
}
2186+
}
2187+
21422188
@Table("VERSIONED_AGGREGATE")
21432189
static class AggregateWithPrimitiveShortVersion extends VersionedAggregate {
21442190

@@ -2226,9 +2272,17 @@ TestClass testClass() {
22262272
}
22272273

22282274
@Bean
2229-
JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context,
2275+
BeforeConvertCallback<BeforeConvertCallbackForSaveBatch> callback() {
2276+
return aggregate -> {
2277+
aggregate.setId(UUID.randomUUID().toString());
2278+
return aggregate;
2279+
};
2280+
}
2281+
2282+
@Bean
2283+
JdbcAggregateOperations operations(ApplicationContext applicationContext, RelationalMappingContext context,
22302284
DataAccessStrategy dataAccessStrategy, JdbcConverter converter) {
2231-
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
2285+
return new JdbcAggregateTemplate(applicationContext, context, converter, dataAccessStrategy);
22322286
}
22332287
}
22342288

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ DROP TABLE THIRD;
5959
DROP TABLE SEC;
6060
DROP TABLE FIRST;
6161

62+
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH;
63+
6264
CREATE TABLE LEGO_SET
6365
(
6466
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@@ -467,4 +469,10 @@ CREATE TABLE THIRD
467469
SEC BIGINT NOT NULL,
468470
NAME VARCHAR(20) NOT NULL,
469471
FOREIGN KEY (SEC) REFERENCES SEC (ID)
470-
);
472+
);
473+
474+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
475+
(
476+
ID VARCHAR(36) PRIMARY KEY NOT NULL,
477+
NAME VARCHAR(20)
478+
);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,4 +417,10 @@ CREATE TABLE THIRD
417417
SEC BIGINT NOT NULL,
418418
NAME VARCHAR(20) NOT NULL,
419419
FOREIGN KEY (SEC) REFERENCES SEC (ID)
420-
);
420+
);
421+
422+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
423+
(
424+
ID VARCHAR PRIMARY KEY,
425+
NAME VARCHAR
426+
);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,10 @@ CREATE TABLE THIRD
419419
SEC BIGINT NOT NULL,
420420
NAME VARCHAR(20) NOT NULL,
421421
FOREIGN KEY (SEC) REFERENCES SEC (ID)
422-
);
422+
);
423+
424+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
425+
(
426+
ID VARCHAR PRIMARY KEY,
427+
NAME VARCHAR
428+
);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,10 @@ CREATE TABLE THIRD
391391
SEC BIGINT NOT NULL,
392392
NAME VARCHAR(20) NOT NULL,
393393
FOREIGN KEY (SEC) REFERENCES SEC (ID)
394-
);
394+
);
395+
396+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
397+
(
398+
ID VARCHAR(36) PRIMARY KEY,
399+
NAME VARCHAR(20)
400+
);

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,4 +441,12 @@ CREATE TABLE THIRD
441441
SEC BIGINT NOT NULL,
442442
NAME VARCHAR(20) NOT NULL,
443443
FOREIGN KEY (SEC) REFERENCES SEC (ID)
444-
);
444+
);
445+
446+
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH;
447+
448+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
449+
(
450+
ID VARCHAR PRIMARY KEY,
451+
NAME VARCHAR
452+
);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,10 @@ CREATE TABLE THIRD
397397
SEC BIGINT NOT NULL,
398398
NAME VARCHAR(20) NOT NULL,
399399
FOREIGN KEY (SEC) REFERENCES SEC (ID)
400-
);
400+
);
401+
402+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
403+
(
404+
ID VARCHAR(36) PRIMARY KEY,
405+
NAME VARCHAR(20)
406+
);

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ DROP TABLE THIRD CASCADE CONSTRAINTS PURGE;
4949
DROP TABLE SEC CASCADE CONSTRAINTS PURGE;
5050
DROP TABLE FIRST CASCADE CONSTRAINTS PURGE;
5151

52+
DROP TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH CASCADE CONSTRAINTS PURGE;
53+
5254
CREATE TABLE LEGO_SET
5355
(
5456
"id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
@@ -447,4 +449,10 @@ CREATE TABLE THIRD
447449
SEC NUMBER NOT NULL,
448450
NAME VARCHAR(20) NOT NULL,
449451
FOREIGN KEY (SEC) REFERENCES SEC (ID)
450-
);
452+
);
453+
454+
CREATE TABLE BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH
455+
(
456+
ID VARCHAR PRIMARY KEY,
457+
NAME VARCHAR
458+
);

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ DROP TABLE THIRD;
5252
DROP TABLE SEC;
5353
DROP TABLE FIRST;
5454

55+
DROP TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH";
56+
5557
CREATE TABLE LEGO_SET
5658
(
5759
"id1" SERIAL PRIMARY KEY,
@@ -470,4 +472,10 @@ CREATE TABLE THIRD
470472
SEC BIGINT NOT NULL,
471473
NAME VARCHAR(20) NOT NULL,
472474
FOREIGN KEY (SEC) REFERENCES SEC (ID)
473-
);
475+
);
476+
477+
CREATE TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH"
478+
(
479+
ID VARCHAR PRIMARY KEY,
480+
NAME VARCHAR
481+
);

0 commit comments

Comments
 (0)