补习系列(17)-springboot mongodb 内嵌数据库

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介: 简介前面的文章中,我们介绍了如何在SpringBoot 中使用MongoDB的一些常用技巧。那么,与使用其他数据库如 MySQL 一样,我们应该怎么来做MongoDB的单元测试呢?使用内嵌数据库的好处是不需要依赖于一个外部环境,如果每一次跑单元测试都需要依赖一个稳定的外部环境,那么这样的测试是极不稳定的。

简介

前面的文章中,我们介绍了如何在SpringBoot 中使用MongoDB的一些常用技巧。
那么,与使用其他数据库如 MySQL 一样,我们应该怎么来做MongoDB的单元测试呢?

使用内嵌数据库的好处是不需要依赖于一个外部环境,如果每一次跑单元测试都需要依赖一个稳定的外部环境,那么这样的测试是极不稳定的。
为了更欢快的使用MongoDB,这里提供两种使用内嵌数据库做单元测试的方式。

一、使用 flapdoodle.embed.mongo

开源地址
该组件的大致原理是,在当前环境中自动下载MongoDB并拉起进程,测试后再做关闭。
先演示一遍如何使用:

A. 引入依赖

        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <version>1.50.5</version>
            <scope>test</scope>
        </dependency>

B. 准备测试类

编写一个基础类:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoBoot.class)
@ActiveProfiles("test")
public class BaseEmbededMongoTest {

    private static final Logger logger = LoggerFactory.getLogger(BaseEmbededMongoTest.class);
    protected static final MongodStarter starter = MongodStarter.getDefaultInstance();
    protected static MongodExecutable _mongodExe;
    protected static MongodProcess _mongod;

    // 确保与配置一致
    protected static final String host = "127.0.0.1";
    protected static final int port = 27027;

    @BeforeClass
    public static void setUp() throws Exception {
        _mongodExe = starter.prepare(new MongodConfigBuilder().version(Version.Main.PRODUCTION)
                .net(new Net(host, port, Network.localhostIsIPv6())).build());
        _mongod = _mongodExe.start();

        logger.info("mongod started on {}:{}", host, port);
    }

    @AfterClass
    public static void tearDown() throws Exception {
        _mongod.stop();
        _mongodExe.stop();
    }
}

BaseEmbededMongoTest 实现了:

  • 测试启动前启动MongoDB进程;
  • 测试完成后关闭MongoDB进程;

让业务测试类继承于基础类:

public class BookServiceTest extends BaseEmbededMongoTest{

    @Autowired
    private BookService bookService;

    @Autowired
    private BookRepository bookRepository;
   ...

C. 完善配置

为了避免冲突,需要关闭EmbeddedMongoAutoConfiguration

@SpringBootApplication
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class})
public class BootSampleMongo {
...

最后一步,为了让业务代码能连接到自启动的MongoDB,需要做对应的配置:

在src/test/resources目录中编辑 application-test.properties

spring.data.mongodb.host=localhost
spring.data.mongodb.port=27027
spring.data.mongodb.database=test

D. 启动测试

执行业务测试类,可以看到一系列输出:

//下载
Download PRODUCTION:Windows:B64 START
Download PRODUCTION:Windows:B64 DownloadSize: 147911698
Download PRODUCTION:Windows:B64 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11%
...
//启动继承
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten] db version v3.2.1
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten] git version: a14d55980c2cdc565d4704a7e3ad37e4e535c1b2
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten] allocator: tcmalloc
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten] modules: none
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten] build environment:
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten]     distmod: 2008plus
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten]     distarch: x86_64
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL  [initandlisten]     target_arch: x86_64
...
[mongod output] 2019-03-02T15:43:02.070+0800 I NETWORK  [initandlisten] waiting for connections on port 27027
//单元测试
...
//关闭进程
[mongod output] 2019-03-02T15:43:20.838+0800 I COMMAND  [conn3] terminating, shutdown command received
[mongod output] 2019-03-02T15:43:20.838+0800 I FTDC     [conn3] Shutting down full-time diagnostic data capture
[mongod output] 2019-03-02T15:43:20.846+0800 I CONTROL  [conn3] now exiting
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK  [conn3] shutdown: going to close listening sockets...
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK  [conn3] closing listening socket: 456
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK  [conn3] shutdown: going to flush diaglog...
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK  [conn3] shutdown: going to close sockets...
[mongod output] 2019-03-02T15:43:20.911+0800 I NETWORK  [conn1] end connection 127.0.0.1:52319 (2 connections now open)
[mongod output] 2019-03-02T15:43:20.911+0800 I STORAGE  [conn3] WiredTigerKVEngine shutting down
[mongod output] 2019-03-02T15:43:20.916+0800 I NETWORK  [conn2] end connection 127.0.0.1:52320 (1 connection now open)
[mongod output] 2019-03-02T15:43:20.943+0800 I STORAGE  [conn3] shutdown: removing fs lock...
[mongod output] 2019-03-02T15:43:20.943+0800 I CONTROL  [conn3] dbexit:  rc: 0

注:首次使用该组件时需要下载安装包,过程比较缓慢需要些耐心..

细节

细心的同学可能注意到了,我们为什么要特别规避EmbeddedMongoAutoConfiguration这个类呢?

在SpringBoot 官方文档中提到了 EmbeddedMongoAutoConfiguration,其作用主要是:

  • 自动检测 flapdoodle.embed.mongo组件是否被引入;
  • 如果当前的运行环境中能找到组件,则会自动启动组件,并在程序退出时做销毁

我们简单看一下其实现:

@Configuration
@EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class })
@AutoConfigureBefore(MongoAutoConfiguration.class)
@ConditionalOnClass({ Mongo.class, MongodStarter.class })
public class EmbeddedMongoAutoConfiguration {
    private final MongoProperties properties;
    private final EmbeddedMongoProperties embeddedProperties;

    @Bean(initMethod = "start", destroyMethod = "stop")
    @ConditionalOnMissingBean
    public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig)
            throws IOException {
        Integer configuredPort = this.properties.getPort();
        if (configuredPort == null || configuredPort == 0) {
            setEmbeddedPort(mongodConfig.net().getPort());
        }
        MongodStarter mongodStarter = getMongodStarter(this.runtimeConfig);
        return mongodStarter.prepare(mongodConfig);
    }

不难猜到,该配置类已经完成了我们在单元测试中所需要的一切事情,那为什么还需要BaseEmbededMongoTest?
答案在于,我们可能会对MongoDB的连接池做许多定制,如下面的代码:

@Configuration
public void MongoConfig{
    @Bean
    public MongoDbFactory mongoDbFactory(){
        ...
     }
}

类似这样的定制,会让MongoAutoConfiguration失效。即SpringDataMongo 的初始化会先于Embeded实例的启动,导致失败。
通过自定义的实现则可以规避该问题,当然如果通过Profile设定也可以进行规避。

二、使用Fongo

开源地址
Fongo 是由 Fousquare 开发团队开源的一款真正的内存式MongoDB,非常适用于轻量级的单元测试。
这个名字.. 不错哈

Fongo 支持对Java-Driver的各种CRUD指令进行解析,并模拟数据在内存中的存储管理操作,可以认为其提供了一层JavaDriver的代理。
同时,该框架是线程安全的,所有的集合读写操作都能得到同步保护

接下来是如何使用:

A. 引入框架

<!-- fongo face mongo -->
<dependency>
 <groupId>com.github.fakemongo</groupId>
 <artifactId>fongo</artifactId>
 <version>2.1.0</version>
 <scope>test</scope>
 <exclusions>
   <exclusion>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-core</artifactId>
   </exclusion>
   <exclusion>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
   </exclusion>
 </exclusions>
</dependency>

注:fongo依赖于jackson,可能与SpringBoot项目冲突,这里显示将其剔除。

B. 准备测试类

编写一个基于Fongo的类:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootSampleMongo.class)
@Import(TestConfig.class)
public class BaseFongoTest {

}

这里使用@Import导入了一个TestConfig,用于初始化Fongo实例,如下:

@TestConfiguration
@Profile("test")
public class TestConfig extends AbstractMongoConfiguration {

    @Autowired
    private Environment env;

    @Override
    protected String getDatabaseName() {
        return env.getProperty("spring.data.mongodb.database", "test");
    }

    @Override
    public Mongo mongo() throws Exception {
        return new Fongo(getDatabaseName()).getMongo();
    }
    
}

这样,通过继承于AbstractMongoConfiguration,可以省去配置MongoDbFactory之类的工作。
需要注意的是,如果业务代码做了一些连接池的定制,如MongoDbFactory/MongoTemplate的定义,则需要通过Profile进行隔离,避免在测试过程中出错:

@Configuration
@Profile("prod")
public class ProdMongoConfig {
...

C.业务测试

准备好上面的工作后,则可以用到业务测试代码上:

public class BookServiceTest extends BaseFongoTest{

    @Autowired
    private BookService bookService;

    @Autowired
    private BookRepository bookRepository;

至此,我们已经完成了Fongo 的使用。

参考文档

springboot-with-mongo-embed
flapdoodle-embed-mongo-github
another-embededmongo-fongo

小结

随着MongoDB 在Web开发中的应用越来越广,许多配套的框架及工具也在逐步完善。
本文介绍了两种在SpringBoot 框架上使用内嵌MongoDB的方式,从简易性来看,个人更推荐Fongo的方案。
由于Fongo 更接近于H2(一种内存SQL数据库)的实现,整个测试过程中不需要开启MongoDB进程,也免去了远程下载软件的烦恼。
所有的操作均在内存中完成,会令整个测试更加的高效,然而其仅有的缺点是无法支持一些原生的MongoDB管理命令(一般也不会用到)。
当然,读者也可以根据自己的需求自行选择。

欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
1月前
|
NoSQL Java 数据库
【问题篇】springboot项目通过数据库限制实例端口号
【问题篇】springboot项目通过数据库限制实例端口号
19 0
|
3天前
|
NoSQL MongoDB 数据库
MongoDB数据恢复—MongoDB数据库文件被破坏的数据恢复案例
服务器数据恢复环境: 一台Windows Server操作系统服务器,服务器上部署MongoDB数据库。 MongoDB数据库故障&检测: 工作人员在未关闭MongoDB数据库服务的情况下,将数据库文件拷贝到其他分区。拷贝完成后将原MongoDB数据库所在分区进行了格式化操作,然后将数据库文件拷回原分区,重新启动MongoDB服务,服务无法启动。
|
7天前
|
NoSQL MongoDB Redis
Python与NoSQL数据库(MongoDB、Redis等)面试问答
【4月更文挑战第16天】本文探讨了Python与NoSQL数据库(如MongoDB、Redis)在面试中的常见问题,包括连接与操作数据库、错误处理、高级特性和缓存策略。重点介绍了使用`pymongo`和`redis`库进行CRUD操作、异常捕获以及数据一致性管理。通过理解这些问题、易错点及避免策略,并结合代码示例,开发者能在面试中展现其技术实力和实践经验。
129 8
Python与NoSQL数据库(MongoDB、Redis等)面试问答
|
17天前
|
存储 关系型数据库 MySQL
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
|
18天前
|
Java 测试技术 数据库
SpringBoot启动时设置不加载数据库
SpringBoot启动时设置不加载数据库
10 0
|
1月前
|
SQL Java 数据库连接
springboot解析txt文件顺便加到数据库中(nohup文件)
springboot解析txt文件顺便加到数据库中(nohup文件)
112 1
|
1月前
|
NoSQL 网络协议 MongoDB
Windows公网远程连接MongoDB数据库【无公网IP】
Windows公网远程连接MongoDB数据库【无公网IP】
|
1月前
|
Oracle Java 关系型数据库
SpringBoot整合Mybatis连接Oracle数据库
SpringBoot整合Mybatis连接Oracle数据库
SpringBoot整合Mybatis连接Oracle数据库
|
1月前
|
存储 NoSQL 关系型数据库
一篇文章带你搞懂非关系型数据库MongoDB
一篇文章带你搞懂非关系型数据库MongoDB
58 0
|
1月前
|
人工智能 NoSQL MongoDB

热门文章

最新文章