From 3ee0e1e7ec3450f1dafb9774aa48b8df01fe24d7 Mon Sep 17 00:00:00 2001 From: SUJAY Date: Sun, 14 Jun 2026 19:43:00 +0530 Subject: [PATCH] GH-5166 - Apply BeforeConvertCallback result in MongoTemplate.replace() Signed-off-by: SUJAY --- .../data/mongodb/core/MongoTemplate.java | 5 ++- .../mongodb/core/ReactiveMongoTemplate.java | 29 ++++++++------ .../data/mongodb/core/MongoTemplateTests.java | 40 +++++++++++++++++++ .../core/ReactiveMongoTemplateTests.java | 36 +++++++++++++++++ 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 7b1a5181e4..f110d35d99 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -186,6 +186,7 @@ * @author Michael Krog * @author Jakub Zurawa * @author Florian Lüdiger + * @author Sujay HK */ public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider, SearchIndexOperationsProvider, ReadPreferenceAware { @@ -2184,7 +2185,7 @@ public UpdateResult replace(Query query, T replacement, ReplaceOptions optio } protected UpdateResult replace(Query query, Class entityType, T replacement, ReplaceOptions options, - String collectionName) { + String collectionName) { Assert.notNull(query, "Query must not be null"); Assert.notNull(replacement, "Replacement must not be null"); @@ -2195,10 +2196,10 @@ protected UpdateResult replace(Query query, Class entityType, T replac Assert.isTrue(query.getLimit() <= 1, "Query must not define a limit other than 1 ore none"); Assert.isTrue(query.getSkip() <= 0, "Query must not define skip"); + replacement = maybeCallBeforeConvert(replacement, collectionName); UpdateContext updateContext = queryOperations.replaceSingleContext(query, operations.forEntity(replacement).toMappedDocument(this.mongoConverter), options.isUpsert()); - replacement = maybeCallBeforeConvert(replacement, collectionName); Document mappedReplacement = updateContext.getMappedUpdate(mappingContext.getPersistentEntity(entityType)); maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName)); replacement = maybeCallBeforeSave(replacement, mappedReplacement, collectionName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 8c879a192d..406eaa3c93 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -194,6 +194,7 @@ * @author Yadhukrishna S Pai * @author Florian Lüdiger * @author Kyuhong Han + * @author Sujay HK * @since 2.0 */ public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware { @@ -2095,25 +2096,29 @@ public Mono replace(Query query, T replacement, ReplaceOptions } protected Mono replace(Query query, Class entityType, T replacement, ReplaceOptions options, - String collectionName) { + String collectionName) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityType); - UpdateContext updateContext = queryOperations.replaceSingleContext(query, - operations.forEntity(replacement).toMappedDocument(this.mongoConverter), options.isUpsert()); - return createMono(collectionName, collection -> { + return maybeCallBeforeConvert(replacement, collectionName).flatMap(converted -> { + + UpdateContext updateContext = queryOperations.replaceSingleContext(query, + operations.forEntity(converted).toMappedDocument(this.mongoConverter), options.isUpsert()); - Document mappedUpdate = updateContext.getMappedUpdate(entity); + return createMono(collectionName, collection -> { - MongoAction action = new MongoAction(writeConcern, MongoActionOperation.REPLACE, collectionName, entityType, - mappedUpdate, updateContext.getQueryObject()); + Document mappedUpdate = updateContext.getMappedUpdate(entity); - MongoCollection collectionToUse = createCollectionPreparer(query, action).prepare(collection); + MongoAction action = new MongoAction(writeConcern, MongoActionOperation.REPLACE, collectionName, entityType, + mappedUpdate, updateContext.getQueryObject()); - return collectionToUse.replaceOne(updateContext.getMappedQuery(entity), mappedUpdate, - updateContext.getReplaceOptions(entity, it -> { - it.upsert(options.isUpsert()); - })); + MongoCollection collectionToUse = createCollectionPreparer(query, action).prepare(collection); + + return collectionToUse.replaceOne(updateContext.getMappedQuery(entity), mappedUpdate, + updateContext.getReplaceOptions(entity, it -> { + it.upsert(options.isUpsert()); + })); + }); }); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index fd60ab6aa8..db02f046a3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -73,6 +73,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; import org.springframework.data.mongodb.MongoDatabaseFactory; @@ -95,8 +96,10 @@ import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.FieldName.Type; import org.springframework.data.mongodb.core.mapping.MongoId; +import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; +import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; import org.springframework.data.mongodb.core.query.BasicQuery; @@ -144,6 +147,7 @@ * @author duozhilin * @author Jakub Zurawa * @author Florian Lüdiger + * @author Sujay HK */ public class MongoTemplateTests { @@ -4099,6 +4103,29 @@ void readsMapWithDotInKey() { assertThat(loaded.mapValue).isEqualTo(sourceMap); } + @Test // GH-5166 + void beforeConvertCallbackIsAppliedOnReplace() { + + PersonWithConvertedName person = new PersonWithConvertedName("sujay"); + template.insert(person); + + // Register a BeforeConvertCallback that uppercases the name + template.setEntityCallbacks(EntityCallbacks.create( + (BeforeConvertCallback) (entity, collection) -> { + entity.name = entity.name.toUpperCase(); + return entity; + } + )); + + PersonWithConvertedName replacement = new PersonWithConvertedName("sujay"); + replacement.id = person.id; + + template.replace(query(where("id").is(person.id)), replacement); + + PersonWithConvertedName found = template.findById(person.id, PersonWithConvertedName.class); + assertThat(found.name).isEqualTo("SUJAY"); // will FAIL before the fix + } + private AtomicReference createAfterSaveReference() { AtomicReference saved = new AtomicReference<>(); @@ -4113,6 +4140,17 @@ public void onAfterSave(AfterSaveEvent event) { return saved; } + @org.springframework.data.mongodb.core.mapping.Document + static class PersonWithConvertedName { + + @Id String id; + String name; + + PersonWithConvertedName(String name) { + this.name = name; + } + } + static class TypeWithNumbers { @Id String id; @@ -4813,6 +4851,7 @@ public void onBeforeConvert(BeforeConvertEvent e person.setId(UUID.randomUUID()); } + } public static class Message { @@ -5097,4 +5136,5 @@ public int hashCode() { return Objects.hash(id, value, mapValue); } } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java index b42555b14d..aa3a8d31e5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java @@ -71,6 +71,8 @@ import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; +import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback; +import org.springframework.data.mapping.callback.ReactiveEntityCallbacks; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; @@ -89,6 +91,7 @@ * * @author Mark Paluch * @author Christoph Strobl + * @author Sujay HK */ public class ReactiveMongoTemplateTests { @@ -2101,6 +2104,28 @@ public void doesNotFailOnInsertForEntityWithNonAutogeneratableId3() { template.save(source).as(StepVerifier::create).expectNextCount(1).verifyComplete(); } + @Test // GH-5166 + void beforeConvertCallbackIsAppliedOnReplace() { + + PersonWithConvertedName person = new PersonWithConvertedName("sujay"); + template.insert(person).block(); + + template.setEntityCallbacks(ReactiveEntityCallbacks.create( + (ReactiveBeforeConvertCallback) (entity, collection) -> { + entity.name = entity.name.toUpperCase(); + return Mono.just(entity); + } + )); + + PersonWithConvertedName replacement = new PersonWithConvertedName("sujay"); + replacement.id = person.id; + + template.replace(query(where("id").is(person.id)), replacement).block(); + + PersonWithConvertedName found = template.findById(person.id, PersonWithConvertedName.class).block(); + assertThat(found.name).isEqualTo("SUJAY"); + } + static class NonAutogeneratableId { @Id IdObject id; @@ -2136,6 +2161,17 @@ public void setVersion(int version) { } } + @org.springframework.data.mongodb.core.mapping.Document + static class PersonWithConvertedName { + + @Id String id; + String name; + + PersonWithConvertedName(String name) { + this.name = name; + } + } + record IdObject(String value, long timestamp) { }