MongoDB是一款开源的文档型数据库。
NoSQL可以分为四大块:
- K-V类型:redis、MemberCached
- 文档型:MongoDB、Couchbase
- 列存储:Cassandra、HBase
- 图存储:Neo4j
启动MongoDB服务
通过Docker引擎启动MongoDB服务。这里有MongoDB容器的相关说明。
获取镜像
docker pull mongo
执行如上命令获取最新的mongo镜像。
运行MongoDB镜像
docker run --name mongo -p 27017:27017 -v /Users/lucky/Documents/workspace/docker_mapping_volume/mongo:/data/db -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin -d mongo
- --name mongo:指定容器名称为mongo
- -p 27017:27017:指定宿主机和容器的映射端口,[宿主机端口]:[容器端口]
- -v [宿主机目录]:[容器目录]
- -e 环境变量设置,设置账号、密码
- -d 后台执行
查看运行的镜像
docker ps
登陆到MongoDB容器中
docker exec -it mongo bash
通过shell连接MongoDB
mongo -u admin -p admin
初始化MongoDB的库及权限
为项目创建对应的数据库,以及有读写该库权限的用户。
创建库
use springbucks;
使用use
命令用来选择数据库,当我们在该库上做操作时,MongoDB就会自动为我们创建数据库。
创建用户
db.createUser(
{
user: "springbucks",
pwd: "springbucks",
roles: [
{ role: "readWrite", db: "springbucks" }
]
} )
通过db.createUser来创建用户,并指定用户名密码以及该用户的权限。
查看用户
show users
通过show users
命令可以看到当前数据库中创建的账号信息。
Spring对MongoDB的支持
Spring对MongoDB的支持是通过Spring Data MongoDB这个项目来支持的,Spring Data MongoDB提供了MongoTemplate来对数据做增删改查的操作。Spring Data MongoDB和Spring Data JPA类似,有对应的注解来标识。
注解
- @Document
- @Id
MongoTemplate
- save / remove
- Criteria / Query / Update
使用MongoTemplate操作MongoDB
自定义类型转化类
业务中使用了joda-money
这个类库,我们在取数据的时候就需要将获取到的数据类型Document
进行转换成我们需要的类型Money
。
package com.lucky.spring.converter;
import org.bson.Document;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.Nullable;
/**
* Created by zhangdd on 2020/8/8
* <p>
* 处理将Document 转换成 Money
*/
public class MoneyReaderConverter implements Converter<Document, Money> {
@Nullable
@Override
public Money convert(Document source) {
//取数据
Document money = (Document) source.get("money");
//从获取的数据中取对应的字段
double amount = Double.parseDouble(money.getString("amount"));
String currency = ((Document) money.get("currency")).getString("code");
//将获取的业务字段进行类型转换
return Money.of(CurrencyUnit.of(currency), amount);
}
}
注入转化Bean
类型转换的功能已经做好,这里需要将该功能注入到容器中。
@Bean
public MongoCustomConversions mongoCustomConversions(){
return new MongoCustomConversions(Arrays.asList(new MoneyReaderConverter()));
}
说下思路吧。为什么会想到自定义这个转换Bean。
既然Spring提供了MongoTemplate作为操作入口,那我们就从MongoTemplate开始看起。MongoTemplate同样也是在自动配置模块中,如下图所示:
可以看到并排的包还有jpa、redis、couchbase等。
MongoDataAutoConfiguration
从左侧类结构可以看出,MongoDataAutoConfiguration这个类方法不多。其中整个流程我们会用到的有如下这几个
- mongoTemplate()
- mappingMongoConverter()
- mongoCustomConversions()
MongoTemplate
既然MongoTemplate是入口,那就先找到MongoTemplate这个Bean的声明地方。该方法需要一个MongoConverter
。
@Bean
@ConditionalOnMissingBean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
return new MongoTemplate(mongoDbFactory, converter);
}
MongoConverter
MongoConverter看名字像是和转换有关的一个东西,如下图是其类关系图,可以看到其最后的实现类是MappingMongoConverter
这个类。
如MongoDataAutoConfiguration类中定义的Bean已经实现了MappingMongoConverter
这个类注入。
MappingMongoConverter
@Bean
@ConditionalOnMissingBean({MongoConverter.class})
public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context, MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
mappingConverter.setCustomConversions(conversions);
return mappingConverter;
}
在mappingMongoConverter方法定义中,需要一个MongoCustomConversions参数。
MongoCustomConversions
@Bean
@ConditionalOnMissingBean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.emptyList());
}
可以看到在MongoCustomConversions 这个Bean的定义处,通过@ConditionalOnMissingBean
注解说明:如果我们不自己定义该Bean,就会使用这段代码生成的Bean。同时这个方法里传的是一个空集合。所以 为什么会想到自定义这个转换Bean 的原理就在这里了。Spring中很多Bean都是留了这样的一个入口让我们去可以自定义Bean。
保存查询数据
private void saveFindData() throws InterruptedException {
Coffee espresso = Coffee.builder()
.name("espresso")
.price(Money.of(CurrencyUnit.of("CNY"), 20.0))
.createTime(new Date())
.updateTime(new Date())
.build();
mongoTemplate.save(espresso);
log.info("Coffee {}", espresso);
List<Coffee> list = mongoTemplate.find(
Query.query(Criteria.where("name").is("espresso")), Coffee.class
);
log.info("find {} coffee", list.size());
list.forEach(c->log.info("coffee {}",c));
TimeUnit.SECONDS.sleep(1000);
}
打印结果如下:
2020-08-08 09:56:59.163 INFO 50442 --- [ main] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:4}] to localhost:27017
2020-08-08 09:56:59.232 INFO 50442 --- [ main] com.lucky.spring.Application : Coffee Coffee(id=5f2e066b69ffe1c50ad58e1f, name=espresso, price=CNY 20.00, createTime=Sat Aug 08 09:56:59 CST 2020, updateTime=Sat Aug 08 09:56:59 CST 2020)
2020-08-08 09:56:59.277 INFO 50442 --- [ main] com.lucky.spring.Application : find 1 coffee
2020-08-08 09:56:59.278 INFO 50442 --- [ main] com.lucky.spring.Application : coffee Coffee(id=5f2e066b69ffe1c50ad58e1f, name=espresso, price=CNY 20.00, createTime=Sat Aug 08 09:56:59 CST 2020, updateTime=Sat Aug 08 09:56:59 CST 2020)
在MondoDB数据库中查看结果:
更新数据
private void updateData() {
UpdateResult result = mongoTemplate.updateFirst(Query.query(Criteria.where("name").is("espresso")),
new Update().set("price", Money.ofMajor(CurrencyUnit.of("CNY"), 30)).currentDate("updateTime"),
Coffee.class);
log.info("update result:{}", result.getMatchedCount());
}
在MondoDB数据库中查看结果:
知行合一