Spring Data MongoDB问题汇总

2022-01-12 12:26:40 浏览数 (1)

前言

Spring Data除了常用的JPA(Hibernate)关系型数据库的模块外,还有其他用于非关系型数据库的数据交互模块:比如Redis、MongoDB、Elasticsearch等。

用法和JPA模块类似,都需要定义对应的POJO、Repository,同时也提供了对应的数据库工具模板类:如RedisTemplate、MongoTemplate等。

本文基于以下版本:

1 2 3 4 5 6

<!-- 对应的是4.1.1版本的MongoDB --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>2.4.0</version> </dependency>

这是MongoDB官网用户手册的翻译文档仓库:MongoDB-4.2-Manual

忽略某个字段

和JPA-Hibernate类似,使用@Transient即可。注意不能使用javax.persistence.Transient,这个是JPA规范的注解,对Spring Data MongoDB无效,需要使用org.springframework.data.annotation.Transient

移除_class字段

Spring Data在查询MongoDB时会自动添加_class字段,可以用以下方式移除:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

@Configuration public class MongoDBConfig { @Bean public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context, BeanFactory beanFactory) { DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context); try { mappingConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class)); } catch (NoSuchBeanDefinitionException ignore) { } // 取消_class字段 mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null)); return mappingConverter; } }

不支持ZonedDateTime类型

MongoDB不支持ZonedDateTime,因此在读取和写入时需要转换为java.util.Date或LocalDateTime类型:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

@Configuration public class MongoDBConfig { @Autowired MongoDbFactory mongoDbFactory; @Bean public MongoTemplate mongoTemplate() throws UnknownHostException { MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext()); converter.setCustomConversions(customConversions()); converter.afterPropertiesSet(); return new MongoTemplate(mongoDbFactory, converter); } public MongoCustomConversions customConversions() { List<Converter<?, ?>> converters = new ArrayList<>(); converters.add(DateToZonedDateTimeConverter.INSTANCE); converters.add(ZonedDateTimeToDateConverter.INSTANCE); return new MongoCustomConversions(converters); } @ReadingConverter enum DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> { INSTANCE; public ZonedDateTime convert(Date source) { return source == null ? null : ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault()); } } @WritingConverter enum ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, LocalDateTime> { INSTANCE; public LocalDateTime convert(ZonedDateTime source) { return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault()); } } }

The bean ‘xxx’, defined in null, could not be registered.

当同时使用了多个Spring Data模块时,比如混用了Spring Data JPA和Spring Data MongoDB时就会报这种错:

1 2 3 4 5 6 7

Description: The bean 'itemMongoRepository', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

原因很简单,这些Spring Data模块属于不同的jar,但用的是同一个接口,Spring在运行时不知道当前的bean是绑定的JPA的,还是MongoDB或者Elasticsearch的库。

此时需要使用注解来声明不同模块对应的包路径,以此区分开这些Repository的bean:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

@Configuration @EnableMongoRepositories(basePackages = "test.repository.mongodb") public class MongoConfig { } @Configuration @EnableJpaRepositories(basePackages = "test.repository.jpa") public class EntityConfig { } @Configuration @EnableElasticsearchRepositories(basePackages = "test.repository.es") public class ElasticSearchConfig { }

整合多个数据库

现在有两个不同的功能模块,各自对应一个MongoDB,此时需要配置两个不同的数据库配置,并指定不同的MongoTemplate,然后通过调用不同的MongoTemplate来操作不同的MongoDB。

比如在配置文件中有如下两个数据库:

1 2 3 4

## Default MongoDB database spring.data.mongodb.primary.uri=mongodb://localhost:27017/db1 ## Secondary MongoDB database spring.data.mongodb.secondary.uri=mongodb://localhost:27017/db2

此时定义两个MongoConfig的bean,各自对应上述两个不同的数据库。由于定义重复了相同类型的bean对象,需要用@Primary来指明默认注入哪个bean对象。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

@Configuration @EnableMongoRepositories(basePackages = "test.repository.mongodb.primary", mongoTemplateRef = "primaryMongoTemplate") public class PrimaryMongoConfig { private static final String ENTITY_MONGODB_URL = "spring.data.mongodb.primary.uri"; @Bean(name = "primaryMongoTemplate") @Primary public MongoTemplate mongoTemplate(Environment env) { return new MongoTemplate(mongoFactory(env)); } @Bean(name = "primaryMongoFactory") @Primary public MongoDatabaseFactory mongoFactory(Environment env) { return new SimpleMongoClientDatabaseFactory(env.getProperty(ENTITY_MONGODB_URL)); } } @Configuration @EnableMongoRepositories(basePackages = "test.repository.mongodb.secondary", mongoTemplateRef = "secondaryMongoTemplate") public class SecondaryMongoConfig { private static final String ENTITY_MONGODB_URL = "spring.data.mongodb.secondary.uri"; @Bean(name = "secondaryMongoTemplate") public MongoTemplate mongoTemplate(Environment env) { return new MongoTemplate(mongoFactory(env)); } @Bean(name = "secondaryMongoFactory") public MongoDatabaseFactory mongoFactory(Environment env) { return new SimpleMongoClientDatabaseFactory(env.getProperty(ENTITY_MONGODB_URL)); } }

使用SPEL表达式来动态获取集合的值

Spring Data MongoDB的POJO需要用@Document(collection = "xxx")来指明映射数据库的某个集合(相当于JPA里的@Table(name = "xxx")),但有时不想要直接写死集合名字,可以用SPEL表达式来实现:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

// 将集合名字作为一个变量,存到一个bean对象中 // @Data是lombok的注解,用来自动生成setter和getter方法 @Bean(name = "entityMongoCollection") public EntityMongoCollection getEntityMongoCollection() { return new EntityMongoCollection("myCollection"); } @Data @AllArgsConstructor public class EntityMongoCollection { private String collectionName; } // 用SPEL表达式来获取这个bean里的变量值 @Data @Document(collection = "#{@entityMongoCollection.getCollectionName()}") public class EntityMongo implements Serializable { @Id @Field("id") private String id; @Field("ref_no") private String refNo; @Field("version") private Interger version; }

查询数据库

可以用官方提供的MongoTemplate来查询数据,也可以使用MongoRepository和@Query注解来实现:

1 2 3 4 5 6

public interface EntityMongoRepository extends MongoRepository<EntityMongo, String> { @Query("{'refNo':?0 , 'version':?1}") List<EntityMongo> findByRefNoAndVersion(final String refNo, final String version); }

如果只需要查询部分字段,可以用MongoTemplate的Projection来实现:

1 2 3 4 5 6 7

String collectionName = "test"; Query query = new Query(); query.fields().include("ref_no"); // 想查询的字段 query.fields().exclude("version"); // 不想查询的字段 final List<EntityMongo> list = mongoTemplate.find(query, EntityMongo.class, collectionName);

参考链接

  • Spring Data Mongo中@Transient无效的解决办法
  • Spring 框架 MongoDB 去掉_class属性字段
  • Mongo Date Custom Converter not being called when save method of mongo repository is invoked
  • The bean ‘xxxxx’, defined in null, could not be registered. A bean with that name has already …
  • SpringBoot整合MongoDB多数据源
  • springboot整合MongoDB
  • Cannot resolve bean in SpEL for Spring Data MongoDB collection name

注意

本文最后更新于 September 27, 2021,文中内容可能已过时,请谨慎使用。

0 人点赞