前言
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);
|
参考链接
警告
本文最后更新于 September 27, 2021,文中内容可能已过时,请谨慎使用。