springspring-bootspring-data-mongodbspring-data-mongodb-reactive

CustomConverter do not get applied for ReactiveMongoTemplate


Hello Spring community,

I have a Spring Boot 3 application with a Mongo DB. I use Spring data mongo reactive to communicate with the database and that works great apart from this issue.

I have a property that Id like to capsulate with another class but I need the same database structure for compatibility.

@Data
@Builder
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class TestType {
    String value;
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Document(collection = "entity")
@AllArgsConstructor
@NoArgsConstructor
@FieldNameConstants
public class Entity {

    public static final String COLLECTION_NAME = "entity";

    @Id
    UUID id;

    @Builder.Default
    @Field(name = Fields.data)
    List<SomeOtherData> data = new ArrayList<>();

    @Builder.Default
    TestType testType = new TestType("payload");
}

If written without converters, this code results in the following document:

{
    "id": someID,
    "data": {},
    "testType":{"value":"payload"}
}

But I need it to look like this:

{
    "id": someID,
    "data": {},
    "testType":"payload"
}

So I added two CustomConverters and added a bean to provide the mongoCustomConversions:

@WritingConverter
public class TestTypeStringConverter implements Converter<TestType, String> {
    @Override
    public String convert(TestType source) {
        return source.getValue();
    }
}
@ReadingConverter
public class StringTestTypeConverter implements Converter<String, TestType> {
    @Override
    public TestType convert(@NonNull String source) {
        return new TestType(source);
    }
}
    @Bean
    public MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(List.of(
                new TestTypeStringConverter(),
                new StringTestTypeConverter()
        ));
    }

I use the mongoTemplate to write my changes to the database:

    private UpdateOneModel<Document> createUpdate(Entity newEntity) {
        var query = new Query().addCriteria(new Criteria(UNDERSCORE_ID).is(newEntity.getId()));
        var updateVehicle = new Update();
        var options = new UpdateOptions();
        options.upsert(true);
        updateVehicle.set(Entity.Fields.data, newEntity.getData());
        updateVehicle.set(Entity.Fields.testType, newEntity.getTestType());
        return new UpdateOneModel<>(query.getQueryObject(), updateVehicle.getUpdateObject(), options);
    }
private Mono<BulkWriteResult> execute(String collectionName, UpdateOneModel<Document> updateModelList) {
        return reactiveMongoTemplate.getCollection(collectionName)
                .flatMap(c -> Mono.from(c.bulkWrite(List.of(updateModelList))));
    }

But still my object gets written as without the converters.

Debugging confirmed that the mongoTemplate contains instances of my converters. Both are listed in the converters and in the writing- and readingPairs respectively. I tried changing the datatypes but that also didnt change anything. At that point I was out of ideas.

What am I doing wrong?

Thank you for your support.


Solution

  • To perform an upsert, you are not utilizing Spring in the proper way. Instead, you are calling the bulkWrite method from the com.mongodb package. The converters that you have correctly defined, are a Spring concept present in SpringData Mongo. I have used the same reactiveMongoTemplate but have employed the upsert method from SpringData Mongo and it works as expected.

    I've made an example commit here: https://github.com/sbernardo/spring-issues-examples/tree/main/sof-questions-77612149

    Regardless, you can still use your current approach with the bulk operation (you will find this code in my repo):

    var entity = new Entity(UUID.fromString("7f6a35fb-a449-4e31-b9c1-7592cc969d83"), new TestType("HelloValue"));
    
    Update update = new Update();
    update.set(Entity.Fields.testType, entity.getTestType());
    update.set("field2", "otherFieldValue");
    
    return reactiveMongoTemplate
            .bulkOps(BulkOperations.BulkMode.UNORDERED, Entity.class)
            .upsert(Query.query(new Criteria(UNDERSCORE_ID).is(entity.getId())), update)
            .execute()
            .block()
            .toString();
    

    I'm using spring-boot 3.2.0 and java 21.