Skip to content

Commit 651a4f5

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 4041729 commit 651a4f5

10 files changed

+168
-21
lines changed

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.stream.Stream;
2929
import java.util.stream.StreamSupport;
3030

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

@@ -175,7 +187,7 @@ public <T> T save(T instance) {
175187

176188
@Override
177189
public <T> List<T> saveAll(Iterable<T> instances) {
178-
return doInBatch(instances, (first) -> (second -> changeCreatorSelectorForSave(first).apply(second)));
190+
return saveInBatch(instances, instance -> changeCreatorSelectorForSave(instance));
179191
}
180192

181193
/**
@@ -196,7 +208,7 @@ public <T> T insert(T instance) {
196208

197209
@Override
198210
public <T> List<T> insertAll(Iterable<T> instances) {
199-
return doInBatch(instances, (__) -> (entity -> createInsertChange(prepareVersionForInsert(entity))));
211+
return doInBatch(instances, entity -> createInsertChange(prepareVersionForInsert(entity)));
200212
}
201213

202214
/**
@@ -217,10 +229,28 @@ public <T> T update(T instance) {
217229

218230
@Override
219231
public <T> List<T> updateAll(Iterable<T> instances) {
220-
return doInBatch(instances, (__) -> (entity -> createUpdateChange(prepareVersionForUpdate(entity))));
232+
return doInBatch(instances, entity -> createUpdateChange(prepareVersionForUpdate(entity)));
221233
}
222234

223-
private <T> List<T> doInBatch(Iterable<T> instances,Function<T, Function<T, RootAggregateChange<T>>> changeCreatorFunction) {
235+
private <T> List<T> saveInBatch(Iterable<T> instances, Function<T, AggregateChangeCreator<T>> changes) {
236+
237+
Assert.notNull(instances, "Aggregate instances must not be null");
238+
239+
if (!instances.iterator().hasNext()) {
240+
return Collections.emptyList();
241+
}
242+
243+
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
244+
245+
for (T instance : instances) {
246+
verifyIdProperty(instance);
247+
entityAndChangeCreators.add(new EntityAndChangeCreator<>(instance, changes.apply(instance)));
248+
}
249+
250+
return performSaveAll(entityAndChangeCreators);
251+
}
252+
253+
private <T> List<T> doInBatch(Iterable<T> instances, AggregateChangeCreator<T> changeCreatorFunction) {
224254

225255
Assert.notNull(instances, "Aggregate instances must not be null");
226256

@@ -231,7 +261,7 @@ private <T> List<T> doInBatch(Iterable<T> instances,Function<T, Function<T, Root
231261
List<EntityAndChangeCreator<T>> entityAndChangeCreators = new ArrayList<>();
232262
for (T instance : instances) {
233263
verifyIdProperty(instance);
234-
entityAndChangeCreators.add(new EntityAndChangeCreator<T>(instance, changeCreatorFunction.apply(instance)));
264+
entityAndChangeCreators.add(new EntityAndChangeCreator<T>(instance, changeCreatorFunction));
235265
}
236266
return performSaveAll(entityAndChangeCreators);
237267
}
@@ -484,7 +514,7 @@ private <T> RootAggregateChange<T> beforeExecute(EntityAndChangeCreator<T> insta
484514

485515
T aggregateRoot = triggerBeforeConvert(instance.entity);
486516

487-
RootAggregateChange<T> change = instance.changeCreator.apply(aggregateRoot);
517+
RootAggregateChange<T> change = instance.changeCreator.createAggregateChange(aggregateRoot);
488518

489519
aggregateRoot = triggerBeforeSave(change.getRoot(), change);
490520

@@ -542,7 +572,7 @@ private <T> List<T> performSaveAll(Iterable<EntityAndChangeCreator<T>> instances
542572
return results;
543573
}
544574

545-
private <T> Function<T, RootAggregateChange<T>> changeCreatorSelectorForSave(T instance) {
575+
private <T> AggregateChangeCreator<T> changeCreatorSelectorForSave(T instance) {
546576

547577
return context.getRequiredPersistentEntity(instance.getClass()).isNew(instance)
548578
? entity -> createInsertChange(prepareVersionForInsert(entity))
@@ -681,6 +711,13 @@ private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableA
681711
private record EntityAndPreviousVersion<T> (T entity, @Nullable Number version) {
682712
}
683713

684-
private record EntityAndChangeCreator<T> (T entity, Function<T, RootAggregateChange<T>> changeCreator) {
714+
private record EntityAndChangeCreator<T> (T entity, AggregateChangeCreator<T> changeCreator) {
715+
}
716+
717+
private interface AggregateChangeCreator<T> extends Function<T, RootAggregateChange<T>> {
718+
719+
default RootAggregateChange<T> createAggregateChange(T instance) {
720+
return this.apply(instance);
721+
}
685722
}
686723
}

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
@@ -29,8 +29,10 @@
2929
import java.util.stream.IntStream;
3030
import java.util.stream.Stream;
3131

32+
import org.assertj.core.api.Assertions;
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;
@@ -1373,6 +1377,22 @@ void mapWithEnumKey() {
13731377
assertThat(enumMapOwners).containsExactly(enumMapOwner);
13741378
}
13751379

1380+
@Test //GH-2064
1381+
void saveAllBeforeConvertCallback() {
1382+
var first = new BeforeConvertCallbackForSaveBatch("first");
1383+
var second = new BeforeConvertCallbackForSaveBatch("second");
1384+
var third = new BeforeConvertCallbackForSaveBatch("third");
1385+
1386+
template.saveAll(List.of(first, second, third));
1387+
1388+
var allEntriesInTable = template.findAll(BeforeConvertCallbackForSaveBatch.class);
1389+
1390+
Assertions.assertThat(allEntriesInTable)
1391+
.hasSize(3)
1392+
.extracting(BeforeConvertCallbackForSaveBatch::getName)
1393+
.containsOnly("first", "second", "third");
1394+
}
1395+
13761396
@Test // GH-1684
13771397
void oneToOneWithIdenticalIdColumnName() {
13781398

@@ -2184,6 +2204,32 @@ public Short getVersion() {
21842204
}
21852205
}
21862206

2207+
@Table("BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH")
2208+
static class BeforeConvertCallbackForSaveBatch {
2209+
2210+
@Id
2211+
private String id;
2212+
2213+
private String name;
2214+
2215+
public BeforeConvertCallbackForSaveBatch(String name) {
2216+
this.name = name;
2217+
}
2218+
2219+
public String getId() {
2220+
return id;
2221+
}
2222+
2223+
public BeforeConvertCallbackForSaveBatch setId(String id) {
2224+
this.id = id;
2225+
return this;
2226+
}
2227+
2228+
public String getName() {
2229+
return name;
2230+
}
2231+
}
2232+
21872233
@Table("VERSIONED_AGGREGATE")
21882234
static class AggregateWithPrimitiveShortVersion extends VersionedAggregate {
21892235

@@ -2271,9 +2317,17 @@ TestClass testClass() {
22712317
}
22722318

22732319
@Bean
2274-
JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context,
2320+
BeforeConvertCallback<BeforeConvertCallbackForSaveBatch> callback() {
2321+
return aggregate -> {
2322+
aggregate.setId(UUID.randomUUID().toString());
2323+
return aggregate;
2324+
};
2325+
}
2326+
2327+
@Bean
2328+
JdbcAggregateOperations operations(ApplicationContext applicationContext, RelationalMappingContext context,
22752329
DataAccessStrategy dataAccessStrategy, JdbcConverter converter) {
2276-
return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy);
2330+
return new JdbcAggregateTemplate(applicationContext, context, converter, dataAccessStrategy);
22772331
}
22782332
}
22792333

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)