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 #4107
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.mongodb.core;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Delegate class to encapsulate lifecycle event configuration and publishing.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 4.0
|
||||
* @see ApplicationEventPublisher
|
||||
*/
|
||||
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 event the application event.
|
||||
*/
|
||||
public void publishEvent(Object event) {
|
||||
|
||||
if (canPublishEvent()) {
|
||||
publisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canPublishEvent() {
|
||||
return publisher != null && eventsEnabled;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -174,6 +175,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final EntityOperations operations;
|
||||
private final PropertyOperations propertyOperations;
|
||||
private final QueryOperations queryOperations;
|
||||
private final EntityLifecycleEventDelegate eventDelegate;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
|
||||
@@ -228,6 +230,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
|
||||
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
|
||||
mongoDbFactory);
|
||||
this.eventDelegate = new EntityLifecycleEventDelegate();
|
||||
|
||||
// We always have a mapping context in the converter, whether it's a simple one or not
|
||||
mappingContext = this.mongoConverter.getMappingContext();
|
||||
@@ -266,6 +269,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.operations = that.operations;
|
||||
this.propertyOperations = that.propertyOperations;
|
||||
this.queryOperations = that.queryOperations;
|
||||
this.eventDelegate = that.eventDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,12 +312,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.readPreference = readPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure whether lifecycle events such as {@link AfterLoadEvent}, {@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 4.0
|
||||
* @see MongoMappingEvent
|
||||
*/
|
||||
public void setEntityLifecycleEventsEnabled(boolean enabled) {
|
||||
this.eventDelegate.setEventsEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
prepareIndexCreator(applicationContext);
|
||||
|
||||
eventPublisher = applicationContext;
|
||||
eventDelegate.setPublisher(eventPublisher);
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(EntityCallbacks.create(applicationContext));
|
||||
@@ -2168,11 +2185,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
|
||||
|
||||
if (eventPublisher != null) {
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
|
||||
eventDelegate.publishEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final EntityOperations operations;
|
||||
private final PropertyOperations propertyOperations;
|
||||
private final QueryOperations queryOperations;
|
||||
private final EntityLifecycleEventDelegate eventDelegate;
|
||||
|
||||
private @Nullable WriteConcern writeConcern;
|
||||
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
|
||||
@@ -256,6 +257,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
|
||||
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
|
||||
mongoDatabaseFactory);
|
||||
this.eventDelegate = new EntityLifecycleEventDelegate();
|
||||
|
||||
// We create indexes based on mapping events
|
||||
if (this.mappingContext instanceof MongoMappingContext) {
|
||||
@@ -287,6 +289,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.propertyOperations = that.propertyOperations;
|
||||
this.sessionSynchronization = that.sessionSynchronization;
|
||||
this.queryOperations = that.queryOperations;
|
||||
this.eventDelegate = that.eventDelegate;
|
||||
}
|
||||
|
||||
private void onCheckForIndexes(MongoPersistentEntity<?> entity, Consumer<Throwable> subscriptionExceptionHandler) {
|
||||
@@ -339,12 +342,25 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.readPreference = readPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure whether lifecycle events such as {@link AfterLoadEvent}, {@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 4.0
|
||||
* @see MongoMappingEvent
|
||||
*/
|
||||
public void setEntityLifecycleEventsEnabled(boolean enabled) {
|
||||
this.eventDelegate.setEventsEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
prepareIndexCreator(applicationContext);
|
||||
|
||||
eventPublisher = applicationContext;
|
||||
eventDelegate.setPublisher(eventPublisher);
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
|
||||
@@ -2324,11 +2340,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
|
||||
|
||||
if (eventPublisher != null) {
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
|
||||
eventDelegate.publishEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
|
||||
@@ -1616,6 +1616,21 @@ public class ReactiveMongoTemplateTests {
|
||||
assertThat(saved.get().id).isNotNull();
|
||||
}
|
||||
|
||||
@Test // GH-4107
|
||||
void afterSaveEventCanBeDisabled() {
|
||||
|
||||
AtomicReference<ImmutableVersioned> saved = createAfterSaveReference();
|
||||
ImmutableVersioned source = new ImmutableVersioned();
|
||||
|
||||
template.setEntityLifecycleEventsEnabled(false);
|
||||
template.insertAll(Collections.singleton(new ImmutableVersioned())) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(saved).hasValue(null);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2012
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "4.0")
|
||||
@EnableIfReplicaSetAvailable
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.mongodb.repository.support.MongoRepositoryFactor
|
||||
import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.MongoClient;
|
||||
@@ -161,6 +162,22 @@ public class ApplicationContextEventTests {
|
||||
assertThat(listener.onAfterConvertEvents.get(0).getCollectionName()).isEqualTo(COLLECTION_NAME);
|
||||
}
|
||||
|
||||
@Test // GH-4107
|
||||
@DirtiesContext
|
||||
public void configurationShouldDisableLifecycleEvents() {
|
||||
|
||||
template.setEntityLifecycleEventsEnabled(false);
|
||||
|
||||
PersonPojoStringId entity = new PersonPojoStringId("1", "Text");
|
||||
template.insert(entity);
|
||||
|
||||
template.findOne(query(where("id").is(entity.getId())), PersonPojoStringId.class);
|
||||
|
||||
assertThat(listener.onAfterLoadEvents).isEmpty();
|
||||
assertThat(listener.onBeforeConvertEvents).isEmpty();
|
||||
assertThat(listener.onAfterConvertEvents).isEmpty();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1256
|
||||
public void loadEventsOnAggregation() {
|
||||
|
||||
|
||||
@@ -2537,9 +2537,15 @@ You can get at the MongoDB driver's `MongoDatabase.runCommand( )` method by usin
|
||||
[[mongodb.mapping-usage.events]]
|
||||
== Lifecycle Events
|
||||
|
||||
The MongoDB mapping framework includes several `org.springframework.context.ApplicationEvent` events that your application can respond to by registering special beans in the `ApplicationContext`. Being based on Spring's `ApplicationContext` event infrastructure enables other products, such as Spring Integration, to easily receive these events, as they are a well known eventing mechanism in Spring-based applications.
|
||||
The MongoDB mapping framework includes several `org.springframework.context.ApplicationEvent` events that your application can respond to by registering special beans in the `ApplicationContext`.
|
||||
Being based on Spring's `ApplicationContext` event infrastructure enables other products, such as Spring Integration, to easily receive these events, as they are a well known eventing mechanism in Spring-based applications.
|
||||
|
||||
To intercept an object before it goes through the conversion process (which turns your domain object into a `org.bson.Document`), you can register a subclass of `AbstractMongoEventListener` that overrides the `onBeforeConvert` method. When the event is dispatched, your listener is called and passed the domain object before it goes into the converter. The following example shows how to do so:
|
||||
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:https://docs.spring.io/spring-data/mongodb/docs/{version}/api/org/springframework/data/mongodb/core/MongoTemplate.html#setEntityLifecycleEventsEnabled(boolean)[Template API].
|
||||
|
||||
To intercept an object before it goes through the conversion process (which turns your domain object into a `org.bson.Document`), you can register a subclass of `AbstractMongoEventListener` that overrides the `onBeforeConvert` method.
|
||||
When the event is dispatched, your listener is called and passed the domain object before it goes into the converter.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
|
||||
Reference in New Issue
Block a user