Browse Source

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
pull/1298/head
Mark Paluch 3 years ago
parent
commit
d79ba7911f
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 33
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java
  2. 21
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateUnitTests.java
  3. 63
      spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java
  4. 6
      src/main/asciidoc/jdbc.adoc

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

@ -34,6 +34,7 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.EntityLifecycleEventDelegate;
import org.springframework.data.relational.core.conversion.AggregateChange; import org.springframework.data.relational.core.conversion.AggregateChange;
import org.springframework.data.relational.core.conversion.BatchingAggregateChange; import org.springframework.data.relational.core.conversion.BatchingAggregateChange;
import org.springframework.data.relational.core.conversion.DeleteAggregateChange; import org.springframework.data.relational.core.conversion.DeleteAggregateChange;
@ -67,7 +68,7 @@ import org.springframework.util.ClassUtils;
*/ */
public class JdbcAggregateTemplate implements JdbcAggregateOperations { public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private final ApplicationEventPublisher publisher; private final EntityLifecycleEventDelegate eventDelegate = new EntityLifecycleEventDelegate();
private final RelationalMappingContext context; private final RelationalMappingContext context;
private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter; private final RelationalEntityDeleteWriter jdbcEntityDeleteWriter;
@ -95,7 +96,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(converter, "RelationalConverter must not be null"); Assert.notNull(converter, "RelationalConverter must not be null");
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null"); Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
this.publisher = publisher; this.eventDelegate.setPublisher(publisher);
this.context = context; this.context = context;
this.accessStrategy = dataAccessStrategy; this.accessStrategy = dataAccessStrategy;
this.converter = converter; this.converter = converter;
@ -123,7 +124,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
Assert.notNull(converter, "RelationalConverter must not be null"); Assert.notNull(converter, "RelationalConverter must not be null");
Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null"); Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null");
this.publisher = publisher; this.eventDelegate.setPublisher(publisher);
this.context = context; this.context = context;
this.accessStrategy = dataAccessStrategy; this.accessStrategy = dataAccessStrategy;
this.converter = converter; this.converter = converter;
@ -145,6 +146,18 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
this.entityCallbacks = entityCallbacks; this.entityCallbacks = entityCallbacks;
} }
/**
* Configure whether lifecycle events such as {@link AfterSaveEvent}, {@link BeforeSaveEvent}, etc. should be
* published or whether emission should be suppressed. Enabled by default.
*
* @param enabled {@code true} to enable entity lifecycle events; {@code false} to disable entity lifecycle events.
* @since 3.0
* @see AbstractRelationalEvent
*/
public void setEntityLifecycleEventsEnabled(boolean enabled) {
this.eventDelegate.setEventsEnabled(enabled);
}
@Override @Override
public <T> T save(T instance) { public <T> T save(T instance) {
@ -529,34 +542,32 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private <T> T triggerAfterConvert(T entity) { private <T> T triggerAfterConvert(T entity) {
publisher.publishEvent(new AfterConvertEvent<>(entity)); eventDelegate.publishEvent(() -> new AfterConvertEvent<>(entity));
return entityCallbacks.callback(AfterConvertCallback.class, entity); return entityCallbacks.callback(AfterConvertCallback.class, entity);
} }
private <T> T triggerBeforeConvert(T aggregateRoot) { private <T> T triggerBeforeConvert(T aggregateRoot) {
publisher.publishEvent(new BeforeConvertEvent<>(aggregateRoot)); eventDelegate.publishEvent(() -> new BeforeConvertEvent<>(aggregateRoot));
return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot); return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
} }
private <T> T triggerBeforeSave(T aggregateRoot, AggregateChange<T> change) { private <T> T triggerBeforeSave(T aggregateRoot, AggregateChange<T> change) {
publisher.publishEvent(new BeforeSaveEvent<>(aggregateRoot, change)); eventDelegate.publishEvent(() -> new BeforeSaveEvent<>(aggregateRoot, change));
return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, change); return entityCallbacks.callback(BeforeSaveCallback.class, aggregateRoot, change);
} }
private <T> T triggerAfterSave(T aggregateRoot, AggregateChange<T> change) { private <T> T triggerAfterSave(T aggregateRoot, AggregateChange<T> change) {
publisher.publishEvent(new AfterSaveEvent<>(aggregateRoot, change)); eventDelegate.publishEvent(() -> new AfterSaveEvent<>(aggregateRoot, change));
return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot); return entityCallbacks.callback(AfterSaveCallback.class, aggregateRoot);
} }
private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) { private <T> void triggerAfterDelete(@Nullable T aggregateRoot, Object id, AggregateChange<T> change) {
publisher.publishEvent(new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change)); eventDelegate.publishEvent(() -> new AfterDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
if (aggregateRoot != null) { if (aggregateRoot != null) {
entityCallbacks.callback(AfterDeleteCallback.class, aggregateRoot); entityCallbacks.callback(AfterDeleteCallback.class, aggregateRoot);
@ -566,7 +577,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
@Nullable @Nullable
private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) { private <T> T triggerBeforeDelete(@Nullable T aggregateRoot, Object id, MutableAggregateChange<T> change) {
publisher.publishEvent(new BeforeDeleteEvent<>(Identifier.of(id), aggregateRoot, change)); eventDelegate.publishEvent(() -> new BeforeDeleteEvent<>(Identifier.of(id), aggregateRoot, change));
if (aggregateRoot != null) { if (aggregateRoot != null) {
return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, change); return entityCallbacks.callback(BeforeDeleteCallback.class, aggregateRoot, change);

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

@ -30,6 +30,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version; import org.springframework.data.annotation.Version;
@ -62,7 +63,7 @@ import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class JdbcAggregateTemplateUnitTests { public class JdbcAggregateTemplateUnitTests {
JdbcAggregateOperations template; JdbcAggregateTemplate template;
@Mock DataAccessStrategy dataAccessStrategy; @Mock DataAccessStrategy dataAccessStrategy;
@Mock ApplicationEventPublisher eventPublisher; @Mock ApplicationEventPublisher eventPublisher;
@ -97,7 +98,7 @@ public class JdbcAggregateTemplateUnitTests {
assertThat(template.findAllById(emptyList(), SampleEntity.class)).isEmpty(); assertThat(template.findAllById(emptyList(), SampleEntity.class)).isEmpty();
} }
@Test // DATAJDBC-393 @Test // DATAJDBC-393, GH-1291
public void callbackOnSave() { public void callbackOnSave() {
SampleEntity first = new SampleEntity(null, "Alfred"); SampleEntity first = new SampleEntity(null, "Alfred");
@ -112,6 +113,22 @@ public class JdbcAggregateTemplateUnitTests {
verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), any(MutableAggregateChange.class)); verify(callbacks).callback(eq(BeforeSaveCallback.class), eq(second), any(MutableAggregateChange.class));
verify(callbacks).callback(AfterSaveCallback.class, third); verify(callbacks).callback(AfterSaveCallback.class, third);
assertThat(last).isEqualTo(third); assertThat(last).isEqualTo(third);
verify(eventPublisher, times(3)).publishEvent(any(Object.class));
}
@Test // GH-1291
public void doesNotEmitEvents() {
SampleEntity first = new SampleEntity(null, "Alfred");
SampleEntity second = new SampleEntity(23L, "Alfred E.");
SampleEntity third = new SampleEntity(23L, "Neumann");
when(callbacks.callback(any(Class.class), any(), any())).thenReturn(second, third);
template.setEntityLifecycleEventsEnabled(false);
template.save(first);
verifyNoInteractions(eventPublisher);
} }
@Test // GH-1137 @Test // GH-1137

63
spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java

@ -0,0 +1,63 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.Nullable;
/**
* Delegate class to encapsulate lifecycle event configuration and publishing. Event creation is deferred within an
* event {@link Supplier} to delay the actual event object creation.
*
* @author Mark Paluch
* @since 3.0
* @see ApplicationEventPublisher
*/
public class EntityLifecycleEventDelegate {
private @Nullable ApplicationEventPublisher publisher;
private boolean eventsEnabled = true;
public void setPublisher(@Nullable ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public boolean isEventsEnabled() {
return eventsEnabled;
}
public void setEventsEnabled(boolean eventsEnabled) {
this.eventsEnabled = eventsEnabled;
}
/**
* Publish an application event if event publishing is enabled.
*
* @param eventSupplier the supplier for application events.
*/
public void publishEvent(Supplier<?> eventSupplier) {
if (canPublishEvent()) {
publisher.publishEvent(eventSupplier.get());
}
}
private boolean canPublishEvent() {
return publisher != null && eventsEnabled;
}
}

6
src/main/asciidoc/jdbc.adoc

@ -829,6 +829,10 @@ Note that the type used for prefixing the statement name is the name of the aggr
== Lifecycle Events == Lifecycle Events
Spring Data JDBC triggers events that get published to any matching `ApplicationListener` beans in the application context. Spring Data JDBC triggers events that get published to any matching `ApplicationListener` beans in the application context.
Entity lifecycle events can be costly and you may notice a change in the performance profile when loading large result sets.
You can disable lifecycle events on the link:{javadoc-base}org/springframework/data/jdbc/core/JdbcAggregateTemplate.html#setEntityLifecycleEventsEnabled(boolean)[Template API].
For example, the following listener gets invoked before an aggregate gets saved: For example, the following listener gets invoked before an aggregate gets saved:
==== ====
@ -1100,4 +1104,4 @@ Select * from user u where u.lastname = lastname LOCK IN SHARE MODE
---- ----
==== ====
Alternative to `LockMode.PESSIMISTIC_READ` you can use `LockMode.PESSIMISTIC_WRITE`. Alternative to `LockMode.PESSIMISTIC_READ` you can use `LockMode.PESSIMISTIC_WRITE`.

Loading…
Cancel
Save