Skip to content

Commit d130984

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 #4107
1 parent f5378bf commit d130984

File tree

6 files changed

+135
-12
lines changed

6 files changed

+135
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.mongodb.core;
17+
18+
import org.springframework.context.ApplicationEventPublisher;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* Delegate class to encapsulate lifecycle event configuration and publishing.
23+
*
24+
* @author Mark Paluch
25+
* @since 4.0
26+
* @see ApplicationEventPublisher
27+
*/
28+
class EntityLifecycleEventDelegate {
29+
30+
private @Nullable ApplicationEventPublisher publisher;
31+
private boolean eventsEnabled = true;
32+
33+
public void setPublisher(@Nullable ApplicationEventPublisher publisher) {
34+
this.publisher = publisher;
35+
}
36+
37+
public boolean isEventsEnabled() {
38+
return eventsEnabled;
39+
}
40+
41+
public void setEventsEnabled(boolean eventsEnabled) {
42+
this.eventsEnabled = eventsEnabled;
43+
}
44+
45+
/**
46+
* Publish an application event if event publishing is enabled.
47+
*
48+
* @param event the application event.
49+
*/
50+
public void publishEvent(Object event) {
51+
52+
if (canPublishEvent()) {
53+
publisher.publishEvent(event);
54+
}
55+
}
56+
57+
private boolean canPublishEvent() {
58+
return publisher != null && eventsEnabled;
59+
}
60+
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.*;
2424
import java.util.concurrent.TimeUnit;
2525
import java.util.function.BiPredicate;
26+
import java.util.function.Supplier;
2627
import java.util.stream.Collectors;
2728
import java.util.stream.Stream;
2829

@@ -174,6 +175,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
174175
private final EntityOperations operations;
175176
private final PropertyOperations propertyOperations;
176177
private final QueryOperations queryOperations;
178+
private final EntityLifecycleEventDelegate eventDelegate;
177179

178180
private @Nullable WriteConcern writeConcern;
179181
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
@@ -228,6 +230,7 @@ public MongoTemplate(MongoDatabaseFactory mongoDbFactory, @Nullable MongoConvert
228230
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
229231
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
230232
mongoDbFactory);
233+
this.eventDelegate = new EntityLifecycleEventDelegate();
231234

232235
// We always have a mapping context in the converter, whether it's a simple one or not
233236
mappingContext = this.mongoConverter.getMappingContext();
@@ -266,6 +269,7 @@ private MongoTemplate(MongoDatabaseFactory dbFactory, MongoTemplate that) {
266269
this.operations = that.operations;
267270
this.propertyOperations = that.propertyOperations;
268271
this.queryOperations = that.queryOperations;
272+
this.eventDelegate = that.eventDelegate;
269273
}
270274

271275
/**
@@ -308,12 +312,25 @@ public void setReadPreference(@Nullable ReadPreference readPreference) {
308312
this.readPreference = readPreference;
309313
}
310314

315+
/**
316+
* Configure whether lifecycle events such as {@link AfterLoadEvent}, {@link BeforeSaveEvent}, etc. should be
317+
* published or whether emission should be suppressed. Enabled by default.
318+
*
319+
* @param enabled {@code true} to enable entity lifecycle events; {@code false} to disable entity lifecycle events.
320+
* @since 4.0
321+
* @see MongoMappingEvent
322+
*/
323+
public void setEntityLifecycleEventsEnabled(boolean enabled) {
324+
this.eventDelegate.setEventsEnabled(enabled);
325+
}
326+
311327
@Override
312328
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
313329

314330
prepareIndexCreator(applicationContext);
315331

316332
eventPublisher = applicationContext;
333+
eventDelegate.setPublisher(eventPublisher);
317334

318335
if (entityCallbacks == null) {
319336
setEntityCallbacks(EntityCallbacks.create(applicationContext));
@@ -2168,11 +2185,7 @@ protected MongoDatabase prepareDatabase(MongoDatabase database) {
21682185
}
21692186

21702187
protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
2171-
2172-
if (eventPublisher != null) {
2173-
eventPublisher.publishEvent(event);
2174-
}
2175-
2188+
eventDelegate.publishEvent(event);
21762189
return event;
21772190
}
21782191

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

+17-5
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
185185
private final EntityOperations operations;
186186
private final PropertyOperations propertyOperations;
187187
private final QueryOperations queryOperations;
188+
private final EntityLifecycleEventDelegate eventDelegate;
188189

189190
private @Nullable WriteConcern writeConcern;
190191
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
@@ -256,6 +257,7 @@ public ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory,
256257
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
257258
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
258259
mongoDatabaseFactory);
260+
this.eventDelegate = new EntityLifecycleEventDelegate();
259261

260262
// We create indexes based on mapping events
261263
if (this.mappingContext instanceof MongoMappingContext) {
@@ -287,6 +289,7 @@ private ReactiveMongoTemplate(ReactiveMongoDatabaseFactory dbFactory, ReactiveMo
287289
this.propertyOperations = that.propertyOperations;
288290
this.sessionSynchronization = that.sessionSynchronization;
289291
this.queryOperations = that.queryOperations;
292+
this.eventDelegate = that.eventDelegate;
290293
}
291294

292295
private void onCheckForIndexes(MongoPersistentEntity<?> entity, Consumer<Throwable> subscriptionExceptionHandler) {
@@ -339,12 +342,25 @@ public void setReadPreference(ReadPreference readPreference) {
339342
this.readPreference = readPreference;
340343
}
341344

345+
/**
346+
* Configure whether lifecycle events such as {@link AfterLoadEvent}, {@link BeforeSaveEvent}, etc. should be
347+
* published or whether emission should be suppressed. Enabled by default.
348+
*
349+
* @param enabled {@code true} to enable entity lifecycle events; {@code false} to disable entity lifecycle events.
350+
* @since 4.0
351+
* @see MongoMappingEvent
352+
*/
353+
public void setEntityLifecycleEventsEnabled(boolean enabled) {
354+
this.eventDelegate.setEventsEnabled(enabled);
355+
}
356+
342357
@Override
343358
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
344359

345360
prepareIndexCreator(applicationContext);
346361

347362
eventPublisher = applicationContext;
363+
eventDelegate.setPublisher(eventPublisher);
348364

349365
if (entityCallbacks == null) {
350366
setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
@@ -2324,11 +2340,7 @@ private <T> Mono<T> doFindAndReplace(String collectionName, Document mappedQuery
23242340
}
23252341

23262342
protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
2327-
2328-
if (eventPublisher != null) {
2329-
eventPublisher.publishEvent(event);
2330-
}
2331-
2343+
eventDelegate.publishEvent(event);
23322344
return event;
23332345
}
23342346

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java

+15
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,21 @@ void afterSaveEventContainsSavedObjectUsingInsertAll() {
16161616
assertThat(saved.get().id).isNotNull();
16171617
}
16181618

1619+
@Test // GH-4107
1620+
void afterSaveEventCanBeDisabled() {
1621+
1622+
AtomicReference<ImmutableVersioned> saved = createAfterSaveReference();
1623+
ImmutableVersioned source = new ImmutableVersioned();
1624+
1625+
template.setEntityLifecycleEventsEnabled(false);
1626+
template.insertAll(Collections.singleton(new ImmutableVersioned())) //
1627+
.as(StepVerifier::create) //
1628+
.expectNextCount(1) //
1629+
.verifyComplete();
1630+
1631+
assertThat(saved).hasValue(null);
1632+
}
1633+
16191634
@Test // DATAMONGO-2012
16201635
@EnableIfMongoServerVersion(isGreaterThanEqual = "4.0")
16211636
@EnableIfReplicaSetAvailable

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java

+17
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor;
4848
import org.springframework.data.mongodb.test.util.Client;
4949
import org.springframework.data.mongodb.test.util.MongoClientExtension;
50+
import org.springframework.test.annotation.DirtiesContext;
5051

5152
import com.mongodb.WriteConcern;
5253
import com.mongodb.client.MongoClient;
@@ -161,6 +162,22 @@ public void loadAndConvertEvents() {
161162
assertThat(listener.onAfterConvertEvents.get(0).getCollectionName()).isEqualTo(COLLECTION_NAME);
162163
}
163164

165+
@Test // GH-4107
166+
@DirtiesContext
167+
public void configurationShouldDisableLifecycleEvents() {
168+
169+
template.setEntityLifecycleEventsEnabled(false);
170+
171+
PersonPojoStringId entity = new PersonPojoStringId("1", "Text");
172+
template.insert(entity);
173+
174+
template.findOne(query(where("id").is(entity.getId())), PersonPojoStringId.class);
175+
176+
assertThat(listener.onAfterLoadEvents).isEmpty();
177+
assertThat(listener.onBeforeConvertEvents).isEmpty();
178+
assertThat(listener.onAfterConvertEvents).isEmpty();
179+
}
180+
164181
@Test // DATAMONGO-1256
165182
public void loadEventsOnAggregation() {
166183

Diff for: src/main/asciidoc/reference/mongodb.adoc

+8-2
Original file line numberDiff line numberDiff line change
@@ -2537,9 +2537,15 @@ You can get at the MongoDB driver's `MongoDatabase.runCommand( )` method by usin
25372537
[[mongodb.mapping-usage.events]]
25382538
== Lifecycle Events
25392539

2540-
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.
2540+
The MongoDB mapping framework includes several `org.springframework.context.ApplicationEvent` events that your application can respond to by registering special beans in the `ApplicationContext`.
2541+
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.
25412542

2542-
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:
2543+
Entity lifecycle events can be costly and you may notice a change in the performance profile when loading large result sets.
2544+
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].
2545+
2546+
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.
2547+
When the event is dispatched, your listener is called and passed the domain object before it goes into the converter.
2548+
The following example shows how to do so:
25432549

25442550
====
25452551
[source,java]

0 commit comments

Comments
 (0)