MongoDB是一款开源的文档型数据库。

NoSQL可以分为四大块:

  • K-V类型:redis、MemberCached
  • 文档型:MongoDB、Couchbase
  • 列存储:Cassandra、HBase
  • 图存储:Neo4j

启动MongoDB服务

通过Docker引擎启动MongoDB服务。这里有MongoDB容器的相关说明

获取镜像

docker pull mongo

执行如上命令获取最新的mongo镜像。

mongotemplate 指定表 mongotemplate updatefirst_spring


mongotemplate 指定表 mongotemplate updatefirst_spring_02

运行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

mongotemplate 指定表 mongotemplate updatefirst_数据库_03

登陆到MongoDB容器中

docker exec -it mongo bash

通过shell连接MongoDB

mongo -u admin -p admin

mongotemplate 指定表 mongotemplate updatefirst_数据库_04

初始化MongoDB的库及权限

为项目创建对应的数据库,以及有读写该库权限的用户。

创建库

use springbucks;

使用use命令用来选择数据库,当我们在该库上做操作时,MongoDB就会自动为我们创建数据库。

mongotemplate 指定表 mongotemplate updatefirst_mongotemplate 指定表_05

创建用户

db.createUser(
    {
      user: "springbucks",
      pwd: "springbucks",
      roles: [
         { role: "readWrite", db: "springbucks" }
      ]
} )

通过db.createUser来创建用户,并指定用户名密码以及该用户的权限。

mongotemplate 指定表 mongotemplate updatefirst_spring_06

查看用户

show users

通过show users命令可以看到当前数据库中创建的账号信息。

mongotemplate 指定表 mongotemplate updatefirst_spring_07

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同样也是在自动配置模块中,如下图所示:

mongotemplate 指定表 mongotemplate updatefirst_数据库_08


可以看到并排的包还有jpa、redis、couchbase等。

MongoDataAutoConfiguration

从左侧类结构可以看出,MongoDataAutoConfiguration这个类方法不多。其中整个流程我们会用到的有如下这几个

  • mongoTemplate()
  • mappingMongoConverter()
  • mongoCustomConversions()

mongotemplate 指定表 mongotemplate updatefirst_spring_09

MongoTemplate

既然MongoTemplate是入口,那就先找到MongoTemplate这个Bean的声明地方。该方法需要一个MongoConverter

@Bean
    @ConditionalOnMissingBean
    public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
        return new MongoTemplate(mongoDbFactory, converter);
    }

MongoConverter

MongoConverter看名字像是和转换有关的一个东西,如下图是其类关系图,可以看到其最后的实现类是MappingMongoConverter这个类。

mongotemplate 指定表 mongotemplate updatefirst_spring_10


如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数据库中查看结果:

mongotemplate 指定表 mongotemplate updatefirst_mongotemplate 指定表_11

更新数据

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数据库中查看结果:

mongotemplate 指定表 mongotemplate updatefirst_docker_12

知行合一