Skip to content

Commit d79ba79

Browse files
committed
Allow disabling entity lifecycle events.
We now support disabling lifecycle events through the Template API to reduce the framework overhead when events are not needed. Closes #1291
1 parent b691af7 commit d79ba79

File tree

4 files changed

+109
-14
lines changed

4 files changed

+109
-14
lines changed

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3535
import org.springframework.data.mapping.IdentifierAccessor;
3636
import org.springframework.data.mapping.callback.EntityCallbacks;
37+
import org.springframework.data.relational.core.EntityLifecycleEventDelegate;
3738
import org.springframework.data.relational.core.conversion.AggregateChange;
3839
import org.springframework.data.relational.core.conversion.BatchingAggregateChange;
3940
import org.springframework.data.relational.core.conversion.DeleteAggregateChange;
@@ -67,7 +68,7 @@
6768
*/
6869
public class JdbcAggregateTemplate implements JdbcAggregateOperations {
6970

70-
private final ApplicationEventPublisher publisher;
71+
private final EntityLifecycleEventDelegate eventDelegate = new EntityLifecycleEventDelegate();
7172
private final RelationalMappingContext context;
7273

7374
private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter;
@@ -95,7 +96,7 @@ public JdbcAggregateTemplate(ApplicationContext publisher, RelationalMappingCont
9596
Assert.notNull(converter, "RelationalConverter must not be null");
9697
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
9798

98-
this.publisher = publisher;
99+
this.eventDelegate.setPublisher(publisher);
99100
this.context = context;
100101
this.accessStrategy = dataAccessStrategy;
101102
this.converter = converter;
@@ -123,7 +124,7 @@ public JdbcAggregateTemplate(ApplicationEventPublisher publisher, RelationalMapp
123124
Assert.notNull(converter, "RelationalConverter must not be null");
124125
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
125126

126-
this.publisher = publisher;
127+
this.eventDelegate.setPublisher(publisher);
127128
this.context = context;
128129
this.accessStrategy = dataAccessStrategy;
129130
this.converter = converter;
@@ -145,6 +146,18 @@ public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
145146
this.entityCallbacks = entityCallbacks;
146147
}
147148

149+
/**
150+
* Configure whether lifecycle events such as {@link AfterSaveEvent}, {@link BeforeSaveEvent}, etc. should be
151+
* published or whether emission should be suppressed. Enabled by default.
152+
*
153+
* @param enabled {@code true} to enable entity lifecycle events; {@code false} to disable entity lifecycle events.
154+
* @since 3.0
155+
* @see AbstractRelationalEvent
156+
*/
157+
public void setEntityLifecycleEventsEnabled(boolean enabled) {
158+
this.eventDelegate.setEventsEnabled(enabled);
159+
}
160+
148161
@Override
149162
public <T> T save(T instance) {
150163

@@ -529,34 +542,32 @@ private <T> Iterable<T> triggerAfterConvert(Iterable<T> all) {
529542

530543
private <T> T triggerAfterConvert(T entity) {
531544

532-
publisher.publishEvent(new AfterConvertEvent<>(entity));
545+
eventDelegate.publishEvent(() -> new AfterConvertEvent<>(entity));
533546
return entityCallbacks.callback(AfterConvertCallback.class, entity);
534547
}
535548

536549
private <T> T triggerBeforeConvert(T aggregateRoot) {
537550

538-
publisher.publishEvent(new BeforeConvertEvent<>(aggregateRoot));
539-
551+
eventDelegate.publishEvent(() -> new BeforeConvertEvent<>(aggregateRoot));
540552
return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
541553
}
542554

543555
private <T> T triggerBeforeSave(T aggregateRoot, AggregateChange<T> change) {
544556

545-
publisher.publishEvent(new BeforeSaveEvent<>(aggregateRoot, change));
557+
eventDelegate.publishEvent(() -> new BeforeSaveEvent<>(aggregateRoot, change));
546558

547559
return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, change);
548560
}
549561

550562
private <T> T triggerAfterSave(T aggregateRoot, AggregateChange<T> change) {
551563

552-
publisher.publishEvent(new AfterSaveEvent<>(aggregateRoot, change));
553-
564+
eventDelegate.publishEvent(() -> new AfterSaveEvent<>(aggregateRoot, change));
554565
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot);
555566
}
556567

557568
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) {
558569

559-
publisher.publishEvent(new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
570+
eventDelegate.publishEvent(() -> new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
560571

561572
if (aggregateRoot != null) {
562573
entityCallbacks.callback(AfterDeleteCallback.class, aggregateRoot);
@@ -566,7 +577,7 @@ private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, Aggreg
566577
@Nullable
567578
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) {
568579

569-
publisher.publishEvent(new BeforeDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
580+
eventDelegate.publishEvent(() -> new BeforeDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
570581

571582
if (aggregateRoot != null) {
572583
return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, change);

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.mockito.ArgumentCaptor;
3131
import org.mockito.Mock;
3232
import org.mockito.junit.jupiter.MockitoExtension;
33+
3334
import org.springframework.context.ApplicationEventPublisher;
3435
import org.springframework.data.annotation.Id;
3536
import org.springframework.data.annotation.Version;
@@ -62,7 +63,7 @@
6263
@ExtendWith(MockitoExtension.class)
6364
public class JdbcAggregateTemplateUnitTests {
6465

65-
JdbcAggregateOperations template;
66+
JdbcAggregateTemplate template;
6667

6768
@Mock DataAccessStrategy dataAccessStrategy;
6869
@Mock ApplicationEventPublisher eventPublisher;
@@ -97,7 +98,7 @@ public void findAllByIdWithEmptyListMustReturnEmptyResult() {
9798
assertThat(template.findAllById(emptyList(), SampleEntity.class)).isEmpty();
9899
}
99100

100-
@Test // DATAJDBC-393
101+
@Test // DATAJDBC-393, GH-1291
101102
public void callbackOnSave() {
102103

103104
SampleEntity first = new SampleEntity(null, "Alfred");
@@ -112,6 +113,22 @@ public void callbackOnSave() {
112113
verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), any(MutableAggregateChange.class));
113114
verify(callbacks).callback(AfterSaveCallback.class, third);
114115
assertThat(last).isEqualTo(third);
116+
verify(eventPublisher, times(3)).publishEvent(any(Object.class));
117+
}
118+
119+
@Test // GH-1291
120+
public void doesNotEmitEvents() {
121+
122+
SampleEntity first = new SampleEntity(null, "Alfred");
123+
SampleEntity second = new SampleEntity(23L, "Alfred E.");
124+
SampleEntity third = new SampleEntity(23L, "Neumann");
125+
126+
when(callbacks.callback(any(Class.class), any(), any())).thenReturn(second, third);
127+
128+
template.setEntityLifecycleEventsEnabled(false);
129+
template.save(first);
130+
131+
verifyNoInteractions(eventPublisher);
115132
}
116133

117134
@Test // GH-1137
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core;
17+
18+
import java.util.function.Supplier;
19+
20+
import org.springframework.context.ApplicationEventPublisher;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* Delegate class to encapsulate lifecycle event configuration and publishing. Event creation is deferred within an
25+
* event {@link Supplier} to delay the actual event object creation.
26+
*
27+
* @author Mark Paluch
28+
* @since 3.0
29+
* @see ApplicationEventPublisher
30+
*/
31+
public class EntityLifecycleEventDelegate {
32+
33+
private @Nullable ApplicationEventPublisher publisher;
34+
private boolean eventsEnabled = true;
35+
36+
public void setPublisher(@Nullable ApplicationEventPublisher publisher) {
37+
this.publisher = publisher;
38+
}
39+
40+
public boolean isEventsEnabled() {
41+
return eventsEnabled;
42+
}
43+
44+
public void setEventsEnabled(boolean eventsEnabled) {
45+
this.eventsEnabled = eventsEnabled;
46+
}
47+
48+
/**
49+
* Publish an application event if event publishing is enabled.
50+
*
51+
* @param eventSupplier the supplier for application events.
52+
*/
53+
public void publishEvent(Supplier<?> eventSupplier) {
54+
55+
if (canPublishEvent()) {
56+
publisher.publishEvent(eventSupplier.get());
57+
}
58+
}
59+
60+
private boolean canPublishEvent() {
61+
return publisher != null && eventsEnabled;
62+
}
63+
}

src/main/asciidoc/jdbc.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,10 @@ Note that the type used for prefixing the statement name is the name of the aggr
829829
== Lifecycle Events
830830

831831
Spring Data JDBC triggers events that get published to any matching `ApplicationListener` beans in the application context.
832+
833+
Entity lifecycle events can be costly and you may notice a change in the performance profile when loading large result sets.
834+
You can disable lifecycle events on the link:{javadoc-base}org/springframework/data/jdbc/core/JdbcAggregateTemplate.html#setEntityLifecycleEventsEnabled(boolean)[Template API].
835+
832836
For example, the following listener gets invoked before an aggregate gets saved:
833837

834838
====
@@ -1100,4 +1104,4 @@ Select * from user u where u.lastname = lastname LOCK IN SHARE MODE
11001104
----
11011105
====
11021106

1103-
Alternative to `LockMode.PESSIMISTIC_READ` you can use `LockMode.PESSIMISTIC_WRITE`.
1107+
Alternative to `LockMode.PESSIMISTIC_READ` you can use `LockMode.PESSIMISTIC_WRITE`.

0 commit comments

Comments
 (0)