news 2026/7/22 16:28:04

MongoDB 4.x——SpringBoot框架整合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MongoDB 4.x——SpringBoot框架整合

MongoDB 4.xSpringBoot框架整合

    • 1、SpringBoot简介
      • 1.1、SpringBoot是什么
      • 1.2、“脚手架”风格
    • 2、第一个SpringBoot项目
      • 2.1、初始化项目
      • 2.2、添加启动类
      • 2.3、编写Echo接口
      • 2.4、配置文件
      • 2.5、启动程序
      • 2.6、热加载
    • 3、Spring Data框架介绍
      • 3.1、Spring Data
      • 3.2、Spring Data MongoDB
    • 4、使用Spring Data MongoDB操作数据库
      • 4.1、引入依赖
      • 4.2、配置文件
      • 4.3、数据模型
      • 4.4、数据操作
      • 4.5、启动测试
    • 5、高级操作
      • 5.1、实现投射
      • 5.2、使用QBE
      • 5.3、自定义Repository方法
    • 6、自定义配置
      • 6.1、Spring Boot通用配置
      • 6.2、JavaConfig配置
      • 6.3、自动配置的原理
    • 7、实现单元测试
      • 7.1、用flapdoodle.embed.mongo
      • 7.2、原理解析
      • 7.3、定制化集成
    • 8、多数据源
    • 9、使用审计功能
      • 9.1、使用注解
      • 9.2、实现审计
    • 10、自定义数据序列化方式
      • 10.1、去掉_class属性
      • 10.2、自定义类型转换

1、SpringBoot简介

1.1、SpringBoot是什么

如果要为SpringBoot下一个定义,笔者认为最准确的是:

  • “当下Java Web最流行的脚手架。​”

SpringBoot为什么会这么流行?

这点与Spring框架是脱不开关系的。我们都知道,Java是一门面向对象的语言,为了更好地实现代码模块化及复用,面向对象定义了封装、多态继承等特性。

一个类的成员变量可以引用其他的类,于是在对象与对象之间就产生了依赖。而在以前的许多大型项目中,由于要实现的功能代码非常多,往往需要大量重复性地构建对象,以及设置依赖关系的代码,长期下来则会导致代码臃肿不堪。这时Spring框架诞生了,它提出了一个核心概念——IoC,即控制反转,如图所示。

控制反转,即对象的关系不再由对象本身决定,而由容器来控制其依赖。简单来说,就是由容器来帮你初始化对象,并完成自动化的装配。、

因为要做自动化对象的初始化、关系的装配,所以需要有一个东西来描述这些关系。一般是用.xml文件来描述,比如,applicationContext.xml会描述一个ApplicationContext上下文中所拥有的对象实例,以及这些实例之间的关系。于是,所有的Spring应用程序都使用了这样的配置方式。

在Web开发方面,Spring框架孵化出了Spring MVC项目,用来简化Servlet的开发。通过AOP实现的路由转换能力,可以快速把URL映射到一个Bean方法中去处理;通过内置常用的编/解码转换器,可以避免每次都要写格式转换的代码。这些能力,也让Spring MVC成为JavaWeb开发框架的不二之选。

在持久层方面,SpringData则提供了对于数据库操作的高度抽象,借助框架我们可以将数据库表中的行、列映射成类、属性,对于内存中对象的操作也被自动转换成数据库层的原语进行执行,这样无疑又大大简化了数据库的读写实现。

随着Spring框架的高歌猛进,许多Java应用开发的场景都逐渐被覆盖到了,于是Spring框架形成了一个庞大的生态帝国。

下图基本上涵盖了Spring框架的核心项目组件。

将Spring框架称为“桃李满天下”一点也不为过。那么SpringBoot又是怎么来的呢?

前面提到了,Spring框架使用.xml文件来描述对象的装配关系。但是在后来,随着Web开发技术的逐步完善,一个框架集成的模块越来越多,单一Web应用的功能也越来越多了。此时大家逐渐发现,基于.xml文件的方式去定义Bean加载,工作量其实很大,而且配置文件逐渐变得臃肿、不好维护,有时配置出现错误,经常要排查很长时间。于是,在Spring框架的版本演进中,逐渐出现了注解的方式。如用@Bean、@Autowired注解来完成声明式的依赖注入,用@ComponentSca注解则可以实现自动化扫描。注解的出现大大简化了开发工作。

2014年4月,Pivotal基于“无配置”的思路设计了SpringBoot项目的首个版本。无配置的意思是让你不用在配置上花太多时间,所有的东西尽可能都用内置的、现成的。

可以说,SpringBoot又一次提高了生产效率,在经过几年的发展之后,它已经成为一个俘获无数粉丝的“杀手级”框架,在Github中已获得了3.6万多个的关注。

1.2、“脚手架”风格

有人说SpringBoot是一个“脚手架”​。这不是没有道理的,整个SpringBoot项目包含许多的starter项目,而严格来说,这些starter只能算是“胶水项目”​(几乎没有什么代码量)​。但是通过引用它们,可以自动引入高度适配的第三方库组件(不需要担心冲突及兼容性问题)​,这会让你获得开发上的愉悦体验。

下图描述了作为核心starter的组件依赖关系。

除上图所提到的组件外,我们还可能会用到其他的starter项目,而它们都具有不同的用途,如下所示。

  • spring-boot-starter:核心启动器,包含自动配置、日志和YAML。
  • spring-boot-starter-web:引入全栈式Web开发组件,包括Tomcat和spring-webmvc。
  • spring-boot-starter-thymeleaf:引入Thymeleaf模板引擎,包括与Spring的集成。
  • spring-boot-starter-test:引入常规的测试依赖,包括JUnit、Hamcrest、Mockito及spring-test模块。
  • spring-boot-starter-websocket:引入WebSocket模块。
  • spring-boot-starter-redis:引入Redis模块。
  • spring-boot-starter-security:引入spring-security安全模块。
  • spring-boot-starter-data-jpa:引入数据存储层JPA(Java Persistence API)​。
  • spring-boot-starter-data-mongodb:引入MongoDB数据库模块。
  • spring-boot-starter-amqp:引入spring-rabbitmq客户端来支持AMQP协议。
  • spring-boot-starter-aop:引入AOP的编程模块,包括spring-aop和AspectJ。
  • spring-boot-starter-mail:引入javax.mail模块。
  • spring-boot-starter-log4j:引入Log4J日志框架。

使用SpringBoot框架多年,笔者认为其带来的便利主要包括以下几个方面。

  1. 约定优于配置,摒弃了大量繁冗且容易出错的XML配置,解放双手;
  2. 模块化:许多starter项目开箱即用,省去总是要解决依赖冲突的问题;
  3. 内嵌Http Server:相比以前使用SpringMVC的方式,不再需要依赖外部的Servlet容器;
  4. 对微服务开发更加友好,能通过框架快速实现一套RestFul接口。

尤其是最后一点,当下SpringCloud(流行的微服务开源框架)大行其道,其底座就采用了SpringBoot。因此SpringBoot的流行与微服务的关系是非常密切的。

2、第一个SpringBoot项目

在开始项目之前,需要先确认已经搭建了基础的Java开发环境,包括:

  • 安装JDK,选择1.8及以上版本。
  • 准备好IDE,可选择IDEA或者Eclipse。
  • 安装Maven,可选择3.5及以上版本。

2.1、初始化项目

以IDEA为例,新建一个Maven项目,如图所示。

我们将项目的groupId设置为org.hscoder.mongoapps,artifactId设置为echo-server。

成功之后,手动创建src/main/resource和src/test/resource两个目录,并将其分别标记为Resources Root和Test Resources Root。

最终的目录结构见表。

其中,pom.xml文件描述了整个项目对第三方库的依赖定义。为了将SpringBoot框架引入,需要编辑pom.xml文件,添加如下代码:



说明:

  1. 定义了几个变量,其中java.version用于指定项目编译的JDK级别,这里使用1.8版本。而project.build.sourceEncoding用于指定源代码的文件编码(UTF-8)​,spring-boot.version则指定SpringBoot框架的版本,这里采用的是2.2.2.RELEASE版本。
  2. 添加maven-compiler-plugin定义,其使用指定的选项进行编译。
  3. 定义了项目的依赖版本管理,其中引入spring-boot-dependencies以声明对SpringBoot依赖的版本范围。
  4. 定义了项目的依赖组件,主要如下。
    • spring-boot-starter-web:用于引入SpringMVC框架。
    • spring-boot-starter-log4j2:用于引入Log4J2日志框架。
    • jackson-databind:用于提供Jackson包实现JSON处理。
    • jackson-datatype-joda:用于添加对joda-time的支持。
    • lombok:用于提供模板化的getter/setter功能。

spring-boot-dependencies声明了SpringBoot依赖组件版本的全集,在项目中引入它之后,对于一些starter组件的依赖不再需要声明版本,而是采用spring-boot-dependencies所指定的版本,这非常有利于对项目依赖进行统一管理。

另一种方式是让项目继承自spring-boot-parent,以获得SpringBoot框架依赖的各种组件版本。但这样可能存在问题,因为许多项目可能拥有自己的父级项目。

2.2、添加启动类

创建org.hscoder.mongoapps.echoserver包,新建EchoBoot类,代码如下:

这里@SpringBootApplication用于将EchoBoot声明为SpringBoot程序的入口类。随后在main方法中,我们构建了一个SpringApplication,并将EchoBoot类作为入参传入,如此,SpringBoot应用将会以EchoBoot作为上下文启动。

此外,我们还为该应用添加了一个生命周期的监听器ApplicationPidFileWriter,它会在应用启动后将JVM的进程ID写入一个application.pid文件中。

2.3、编写Echo接口

新建一个EchoController类,代码如下:


我们使用@Controller注解声明该类是一个控制器,另外,在hello方法上声明了以下两个注解:

  • GetMapping(“/hello”)​,表示将该方法映射到请求路径/hello的HTTP GET方法上。
  • @ResponseBody,表示将方法的返回结果作为HTTP响应内容输出。

这样,我们就完成了一个最基本的HTTP请求控制器。

2.4、配置文件

在src/main/resources/目录下新建一个application.properties文件,内容如下:

参数说明见表。

为了更好地控制日志输出,我们在src/main/resources/目录下新建一个log4j2.xml文件,内容如下:


这里配置了两种日志记录方式,Console是控制台打印,RollingRandomAccessFile指向一个日志文件,我们还为该日志文件设定了滚动的规则:

  1. 当大小超过50MB时会生成新的日志。
  2. 每小时生成一个新的日志;DefaultRolloverStrategy max="20"表示最多保存20个日志文件。

除此之外,我们将根日志级别、主模块(org.hscoder)的日志级别同时设置为info级别。在org.hscode模块中指定additivity=true,即表示继承上级(根日志)的设定,此时所有的日志都会输出到Console和文件中。

2.5、启动程序

在IDEA中用鼠标右击,在弹出的菜单中选择执行EchoBoot,启动程序,从控制台可以看到如下日志:


这样便表示我们的第一个SpringBoot应用已经启动,并同时监听了8090端口。在浏览器中打开http://localhost:8090,即可以看到输出了“HelloWorld!”字样,如图所示。

2.6、热加载

SpringBoot的热加载(livereload)是一个很方便的特性:我们在开发功能时经常需要对代码进行修改,如果每次修改都要重启一次应用则比较麻烦,使用热加载功能可以在不重启应用的情况下令代码修改生效(自动重启)​,这非常方便。

热加载功能由spring-boot-devtools组件提供。在pom.xml文件中声明依赖如下:

启动应用,对源代码或配置文件进行修改,发现SpringBoot会自动重启。

在启动时也可以发现如下日志:

spring-boot-devtools组件会定时扫描类路径下的class和资源文件,一旦发现变更即重启服务,默认1000ms检测一次。热加载在扫描时会自动忽略以下范围的变更:

需要注意的是,spring-boot-devtools在检测到编译后的文件发生变化时才会重启应用。因此需要设置IDEA的自动编译开关。

3、Spring Data框架介绍

3.1、Spring Data

在整个Spring框架体系中,Spring Data一直默默扮演着重要的角色。大家对于Spring MVC可能已经非常熟悉,毫无疑问,Spring MVC是开发Web应用层的绝佳选择。然而,绝大多数应用都是数据密集型的。即几乎所有的Web应用都需要使用数据,并且和存储层打交道。对此,Spring Data则为我们提供了数据操作层面的解决方案。

Spring Data项目创建于2010年的“Spring One开发者大会”​,其起源则来自于Rod Johnson(Spring框架创始人)和Emil Eifrem(Neo4j公司)对于Spring和Neo4j图形数据库的一次技术整合尝试。可见,一开始Spring Data就具备了NoSQL技术的“基因”​,而且从整个发展历程上看,Spring Data也完全覆盖了关系型存储和NoSQL领域。

通过使用Spring Data,开发者可以获得与Spring一样的编程体验。然而,由于不同数据库(尤其是NoSQL)存在着种种差异,Spring Data仍然会支持底层数据库的一些特性。


Spring Data并不具备直接操作底层数据库的能力,而是对每一种数据库的驱动层进行了封装,例如Spring DataMongoDB的部分则是基于MongoDB Java Driver实现的。对于上层应用则开放了两种接口。

  • Repository风格接口:基于Spring生成动态的Bean对象,抽象了标准的增加、删除、修改、查询、排序等功能。
  • Template风格接口:非标准风格的操作接口,对于底层驱动做了一些适配,同时也支持一些原生API。

此外,Spring Data本身依赖Spring框架提供的一些基础能力,主要如下。

  1. IoC容器机制,用于实现Repository、Template接 口的实例化。
  2. 类型转换,用于实现数据库持久化模型到内存对象的转换、类型检查。
  3. 表达式语言,利用Spring EL实现对查询(Query)声明。
  4. JMX整合能力,支持标准的JMX(JavaManagement Extenstions, Java管理扩展)监控接口。
  5. 异常定义,由Spring框架定义了持久化层(DAO)的异常类型(见图)​。

3.2、Spring Data MongoDB

Spring Data MongoDB是Spring Data的子项目,该项目主要用于实现MongoDB数据库的整合,并提供体验一致的持久层API。和MongoDB客户端驱动一样,SpringData MongoDB项目也采用了开源协议Apache LicenseV2。

Spring Data MongoDB的功能架构如图所示。

1.核心功能

  • Repository Support:提供了数据操作方法的抽象,支持MongoDB Repository。同时支持基于方法名的查询和注解式查询。
  • Object/Document Mapping(ODM)​:提供MongoDB文档与内存对象的映射功能,将集合映射为类,将文档映射为对象:提供相应的注解(annotation)​,如@Document、@Field、@Index等。
  • Templating:模板化操作,支持各种丰富且灵活的API,如集合管理、MapReduce/聚合操作等。

2.辅助功能

  • Configuration:支持以Spring风格的方式将MongoClient实例配置为Bean对象。
  • Event Handling:支持生命周期事件的管理,如持久化、对象转换等事件的处理。
  • Exception Translation:支持异常的转换处理,将MongoDB驱动层的错误加以包装,以Spring的DataAccessException形式抛出。
  • Auditing:支持审计功能接口,可对数据管理操作添加审计日志支持。
  • JMX Support:支持标准的JMX接口,方便对连接数、操作次数等指标进行监控统计。

Spring Data MongoDB对MongoDB的各种特性提供了完备支持,除基础的CRUD操作和聚合框架功能外,还支持Change Stream、会话,以及多文档事务等功能。

4、使用Spring Data MongoDB操作数据库

4.1、引入依赖

编辑pom.xml文件,添加如下代码:

spring-boot-starter-mongodb是一个胶水组件,声明对它的依赖会令项目自动引入spring-data-mongo、mongodb-java-driver等基础组件。

4.2、配置文件

在application.properties中配置如下:

不难理解,这里是数据库主机、端口、用户名、密码、数据库名的设置。在启动应用前,需要保证这里配置的数据库连接信息可用,否则会导致程序启动失败。

4.3、数据模型

以在线书评网站为例,我们创建一个book(书籍)实体类,代码如下:

book类定义了一些属性,说明见表。

我们需要关注的几个注解如下:

  • @Document,声明类对象为MongoDB文档,collection用于指定映射MongoDB的集合名称,如果不指定,则会直接使用类的名称。
  • @Id,用于标记ID属性,该属性被映射MongoDB文档_id字段上。默认情况下,MongoDB的_id是一个ObjectId类型,而在对象中可以使用String、BigInteger或ObjectId类型映射,框架会自动完成一些转换工作,例如对于String类型则会使用_id的十六进制形式表示。如果不希望使用数据库默认的类型,那么也可以自己指定。
  • @Indexed,表示这是一个单键索引,其中unique=true表示唯一索引。
  • @CompoundIndexes,表示集合上的复合索引集合。
  • @CompoundIndex,表示一个复合索引,name是索引名称,而def则是索引的字段定义。

在默认情况下,框架会自动扫描类路径中包含@Document注解的类,并做好一些初始化工作,包括创建这些声明式的索引。

4.4、数据操作

ODM的方式可以让你通过操作对象来直接影响数据,这样便减少了操作难度,使用者不再需要熟练记住驱动层的API。Spring Data MongoDB实现了类JPA的接口,通过预定义好的Repository可实现代码方法到数据库语句的映射。

我们创建一个BookRepository接口,代码如下:


BookRepository继承了MongoRepository,间接继承自Spring Data框架的CrudRepository接口。因此SpringBoot在启动后会自动扫描该接口,并完成必要的实例化工作。这里所声明的3个方法,将被自动转换为对应的条件查询实现。

例如,findByAuthor等价于如下的语义:

而findByCategoryOrderByPublishDateDesc表示的语义则是:

接下来,我们编写一段代码来操作数据库:


BookOperation类也是一个声明式的Bean,其中,@PostConstruct注解表明在Bean实例化之后将执行init方法,这会进一步调用runOperations方法,并执行一系列的操作。

注意,这里重复使用了save方法。首次使用save方法时将会执行insert命令,而之后使用save方法则执行的是update命令,这是因为,在第一次使用save方法之后返回的对象中包含了自动生成的id字段,而框架则根据实体对象中是否存在id值来决定执行插入还是更新。

4.5、启动测试

为了捕捉到框架的行为,我们将该模块的日志级别调整为Debug级别。编辑log4j2.xml文件,添加如下代码:


随后,启动SpringBoot应用程序,可以看到日志输出如下:

5、高级操作

5.1、实现投射

如果不希望将整个文档全部返回,则可以使用投射(projection)的方式。例如,在查询书籍榜单时,我们可能只需要书籍的标题、得票数和发布日期这几个字段。Spring Data MongoDB允许你使用Pojo风格的接口来定义返回对象,代码如下:

在BookRankInfo接口中定义了几个getter方法,分别对应title、voteCount字段。

接下来,在BookRepository接口中添加方法:

其中,projectClass表示要进行投射的Pojo接口类型。借助此方法,我们还可以实现多种不同的投射类型。尝试执行如下的代码片段:

最终的日志输出为:

使用Spring EL,我们还可以利用表达式来完成一些动态计算,例如在BookRankInfo接口中添加如下代码:

其中,published字段在Book实体类中并未定义,而是根据文档的publishDate日期格式化而来的。可以看到,我们通过@Value注解定义了一个Spring EL表达式,在执行时框架将会自动完成表达式的计算,最终得到的对象如下:


@Value注解来自org.springframework.beans.factory.annotation包,需要注意与lomback中的@Value区分开。

需要注意的是,一旦加入了Spring EL表达式,框架便无法从Pojo接口中推断出执行投射的字段,此时仍然需要查询整个对象,这相当于无效的优化。为了实现真正意义上的投射,需要在BookRepository方法上添加一些标记,代码如下:

5.2、使用QBE

QBE(Query By Example)即参照一个样例进行查询。例如我们想查询Book信息,则可以构造一个“相似”的Book对象作为例子,查询与例子相匹配的数据。

首先,需要让BookRepository添加继承,代码如下:


QueryByExampleExecutor是QBE执行器的接口,其定义的相关接口如下:

接下来,构造样例进行查询,代码如下:


这里,QBE执行器会将bookQuery样例对象中的非空字段作为查询条件,并融合exampleMatcher中的定义构造出最终的查询语句。如上述代码中得到的查询条件为:

QBE的使用场景是有限的,仅适用于比较简单的查询,如等值、字符串匹配。对于更复杂的需求,仍然需要使用MongoTemplate实现。

5.3、自定义Repository方法

Spring Data的Repository机制为我们提供了一系列标准化功能,包括基本的增加、删除、修改、查询,以及基于方法名和注解的动态查询等。这无疑是非常方便的,但其真正强大的地方却是对数据库驱动原生API的二次封装,即Template类的实现。在Spring Data MongoDB组件中,MongoTemplate类承载了很重要的作用,我们可以通过它实现许多高度定制化的功能。

对于无法通过Repository实现的操作,可以借助于自定义接口,代码如下:

BookRepositoryCustom接口中定义了两个方法。

  • search:用于实现高级的分页检索,可支持分类、标题关键字(模糊匹配)​、作者,以及发布日期范围等多个条件。
  • incrVoteCount:用于实现单独的投票数变更操作。

为了让BookRepository拥有这两个方法,我们需要添加继承关系,代码如下:

接下来,添加BookRepositoryImpl类,实现接口中的方法,代码如下:




在BookRepositoryImpl类中注入了一个MongoTemplate实例,并且借由其API(Query、Update接口)完成了具体的代码实现。可以看到,直接使用MongoTemplate能获得如下一些好处:

  • 在incrVoteCount方法中,直接使用Update指定更新的字段,可避免使用save方法时执行全量的更新。
  • 在search方法中,使用Quer Criteria API,可以构造出各种灵活的查询条件。

务必注意一点,BookRepositoryImpl的命名是约定俗成的,即必须以BookRepository的命名添加后缀Impl。如果不是这样,则会导致Spring Data无法找到扩展类的实现。

此时我们已经让BookRepository拥有了自定义方法的行为,可以在代码中直接使用,如下:

在本例中,我们使用了一种“织入的方式”来实现Repository的自定义方法。当然,这是框架提供的一种便利而非强制的做法,而且这种实践带来的好处是不必破坏现有的代码分层设计,即数据库的操作仍然由Repository层来实现。当然,在一些特殊情况下,仍然可以直接使用MongoTemplate的API,读者可以自行选择。

6、自定义配置

6.1、Spring Boot通用配置

为了正确连接数据库,application.properties的配置如下:

或者采用URI的方式,代码如下:

一般情况下,鉴权的数据库(authenticationDatabase)和连接默认的数据库是同一个,如果存在不一致,则可以单独设置鉴权的数据库,代码如下:

其他的一些选项如下:

6.2、JavaConfig配置

SpringBoot自带的通用配置可参见MongoProperties。而通常,在生产环境中对客户端配置有更高的要求,例如:

  • 使用分片集群,需要连接多个mongos主机。
  • 为了安全考虑,需要对密码进行加密、解密处理。
  • 在微服务场景下,通过配置中心动态获得连接的配置。

这些场景很难直接使用默认的MongoAutoConfiguration完成,此时,我们可以使用Java Config风格的配置方式,对MongoDB客户端的一些选项或行为进行定制,例如,使用如下代码:



这里的关键在于声明MongoDbFactory作为自定义的Bean对象,其中,mongoFactory方法使用了大量Java驱动的API进行MongoClient实例的构造,这在前面的章节已经介绍过,此处不再赘述。

6.3、自动配置的原理

我们说过,SpringBoot实现了MongoDB的自动配置功能,而具体的实现来自下面的配置类。

1.MongoAutoConfiguration

打开MongoAutoConfiguration源代码,如下:


代码片段来自spring-boot-autoconfigure-2.2.2.RELEASE。

可见,MongoAutoConfiguration仅仅完成了MongoClient的配置。

2.MongoDataAutoConfiguration

查看其源代码片段,如下:

不难发现,MongoDataAutoConfiguration会在MongoAutoConfiguration初始化之后执行,而且其进一步引入了3个配置。

  • MongoDataConfiguration:完成MongoMappingContext配置,主要用于定义实体类的扫描和映射策略、索引构建策略等。
  • MongoDbFactoryConfiguration:完成MongoDbFactory配置。
  • MongoDbFactoryDependentConfiguration:完成MongoTemplate配置。MongoTemplate是一个线程安全的对象,最终将被注入MongoRepository代理Bean中使用。

这几个自动配置的类由spring-boot-autoconfigure包提供,在搭建SpringBoot项目时会通过spring-boot-starter依赖自动引入。此时,只要我们引入spring-boot-starter-data-mongodb,就可以使自动配置类生效(@ConditionalOnClass条件被满足)​。

那么,在本节的自定义配置部分,我们自定义实现的MongoDbFactory是如何保证生效的呢?

答案就在于@ConditionalOnMissingBean这个注解的使用,所有的自动配置类通过这个注解,将自身的Bean定义优先级降到最低。也就是说,只要发现有其他地方声明了Bean,那么自动配置的Bean就不再有效了,这点也是SpringBoot自动配置的关键。

7、实现单元测试

7.1、用flapdoodle.embed.mongo

在SpringBoot的自动化配置中,对内嵌式的MongoDB已经做了相应支持,具体的实现由de.flapdoodle.embed.mongo(简称EmbeddedMongoDB)组件提供。

Embedded MongoDB会在应用首次启动时自动下载MongoDB的安装包并解压,之后便按程序的设置启动本地的MongoDB数据库进程,其使用流程如图所示。

下面以EchoServer项目为例演示如何使用。

(1)引入依赖包,在pom.xml文件中添加如下代码:


其中,spring-boot-starter-test用于实现SpringBoot应用的单元测试,而de.flapdoodle.embed.mongo则是Embedded MongoDB组件。组件的scrope被指定为test,即只有在执行单元测试时才会引入这些依赖。

(2)添加BaseTest作为测试的基础类,代码如下:

(3)编写测试代码,代码如下:

(4)配置本地数据库,在src/test/resources目录中新建application-test.properties文件,代码如下:


spring.mongodb.embedded.version指定了使用4.0.2版本的MongoDB。如果不指定则会取EmbeddedMongoProperties中的默认值(如SpringBoot 2.2.2版本中使用MongoDB 3.5.5版本)​,spring.data.mongodb.host在此必须使用本地地址,否则会导致进程启动失败。同样,spring.data.mongodb.port也必须使用本地可用的端口,为0则表示使用随机端口。

Embeded MongoDB会在本地启动一个无鉴权的mongod进程,因此除了host、port信息,其他的连接属性会被忽略。

(5)最后,我们启动测试,可以看到输出如下:



从日志中可以看到,Embedded MongoDB组件会默认将安装包下载到本机的${user.home}目录中:

  • 对于Windows系统,该目录为C:\Users{用户名}.embedmongo。
  • 对于Linux系统,该目录一般为/home/{用户名}/.embedmongo,具体取决于Linux用户的home目录设置。

由于首次运行时需要下载安装包,该过程可能比较缓慢。

7.2、原理解析

在我们的示例中,似乎并没有使用任何控制EmbeddedMongoDB的代码,一切都非常简单。这其中的功劳来自SpringBoot的自动配置机制。在SpringBoot官方文档中提到了EmbeddedMongoAutoConfiguration,其作用主要是:

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

我们简单看一下其实现,代码如下:


EmbeddedMongoAutoConfiguration类已经完成了单元测试所需要的一切准备工作,我们关注的问题如下。

问题一:如何检测flapdoodle.embed.mongo是否引入?

在类的头部声明中,@ConditionalOnClass注解指向了MongodStarter类,即只有当flapdoodle.embed.mongo组件被引入时,MongodStarter类可用,此时自动配置才是生效的。

问题二:如何触发本地MongoDB进程的启动和销毁?

embeddedMongoServer方法被定义为一个Bean构造器(产出MongodExecutabl实例)​,而且Bean初始化(initMethod)​、销毁(destroyMethod)分别指向MongodExecutabl的启动(start)​、停止(stop)方法。

问题三:如何使客户端获知本地的MongoDB地址和端口号?

如果使用了随机端口,那么会通过setEmbeddedPort方法将端口信息写入ApplicationContext上下文中。此后在ApplicationContext的变量表中通过local.mongo.port参数可获得该端口号。另一个关键则是使用@AutoConfigureBefore

(MongoAutoConfiguration.class)声明,这表示MongoDB客户端的自动配置工作会在MongodExecutable启动之后进行,这样便保证了客户端能正确获得这个端口号。

7.3、定制化集成

除了使用自动配置的方式,还可以自行使用EmbeddedMongoDB组件来实现测试。自实现的方式有利于对该组件做一些定制化工作,而且更容易进行问题的排查。基于前面介绍的Embedded MongoDB组件的原理,我们对 测试代码进行一些调整。

(1)取消EmbeddedMongoAutoConfiguration自动配置器以避免冲突,代码如下:


(2)修改BaseTest,代码如下:

关于BaseTest的改动有以下两点:

  1. 添加了EmbeddedMongoRule静态规则,@ClassRule是由JUnit4提供的类级别规则,可以在测试类启动、停止时执行一些自定义代码。
  2. 导入了TestMongoConfig配置,用于声明测试环境的客户端Bean。

(3)具体的代码实现如下。

  • EmbeddedMongoRule

EmbeddedMongoRule在startServer方法中使用了随机端口,在启动数据库之后,调用TestMongoConfig.saveAddr将地址、端口信息保存,以此保证测试配置的客户端能正确连接本地的数据库。

TestRule要求实现apply方法,其中base.evaluate是执行测试的主体方法。而代码在实现时仅在首次运行时启动一次MongoDB,并不是每次都进行启动、销毁,这么做的出发点有两个。

  • ①对于多组测试类来说,使用同一个MongoDB进程已经足够,反复启动和销毁进程会让测试过程更加缓慢。
  • ②Embedded MongoDB在启动进程后会注册JVM的回调钩子,保证Java进程退出时销毁本地MongoDB进程。
    • TestMongoConfig



TestMongoConfig类通过@TestConfiguration将自己注册为一个Configuration Bean。其中mongoDbFactory方法实现了MongoDbFactory的Bean实例注册,而在构造MongoClient时则使用了所记录的Embedded MongoDB地址和端口信息。

至此,已经完成了Embedded MongoDB的定制化集成工作。如果有更多的需求,则可进一步参阅其官方文档。

8、多数据源

至此,我们的样例都是运行在一个MongoDB数据源上面的。自动配置的想法是约定俗成的,即假设百分之九十以上都是单服务单库的情形。然而,如果需要连接多个数据源呢?此时我们要考虑的事情就会复杂一些。这些因素包括:

  • 如何对不同数据源进行配置?
  • MongoTemplate实例需要存在多个,如何区分?
  • MongoRepository接口如何与正确的数据源实现关联?
  • Entity实体类扫描路径如何区分?

多数据源的做法或多或少打破了微服务的一些隔离原则,但这不是Spring Data MongoDB要考虑的范畴。相反,框架提供了多数据源的支持,毕竟这在一些特定的场景下会存在需求。

下面介绍如何实现。

(1)去掉自动配置。为了避免自动配置功能产生的干扰,通常需要将自动配置类进行排除,代码如下:


(2)多数据源配置。添加Java Config风格的配置类,代码如下:

为了简化处理,仍然可以利用MongoProperties作为某个数据源的配置对象,假设我们需要连接first、second两个数据库,那么对应于application.properties的配置内容如下:

(3)实现不同包路径的代码。针对不同的数据源,需要将相应的持久层代码放置在不同的包路径下。

  • org.hscoder.mongoapps.multidb.first

FirstDoc实体类,代码如下:

FirstRepository类,代码如下:

  • org.hscoder.mongoapps.multidb.second

SecondDoc实体类,代码如下:
SecondRepository类,代码如下:


(4)实现MongoTemplate多实例。添加MultiMongoConfig类,完成不同数据源的MongoTemplate实例化,代码如下:



上述代码分别声明了两个MongoTemplate,分别对应first、second两个数据源。其中利用了AbstractMongoClientConfiguration工具类来辅助完成MongoTemplate的创建,对于其getMappingBasePackages方法的重载可以定义Entity类扫描的路径。

此外,@Primary的注解用来指定默认的数据源MongoTemplate,这点比较有用,尤其是当我们的项目从单数据源切换到多数据源时,可以减少对一些旧代码的修改。

(5)使用@EnableMongoRepositories声明,代码如下:


@EnableMongoRepositories注解用于声明对MongoRepository接口的处理模块,其中mongoTemplateRef指定了该模块用于关联的MongoTemplate实例Bean的名称,而basePackages则定义了模块扫描Repsitory接口所在的包路径。需要区分的一点是,basePackages只能定义Repository的扫描路径,而Entity类的扫描路径仍然是在构造MongoTemplate时指定。

(6)测试代码,按正常的方式使用Repository接口,代码如下:


执行上述代码,可以发现FirstDoc和SecondDoc对象分别写入了不同的数据库中。

9、使用审计功能

数据审计功能属于数据合规性管理的一部分。为了降低风险,系统需要将业务数据变更行为进行记录(用于回溯分析)​,这通常包括以下因素:

  • 数据在什么时间创建。
  • 数据由谁创建。
  • 数据最近一次发生变化的时间。
  • 数据最近一次的修改者。

不难猜到,应用层完全可以自行实现,思路如下:

  • (1)为实体类添加审计字段。
  • (2)在保存实体对象时,对审计字段进行赋值。

可以想象的是,由于对所有审计范围的业务实体都需要实现这样的过程,所以很容易产生大量重复代码。一种可行的方案是采用AOP,但这存在一定的复杂性。与此同时,Spring Data提供了非常便利的审计(auditing)功能,可以减少这种重复代码。

9.1、使用注解

出于通用性的考虑,我们可以定义一个基础的审计类,代码如下:


BaseAuditable定义了4个属性,分别对应创建时间、最后修改时间、创建者、最后修改者。每个属性分别加上了Spring Data的注解(来自org.springframework.data.annotation)​,具体的意思可对号入座。

接着,让业务实体类继承该审计类,代码如下:

9.2、实现审计

Spring-Data-MongoDB对审计提供了支持,该功能需要使用@EnableMongoAuditing开启,代码如下:

除了启动类,@EnableMongoAuditing也可以在包含@Configuration的配置类中添加。在开启审计功能后,框架在保存审计类对象时,会自动对注解的字段进行注入:

  • 对于@CreatedDate和@LastModifiedDate注解的字段会采用当前的时间值。
  • 对于@CreatedBy、@LastModifiedBy注解的字段则通过AuditorAware接口处理。

为了实现创建者、最后修改者字段的自动注入,我们需要实现AuditorAware接口,代码如下:

对book实体添加了审计支持后,查看数据库生成的记录,代码如下:

10、自定义数据序列化方式

由于ODM(Object to Document Mapping)的存在,Spring Data MongoDB显现出了强大的便利性。而这种便利更多来自框架内置的类型转换机制。想象一下,如果总是使用MongoDB Java Driver原生的API,我们就不得不经常性地编写POJO类的转换工作,例如在book对象和Document(MongoDB Java Driver类)对象之间实现相互转换。

尽管如此,我们可能仍然需要做一些定制化工作。

10.1、去掉_class属性

一般,通过@Document注解实现映射的实体类,在持久化时都会带上一个_class属性,例如,book实体类在数据库中表示如下:

这个类型是框架默认加上的,目的是用于表明该数据映射实体类型。而实际上,这个字段并没有太多用途,我们可以通过定制TypeMapper的实现来去掉它,代码如下:

我们在自定义的配置中声明了MappingMongoConverter这个Bean对象,这个类是实现类型转换、编/解码的关键,其中的类型字段则是TypeMapper接口所提供的。

默认情况下,会使用DefaultMongoTypeMapper实现,并且typeKey的值被设置为“_class”​(会导致写入对应的字段)​。此时将typeKey设置为空可以避免出现该冗余字段。

10.2、自定义类型转换

MongoDB统一采用了BSON作为存储类型,MongoDBJava Driver可以直接处理Interger、Long、String、Date等一些基本类型,对于内嵌的子对象,Spring DataMongoDB会将其转换为Document(MongoDB JavaDriver类)对象。

在某些场景下,你不得不实现自己的序列化方式,比如 通过文档存储某些特殊格式的内容。如下面的例子:


这里,Person类被用于描述个人信息档案,而关键的信息都存储在personAttrs属性中。PersonAttrs被设计为灵活的KV结构,代码如下:


我们尝试写入一些Person实体信息,代码如下:


如果执行上述代码,则会得到如下错误信息:

原因在于,name、phone这样的名称并不符合MongoDB文档对于key的格式要求。MongoDB规定在文档的一级属性中,不可以使用$ 符号作为前缀。为此,我们可以利用Converter接口实现自己的序列化方式,例如下面的做法:


这里针对PersonAttrs类型分别实现了读转换器(PersonAttrsReadConverter)和写转换器(PersonAttrsWriteConverter)​,之后在MongoCustomConversions这个Bean中注册即可:


最终可实现PersonAttrs的保存,数据库的文档形式如下:

当然,这里的序列化方式仅供大家参考。重点是读者可以借助这种自定义Converter的机制,解决一些特定场景中的需求,例如,为所有的密码字段实现可逆的加解密存取,等等。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 16:25:44

第一块自制PCB-PWM有刷电机调速

目的: 初试电子元件选型初试原理图绘制初试PCB布局以及打样制作过程: 1. 电子元件选型和原理图电源开关&电位器 电位器选择:100K RK097NS 保险丝支架:5x20 BLX-A型 XC-7 整流二极管:1N4007 1A/1200V 电阻电容&…

作者头像 李华
网站建设 2026/7/22 16:16:58

Blender渲染性能深度优化:多线程CPU调度与GPU异构计算实战配置

Blender渲染性能深度优化:多线程CPU调度与GPU异构计算实战配置 【免费下载链接】blender Official mirror of Blender 项目地址: https://gitcode.com/gh_mirrors/bl/blender Blender作为开源三维创作套件,在渲染性能方面拥有强大的并行计算架构。…

作者头像 李华
网站建设 2026/7/22 16:15:03

Arch Linux AUR包管理工具Paru:Rust实现的现代包管理解决方案

Arch Linux AUR包管理工具Paru:Rust实现的现代包管理解决方案 【免费下载链接】paru Feature packed AUR helper 项目地址: https://gitcode.com/GitHub_Trending/pa/paru Paru作为Arch Linux生态系统中功能最全面的AUR助手,通过Rust语言实现了高…

作者头像 李华
网站建设 2026/7/22 16:14:45

EMIFA接口与NAND Flash交互机制及异步访问优化实践

1. EMIFA接口与NAND Flash交互的核心机制在嵌入式系统设计中,外部存储器接口(EMIFA)扮演着连接处理器核心与外部存储世界的桥梁角色。它绝不仅仅是一个简单的地址/数据总线驱动器,而是一个集成了复杂状态机、时序控制和协议处理能…

作者头像 李华