Originally logged issue at https://github.com/spring-projects/spring-data-mongodb/issues/4629 and was referred here.
The project in question uses two relevant dependencies:
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.mongodb:mongodb-driver-reactivestreams'
A test entity with the following @DocumentReference field is used:
@Setter(AccessLevel.NONE)
@ReadOnlyProperty
@DocumentReference(lookup = "{'_id': {$oid: ?#{#self.parentId}}}", lazy = true)
private ParentDocument parent;
The project, in general, uses non-reactive mongo; reactivestreams are pulled in to utilize for changestream registration only. The following field would simply get set to NULL when reading from the (non-reactive) mongoTemplate. This issue only started occurring after updating from 3.1.5 to 3.2.
The issue is due to the reactive configuration being bootstrapped prior to the non-reactive configuration (only after updating to 3.2).
After debugging the issue, I noticed that the MappingMongoConverter being autowired into the MongoTemplate contains a DbRefResolver of NoOpDbRefResolver.
This is occurring because MongoReactiveDataAutoConfiguration is being configured before MongoDatabaseFactoryDependentConfiguration.
Both autoconfiguration classes contain configuration to create a MappingMongoConverter bean:
// in MongoDatabaseFactoryDependentConfiguration
@Bean
@ConditionalOnMissingBean(MongoConverter.class)
MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
mappingConverter.setCustomConversions(conversions);
return mappingConverter;
}
// In MongoReactiveDataAutoConfiguration
@Bean
@ConditionalOnMissingBean(MongoConverter.class)
public MappingMongoConverter mappingMongoConverter(MongoMappingContext context,
MongoCustomConversions conversions) {
MappingMongoConverter mappingConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
mappingConverter.setCustomConversions(conversions);
return mappingConverter;
}
I was able to work around the issue by copy-pasting the non-reactive bean definition above in my application's configuration.
The questions I have are: * should this be considered an issue at all? If so... * should the auto configurations be modified to ensure the MappingMongoConverter for non-reactive is configured first if both autoconfigurations are running? * should the code be changed to create a different mapping mongo converter for the non-reactive template from the reactive template?
Comment From: wilkinsona
I can't reproduce this. From the auto-configuration report, I can see that MongoReactiveDataAutoConfiguration is active:
MongoReactiveDataAutoConfiguration matched:
- @ConditionalOnClass found required classes 'com.mongodb.reactivestreams.client.MongoClient', 'org.springframework.data.mongodb.core.ReactiveMongoTemplate' (OnClassCondition)
- @ConditionalOnBean (types: com.mongodb.reactivestreams.client.MongoClient; SearchStrategy: all) found bean 'reactiveStreamsMongoClient' (OnBeanCondition)
However, the mappingMongoConverter bean is coming from MongoDatabaseFactoryDependentConfiguration:
MongoDatabaseFactoryDependentConfiguration#mappingMongoConverter matched:
- @ConditionalOnMissingBean (types: org.springframework.data.mongodb.core.convert.MongoConverter; SearchStrategy: all) did not find any beans (OnBeanCondition)
As a result, the mappingMongoConverter defined in MongoReactiveDataAutoConfiguration has backed off:
MongoReactiveDataAutoConfiguration#mappingMongoConverter:
Did not match:
- @ConditionalOnMissingBean (types: org.springframework.data.mongodb.core.convert.MongoConverter; SearchStrategy: all) found beans of type 'org.springframework.data.mongodb.core.convert.MongoConverter' mappingMongoConverter (OnBeanCondition)
There must be something else in your app that's changing the auto-configuration ordering. This could be in your application itself or in one of its dependencies.
If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.
Comment From: caspianb
@wilkinsona Thank you for the response!
Since there's no guaranteed ordering between the two mongo auto-configuration classes currently, it's not surprising that it would be a bit pseudorandom.
After creating a very bare project and playing with dependencies for a bit, it looks like adding spring-boot-starter-actuator causes the initialization order to change. I created a demo.zip from the spring initializr and have attached it here.
The log message output from the main method shows the NoOpDbRefResolver is the one being injected in this project.
Comment From: wilkinsona
Since there's no guaranteed ordering between the two mongo auto-configuration classes currently, it's not surprising that it would be a bit pseudorandom.
In the absence of any other ordering information, the classes are ordered alphabetically so MongoDataAutoConfiguration will go before MongoReactiveDataAutoConfiguration. As MongoDataAutoConfiguration imports MongoDatabaseFactoryDependentConfiguration, MongoDatabaseFactoryDependentConfiguration will be processed before MongoReactiveDataAutoConfiguration.
After creating a very bare project and playing with dependencies for a bit, it looks like adding spring-boot-starter-actuator causes the initialization order to change. I created a demo.zip from the spring initializr and have attached it here.
Thank you.
Comment From: wilkinsona
The change in behavior appears to have been introduced in 3.1.8 and 3.2.2. Both 3.1.7 and 3.2.1 behave as 3.1.5 did. I suspect that https://github.com/spring-projects/spring-boot/issues/38904 is the cause but I have yet to confirm that's the case or to figure out why it's only a problem when Actuator's on the classpath.
https://github.com/spring-projects/spring-boot/issues/12407 contains some background on the current arrangement in terms of blocking vs reactive auto-configuration, including this from Mark Paluch:
We lose
@DBRefresolution as we cannot resolve document references with the reactive driver anymore. Reference resolution works only because the blocking driver is on the classpath. This means, thatMappingMongoConvertermust be created with a differentDbRefResolver, depending on which drivers we have on the classpath. If the blocking one is available (or both drivers), thenMongoMappingContextis configured like now. If only the reactive one is configured, then we need to provide a differentDbRefResolver, which isn't available yet.
I don't think we've got this quite right at the moment as the circumstances in which you'll get a DefaultDbRefResolver vs a NoOpDbRefResolver are not as well-defined as those described above.
I think the fix for #38904 has just exposed a bug that's been there for a long time.
@caspianb, while we figure out the right way to fix this, I think you can work around the change in behaviour in 3.1.8 and 3.2.2 by adding the following to application.yaml:
spring:
autoconfigure:
exclude: 'org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration'