Spring Data MongoDB迁移AWS DocumentDB后@DBRef解析失败求助
问题背景
我有一个集成MongoDB的Spring WebFlux应用,实体类使用@DBRef注解,在Spring Boot 2.4.4版本下,仅添加以下依赖并配置spring.data.mongodb.uri时,@DBRef解析功能正常:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
迁移到AWS DocumentDB后,手动编写了数据库配置类来建立反应式与非反应式连接,操作带@DBRef的实体时抛出UnsupportedOperationException,提示"DBRef resolution is not supported!"。
手动配置类代码:
@Slf4j @Configuration public class MongoConfiguration { private static final String SSL_CERTIFICATE = "sslCertificate"; private static final String KEY_STORE_FILE_PREFIX = "sys-connect-via-ssl-test-cacerts"; private static final String KEY_STORE_FILE_SUFFIX = ".jks"; private static final String DEFAULT_KEY_STORE_KEYPW = "abcd"; @Value("${mongodb.uri}") private String mongoURI; @Value("${default.dbname}") private String dbName; @Value("${default.certificateName}") private String defaultCertificatePath; @Bean public ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, KeyManagementException { MongoClientSettings.Builder builder = MongoClientSettings.builder(); SslSettings.Builder sslBuilder = SslSettings.builder(); X509Certificate caCert = createCertificate(); File keyStoreFile = File.createTempFile(KEY_STORE_FILE_PREFIX, KEY_STORE_FILE_SUFFIX); FileOutputStream fos = new FileOutputStream(keyStoreFile.getPath()); TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); keyStore.setCertificateEntry("caCert", caCert); keyStore.store(fos, DEFAULT_KEY_STORE_KEYPW.toCharArray()); trustManagerFactory.init(keyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); sslBuilder.invalidHostNameAllowed(true); sslBuilder.enabled(true); sslBuilder.context(sslContext); SslSettings sslsettings = sslBuilder.build(); builder.applyConnectionString(new ConnectionString( mongoURI)); MongoClientSettings settings = builder .applyToSslSettings(builder1 -> builder1.applySettings(sslsettings)).build(); return new SimpleReactiveMongoDatabaseFactory(MongoClients.create(settings), dbName); } @Bean public MongoDatabaseFactory mongoDatabaseFactory() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, KeyManagementException { MongoClientSettings.Builder builder = MongoClientSettings.builder(); SslSettings.Builder sslBuilder = SslSettings.builder(); X509Certificate caCert = createCertificate(); File keyStoreFile = File.createTempFile(KEY_STORE_FILE_PREFIX, KEY_STORE_FILE_SUFFIX); FileOutputStream fos = new FileOutputStream(keyStoreFile.getPath()); TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); keyStore.setCertificateEntry("caCert", caCert); keyStore.store(fos, DEFAULT_KEY_STORE_KEYPW.toCharArray()); trustManagerFactory.init(keyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); sslBuilder.invalidHostNameAllowed(true); sslBuilder.enabled(true); sslBuilder.context(sslContext); SslSettings sslsettings = sslBuilder.build(); builder.applyConnectionString(new ConnectionString( mongoURI)); MongoClientSettings settings = builder .applyToSslSettings(builder1 -> builder1.applySettings(sslsettings)).build(); return new SimpleMongoClientDatabaseFactory(com.mongodb.client.MongoClients.create(settings), dbName); } @Bean public ReactiveMongoTemplate reactiveMongoTemplate() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory()); } @Primary @Bean public MongoTemplate mongoTemplate() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { return new MongoTemplate(mongoDatabaseFactory()); } private X509Certificate createCertificate() throws CertificateException, MalformedURLException { String sslCertificate = System.getProperty(SSL_CERTIFICATE); if (StringUtils.isEmpty(sslCertificate)) { sslCertificate = defaultCertificatePath; } CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); URL url = new File(sslCertificate).toURI().toURL(); try (InputStream certInputStream = url.openStream()) { return (X509Certificate) certFactory.generateCertificate(certInputStream); } catch (IOException e) { log.error(e.getMessage()); return null; } } }
问题原因
手动配置时直接实例化ReactiveMongoTemplate和MongoTemplate,没有初始化Spring Data MongoDB用于解析@DBRef的核心组件——MappingMongoConverter(及对应的DefaultDbRefResolver)。Spring Boot自动配置会自动创建并关联这些组件,包括MongoMappingContext来处理实体映射和DBRef解析,而手动创建模板时默认使用的转换器不支持DBRef解析,导致抛出异常。
解决方案
方案一:复用Spring自动配置组件,手动配置支持DBRef的模板
修改配置类中的reactiveMongoTemplate和mongoTemplate Bean,手动创建支持DBRef解析的MappingMongoConverter:
// 反应式模板配置 @Bean public ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoMappingContext mappingContext, MongoCustomConversions customConversions) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { // 创建DBRef解析器,关联数据库工厂 DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(reactiveMongoDatabaseFactory()); // 创建映射转换器,关联解析器和映射上下文 MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); // 设置自定义转换规则 converter.setCustomConversions(customConversions); // 初始化转换器 converter.afterPropertiesSet(); return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory(), converter); } // 非反应式模板配置 @Primary @Bean public MongoTemplate mongoTemplate(MongoMappingContext mappingContext, MongoCustomConversions customConversions) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, KeyManagementException { DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory()); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); converter.setCustomConversions(customConversions); converter.afterPropertiesSet(); return new MongoTemplate(mongoDatabaseFactory(), converter); }
方案二:简化配置,复用Spring Boot自动配置(推荐)
不需要手动创建ReactiveMongoDatabaseFactory、MongoDatabaseFactory、ReactiveMongoTemplate和MongoTemplate,仅配置MongoClientSettings Bean,让Spring Boot自动完成其余配置:
修改配置类,只保留mongoClientSettings和createCertificate方法:
@Slf4j @Configuration public class MongoConfiguration { private static final String SSL_CERTIFICATE = "sslCertificate"; private static final String KEY_STORE_FILE_PREFIX = "sys-connect-via-ssl-test-cacerts"; private static final String KEY_STORE_FILE_SUFFIX = ".jks"; private static final String DEFAULT_KEY_STORE_KEYPW = "abcd"; @Value("${mongodb.uri}") private String mongoURI; @Value("${default.certificateName}") private String defaultCertificatePath; @Bean public MongoClientSettings mongoClientSettings() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, KeyManagementException { MongoClientSettings.Builder builder = MongoClientSettings.builder(); SslSettings.Builder sslBuilder = SslSettings.builder(); X509Certificate caCert = createCertificate(); File keyStoreFile = File.createTempFile(KEY_STORE_FILE_PREFIX, KEY_STORE_FILE_SUFFIX); FileOutputStream fos = new FileOutputStream(keyStoreFile.getPath()); TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); keyStore.setCertificateEntry("caCert", caCert); keyStore.store(fos, DEFAULT_KEY_STORE_KEYPW.toCharArray()); trustManagerFactory.init(keyStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); sslBuilder.invalidHostNameAllowed(true); sslBuilder.enabled(true); sslBuilder.context(sslContext); SslSettings sslsettings = sslBuilder.build(); builder.applyConnectionString(new ConnectionString(mongoURI)); return builder .applyToSslSettings(builder1 -> builder1.applySettings(sslsettings)).build(); } private X509Certificate createCertificate() throws CertificateException, MalformedURLException { String sslCertificate = System.getProperty(SSL_CERTIFICATE); if (StringUtils.isEmpty(sslCertificate)) { sslCertificate = defaultCertificatePath; } CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); URL url = new File(sslCertificate).toURI().toURL(); try (InputStream certInputStream = url.openStream()) { return (X509Certificate) certFactory.generateCertificate(certInputStream); } catch (IOException e) { log.error(e.getMessage()); return null; } } }
同时在配置文件中添加数据库名称配置:
spring.data.mongodb.database=${default.dbname}
这样Spring Boot会自动使用自定义的MongoClientSettings创建连接,并自动配置支持DBRef解析的ReactiveMongoTemplate和MongoTemplate,无需手动处理组件初始化。
内容的提问来源于stack exchange,提问作者techmagister

