前言
H2 是一个用 Java 编写的开源关系数据库,特别适合作为嵌入式数据库和内存数据库使用。
针对其特性,我不会太深入讲解什么其他跟原理和很细节方面的东西,本文旨在让大家快速理解什么是内存数据库以及如何使用内存数据库快速进行开发。
什么是内存数据库
内存数据库(In-Memory Database,IMDB)是一种将数据完全存储在计算机内存中的数据库系统。与传统磁盘数据库不同,它跳过了磁盘 I/O 操作,直接通过内存读写数据,因此具备毫秒级响应速度和高并发处理能力。其核心优势在于:
- 速度优势:内存访问速度(纳秒级)比磁盘(毫秒级)快 3-4 个数量级,适合实时分析、高频交易等场景。
- 架构简化:无需复杂的磁盘缓存机制,数据结构设计更贴近内存特性(如哈希表、索引树)。
- 内存计算融合:数据与计算逻辑在内存中直接交互,减少数据搬运开销。
- 数据存储与持久化
- 主要存储在内存:数据加载到内存中运行,减少 I/O 操作。
- 持久化机制:通过日志(Redo Log)、定期快照(Snapshot)或异步写入磁盘等方式,避免断电等故障导致数据丢失(例如 Redis 的 RDB 和 AOF 机制)。
但内存数据库也存在数据易失性(断电数据丢失)、内存容量限制等挑战,因此常与持久化机制结合使用。
维度 | 内存数据库 | 传统磁盘数据库(如 MySQL) |
---|---|---|
数据存储位置 | 主要在内存 | 主要在磁盘 |
性能瓶颈 | 受内存容量限制 | 受磁盘 I/O 速度限制 |
适用场景 | 实时性、高并发、低延迟场景 | 海量数据存储、事务强一致性场景 |
成本 | 硬件成本高(需大量内存) | 硬件成本较低 |
数据一致性 | 部分支持最终一致性,事务能力较弱 | 强一致性(ACID) |
H2 是一个用 Java 开发的嵌入式关系型数据库,它既支持传统磁盘存储,也提供纯内存模式。
H2 数据库介绍
H2是一个采用java语言编写的嵌入式数据库引擎,只是一个类库(即只有一个 jar 文件),可以直接嵌入到应用项目中,不受平台的限制,而且比较重要的是,Spring Boot、Hibernate 等框架默认集成 H2,用于快速搭建测试数据库,这样就是 spring 框架不依赖真实数据库进行测试的场景。
H2 的独特之处在于完全兼容 SQL 标准,支持 JOIN、子查询等复杂关系操作,这是 Redis 等 NoSQL 内存数据库无法替代的。例如,在需要实时分析多张关联表数据时,H2 内存模式可直接执行 SQL,而且H2 通过轻量级架构和灵活的内存 - 磁盘混合模式,展现了内存数据库的核心优势:既满足了 “极速响应” 的性能需求,又通过持久化机制弥补了数据易失性的缺陷。
应用场景:
- 可以同应用程序打包在一起发布,可以非常方便地存储少量结构化数据
- 可用于单元测试
- 可以用作缓存,即当做内存数据库
H2的产品优势:
- 纯Java编写,不受平台的限制;
- 只有一个jar文件,适合作为嵌入式数据库使用;
- h2提供了一个十分方便的web控制台用于操作和管理数据库内容;
- 功能完整,支持标准SQL和JDBC。麻雀虽小五脏俱全;
- 支持内嵌模式、服务器模式和集群。
H2数据库安装,这是它的官网,可以看到 H2 数据库非常小
H2 数据库也是和 Mysql 一样要配环境的。
H2 数据库的安装和简易部署
下载安装
如果你去官网解压下载包,内容就是这样

下载安装程序后也是一样,只不过没有 Uninstall.exe
目录说明
1 | h2-1.4.200.jar H2数据库的jar包 |
数据库启动——Windows环境下
那么如何启动
下载H2后,你会看到几个关键的启动文件:
1 | h2-1.4.200.jar # 核心jar包,可直接运行 |
启动方式对比:
h2.bat
:前台运行,会显示控制台窗口,关闭窗口就停止服务h2w.bat
:后台运行,没有控制台窗口,适合生产环境- 直接运行jar:
java -jar h2-1.4.200.jar
,等同于h2.bat
进入bin目录下,执行h2.bat
,h2w.bat
,h2-1.4.200.jar
都可以。启动后会自动打开浏览器控制台,可以选择语言为中文
启动命令说明如下
1 | # Windows环境 |
系统会进入H2
数据库的Web Console
,http://<你的ip>:8082
,注意占用的是
8082 端口,你 Spring 或者 Servlet
改过prot
的就非常容易重复。
默认会创建一个数据库Generic H2,用户名为sa,空密码

其中的字段的含义如下
保存的连接设置
这是一个下拉选项,作用是快速选择之前保存过的一组完整连接配置 。比如图里选的 “Generic H2 (Embedded)”,选中后,驱动类、JDBC URL、用户名等相关连接参数会自动填充,不用每次手动输入,方便下次直接用相同配置连数据库,避免重复设置参数。是快捷选择历史配置的入口
连接设置名称
是给当前这组连接配置起的标识名 ,像 “Generic H2 (Embedded)” 。主要用途:是配置的标识,用来存、取、管理连接参数,让连数据库更高效
- 保存配置:填好驱动类、JDBC URL 等信息后,点 “保存”,H2 控制台会把这组配置以该名称存起来,之后就能在 “保存的连接设置” 下拉里选,直接复用。
- 管理配置:方便区分不同连接配置(比如有多个 H2 库,或同库不同模式、用户的连接),看到名称就知道对应哪套参数,也能通过 “删除” 按钮,移除不用的配置。
驱动类(Driver Class)
指定用于连接 H2 数据库的 JDBC 驱动程序类。
org.h2.Driver
是 H2 数据库官方提供的、实现了 JDBC 规范的驱动类,Java 程序通过加载这个类,才能建立和 H2 数据库的连接,让 Java 代码借助 JDBC API 操作 H2 数据库(执行 SQL 语句、管理数据等 )。在 Java 代码里,通常会用Class.forName("org.h2.Driver")
加载该驱动(不过现在部分框架能自动加载 ),然后基于此驱动去获取数据库连接。JDBC URL
定义连接 H2 数据库的具体路径、模式及相关配置参数,告诉程序该如何找到并连接到 H2 数据库
它是符合 JDBC 规范的连接字符串,H2 支持多种连接模式(嵌入式、服务器模式等 ),不同模式对应不同格式这个后面细嗦
用户名和密码和mysql的都一样,H2 里用户名不区分大小写,但密码区分,创建数据库的用户默认成为该数据库管理员,拥有对应管理权限,而且 H2 可以把可把密码字段留空
在Web控制台中,你需要配置连接参数
1 | 驱动类: org.h2.Driver |
例如,jdbc:h2:D:/software/h2/data/test
,会在指定路径下创建一个名为
test.mv.db
的数据库,你可能还会看到一个
test.trace.db
的文件,这个是h2
的错误信息,可以直接打开看。有没有跟我一样好奇为啥名称里面有个mv
,这是因为高版本的H2
存储引擎默默认为mvStore
,文档最后面有讲这个存储引擎的。
设置好配置好,点击”连接”就可以连接上服务器,页面就是这样的

与外部数据库工具连接
这里我以 DataGrip 进行演示,其实这些工具都一样
连接数据源时候选择 H2

填写基本信息

创建表进行测试
正如上述所说,H2数据库支持原生的sql,这个也支持别的,包括非关系型,跟你在创建数据库时候的配置有关
以常见的 “用户信息表” 和 “订单表” 为例,演示关系型数据的创建:
1 | CREATE TABLE users ( |
执行后看到左边出现了USERS表
冷知识,H2数据库的控制台UI界面是用纯 Swing 写的

1 | CREATE TABLE orders ( |
执行方式:将 SQL 语句复制到控制台输入框,点击
执行 按钮(或按 F9),成功后会显示
更新: 0
(创建表不涉及数据行更新)
注意 H2 不支持 InnoDB 索引
插入数据测试
向用户表插入数据
1 | INSERT INTO users (username, email, password) VALUES |

向订单表插入数据
1 | INSERT INTO orders (user_id, order_amount, order_status, order_time) VALUES |

查找测试
简单查询
1 | -- 通过 user_id 关联 users 表和 orders 表,查询 alice(user_id=1)的所有订单 |

复杂查询
1 | -- 先通过子查询算出每个用户的平均订单金额,再关联查询出金额大于自身平均的订单 |

关闭数据库
点击 [Disconnect]:

来断开连接
在JAVA应用内部,也可以通过代码来实现TCP服务的启动和停止,例子代码如下
1 | import org.h2.tools.Server; |
可以从另外的程序关闭 TCP 服务器,使用下面命令行:
1 | java org.h2.tools.Server -tcpShutdown tcp://localhost:9092 |
在用户应用中关闭服务器,使用
1 | org.h2.tools.Server.shutdownTcpServer("tcp://localhost:9094"); |
这个功能将仅仅关闭 TCP
服务器。如果相同的程序有其他服务器,他们不会被关闭,而是继续执行。为了避免覆盖在数据库下次打开时,在调用这个方法时,所有的到数据库的连接将会关闭。要实现远程关闭服务器,需启用远程连接。关闭一个
TCP 服务器可以通过选项 -tcpPassword
来保护 (启动和关闭 TCP
服务器也要用这个密码)
H2数据库的运行模式与运行方式介绍
运行模式
H2有三种运行模式。
- 内嵌模式(Embedded Mode):内嵌模式下,应用和数据库同在一个JVM中,通过JDBC进行连接。可持久化,但同时只能一个客户端连接。内嵌模式性能会比较好。
- 服务器模式(Server Mode):使用服务器模式和内嵌模式一样,只不过它可以跑在另一个进程里。
- 混合模式:第一个应用以内嵌模式启动它,对于后面的应用来说它是服务器模式跑着的。混合模式是内嵌模式和服务器模式的组合。第一个应用通过内嵌模式与数据库建立连接,同时也作为一个服务器启动,于是另外的应用(运行在不同的进程或是虚拟机上)可以同时访问同样的数据。第一个应用的本地连接与嵌入式模式的连接性能一样的快,而其它连接理论上会略慢。
连接方式
以嵌入式模式(本地文件)连接方式连接H2数据库
这种连接方式默认情况下只允许有一个客户端连接到H2数据库,有客户端连接到H2数据库之后,此时数据库文件就会被锁定,那么其他客户端就无法再连接了。
连接语法:
jdbc:h2:[file:][]<databaseName>
1
2
3JDBC URL: jdbc:h2:~/test
# 或指定完整路径
JDBC URL: jdbc:h2:file:D:/data/mydb例如:
jdbc:h2:~/test
// 连接位于用户目录下的test数据库jdbc:h2:file:/data/sample
jdbc:h2:file:E:/H2/gacl // Windows only
特点:
- 数据持久化到磁盘文件
- 只允许一个连接
- 性能较好
- 会生成.mv.db文件
使用TCP/IP的服务器模式(远程连接)方式连接H2数据库(推荐)
这种连接方式就和其他数据库类似了,是基于Service的形式进行连接的,因此允许多个客户端同时连接到H2数据库。
连接语法:
jdbc:h2:tcp://<server>[:<port>]/[<path>]<databaseName>
举例:
jdbc:h2:tcp://localhost/~/test
特点:
- 支持多客户端同时连接
- 需要先启动H2服务器
- 通过TCP/IP通信
- 适合多用户环境
H2数据库的内存模式,纯内存数据库
连接语法:
jdbc:h2:mem:<databaseName>
- H2数据库被称为内存数据库,因为它支持在内存中创建数据库和表。
- 注意:如果使用H2数据库的内存模式,那么我们创建的数据库和表都只是保存在内存中,一旦服务器重启,那么内存中的数据库和表就不存在了。所以 H2 数据库的持久化是个很关键的问题
可选配置
在用户目录下新建 .h2.server.properties,支持如下属性配置:
webAllowOthers
: 是否允许远程连接,默认 false。webPort
: h2 端口,默认为 8082。webSSL
: 是否启用 SSL 加密连接,默认 false。webAdminPassword
: 超级管理员密码。
如果没有手动配置此文件,以 web-server
方式首次启动 H2
后,点击打开的浏览器页面的 Save 按钮后就会自动创建一个。
端口配置
如果8082端口冲突,可以修改启动参数:
1 | java -jar h2-1.4.200.jar -webPort 8083 |
允许远程访问
默认只允许本地访问,开启远程访问:
1 | java -jar h2-1.4.200.jar -webAllowOthers |
配置文件方式
在用户目录创建.h2.server.properties
:
1 | webAllowOthers=true |
在你的 Spring 项目中使用 H2 数据库
这一次经过 H2 的基本了解之后,我们就需要在实际项目中使用 H2 这种小巧的数据库方便我们的开发测试了
在 Spring Boot 项目里使用 H2 数据库,能让我们便捷地进行开发、测试,特别是在需要轻量级数据库支持,或者想快速验证数据操作逻辑时,H2 是个很不错的选择。使用一个项目来了解如何使用
而且,H2 数据库支持 Hibernate 3.1及以上的版本。 你能够使用 HSQLDB 方言,或是 H2 自己的方言。注意的是,在 Hibernate 中包含的 H2 方言有BUG,针对这些 BUG 的补丁已经被发布和修复,见https://hibernate.atlassian.net/browse/HHH-3401。你能够将它改名为H2Dialect.java,直接把它包含在你的应用中即可使用,或者使用 已经修复了这个问题的 Hibernate 的版本。
当使用 Hibernate,尝试使用 H2Dialect
如果可能的话。当使用H2Dialect
,兼容性模式比如MODE=MySQL
是不支持的。当使用兼容模式时,使用
Hibernate 相应的数据库的方言,而不是 H2Dialect
;但请注意 H2
不支持所有数据库的所有功能。
我们在 Spring Boot 项目中(Servlet的Web应用也大差不差)使用 H2
数据库的思路如下,最简单(目前)的方法就是将数据库内嵌到应用中,这样就意味着应用启动的时候就打开了一个连接,数据库能被多个
session 和应用访问,他们跟应用运行在一个进程内,大部分的 Servlet
容器只适用一个进程(如Tomcat),这些容器都是没有问题的,Tomcat
使用多线程和多类加载器。如果多个应用同时访问同一个数据库,你需要将数据库的
jar
文件放在shared/lib
或是server/lib
目录。好的方案是
Web 应用启动时打开数据库,Web
应用停止时关闭数据库。如果是多个应用,只需要一个应用来处理启动和关闭。好的方案是一个
Session
一个连接,或者是一个请求(action)一个连接,连接使用完后尽可能的关闭它,当然不关闭并不会引起可怕的后果。服务器模式是差不多的,但是它可以运行在其他的进程中。
添加依赖
在 pom.xml
文件中添加 H2 数据库和 Spring Boot
数据访问相关依赖
1 | <!-- Spring Boot 数据访问基础依赖,用于简化数据库操作 --> |
spring-boot-starter-data-jpa
是 Spring Boot
提供的用于简化 JPA(Java Persistence API,Java 持久化 API )操作的
starter 依赖,它能帮我们快速实现对数据库的各种操作;h2
依赖则引入了 H2 数据库的相关类和功能。
配置 application.properties
1 | # 配置 H2 数据库的 JDBC 连接 URL,这里用嵌入式模式,数据存储在项目根目录下的 test.mv.db 文件中 |
如果是yml,如下
1 | spring: |
创建实体类
比如针对前面的 users
表和 orders
表,创建对应的实体类
User 实体类
1 | package edu.software.ergoutree.h2andspringboottest.entity; |
Order 实体类
1 | package edu.software.ergoutree.h2andspringboottest.entity; |
创建 Repository 接口(JPA Repository)
通过继承 JpaRepository
来实现对数据库的基本操作,无需手动编写 SQL 语句(当然也支持自定义 SQL
)。
UsersRepository:
1 | package edu.software.ergoutree.h2andspringboottest.repository; |
OrdersRepository
1 | import edu.software.ergoutree.h2andspringboottest.entity.Orders; |
JpaRepository
提供了诸如 save
(保存实体
)、findAll
(查询所有实体 )、delete
(删除实体
)等基础方法;自定义方法像 findByUsername
是按照 JPA
的方法命名规则来定义的,框架会自动生成对应的 SQL 语句执行查询操作。
编写业务逻辑和控制器(Service 和 Controller)
UsersService
1 | package edu.software.ergoutree.h2andspringboottest.service; |
OrdersService
1 | package edu.software.ergoutree.h2andspringboottest.service; |
UsersController
1 | package edu.software.ergoutree.h2andspringboottest.controller; |
OrdersController
1 | package edu.software.ergoutree.h2andspringboottest.controller; |
H2 数据库控制台的使用
H2 控制台是一个包含在 web 服务中的独立的应用
前面在配置里启用了 H2 控制台,启动 Spring Boot 项目后,在浏览器中访问
http://localhost:8080/h2-console
(具体路径根据配置的
spring.h2.console.path
而定 ),会出现登录页面,填写对应的
JDBC URL、用户名、密码(就是配置文件里 spring.datasource
相关的配置 ),登录后就能看到数据库的表结构、执行 SQL
语句、查看数据等。比如可以在控制台里执行
SELECT * FROM users;
来查询用户表数据,方便开发过程中调试和检查数据。
冷知识,H2支持原生的CSV
CSV(逗号分隔文件)文件在数据库系统中支持
CSVREAD
和CSVWRITE
方法,也可以把它作为数据库之外的一个工具来使用。 将数据库查询结果写成CSV文件

可以看到访问网址就来到了如上控制台页面,和上面的都是一样的,输入你配置文件中的密码,就可以进入了,进入后发现 Hibernate 为我们的实体类已经自动创建表
重新写一个Home定向页面,来看看各个该Spring程序的各个功能进行测试
可以看到是没有任何问题的


在 Spring Boot 与 H2 的搭配中使用连接池
H2
数据库的连接池机制通过复用数据库连接,避免了频繁创建和关闭连接的开销。H2
内置连接池基于Mini Connection Pool
Manager,相比直接使用DriverManager.getConnection()
有以下优势:
- 性能提升:连接池管理的连接获取速度比原生方式快 2 倍以上
- 资源复用:维护一定数量的活跃连接,避免重复握手开销
- 连接管理:自动处理连接超时、异常回收等场景
在 Spring Boot 中配置 H2 内置连接池只需在数据源配置中添加连接池参数:
1 | spring: |
关键参数说明:
POOL_SIZE
:连接池最大连接数(默认 10)AUTO_COMMIT
:是否自动提交事务(默认 true)MAX_IDLE_TIME
:连接最大空闲时间(毫秒,默认 60000)CONNECTION_TIMEOUT
:获取连接超时时间(毫秒,默认 30000)
若需更强大的连接池功能,可集成 Apache Commons DBCP
1 | <dependency> |
我使用 Spring Boot 默认的 Hikari 数据池添加如下控制器和服务层
1 | package edu.software.ergoutree.h2andspringboottest.controller; |
1 | package edu.software.ergoutree.h2andspringboottest.service; |
可以发现 H2 数据库成功连接到连接池,并且进行了数据访问


测试
可以编写单元测试或集成测试来验证数据库操作是否正确。比如使用
SpringBootTest
注解进行集成测试
测试才是在 Spring Boot 中使用内存数据库的精髓之处,因为H2 数据库作为轻量级内存数据库,在 Spring Boot 测试体系中具备不可替代的优势:
- 隔离性测试环境:每次测试启动时创建独立数据库实例,测试结束后自动销毁,避免真实数据库的数据污染
- 提升测试效率:内存中运行速度极快,相比传统数据库可减少 70% 以上的测试启动时间
- 简化测试配置:无需部署独立数据库服务,通过纯 Java 驱动即可完成测试环境搭建
- 精准测试验证:支持 SQL 标准语法,能准确模拟生产环境的数据库操作逻辑
在 Spring Boot 测试体系中,H2 内存数据库的应用不仅仅是 “替代真实数据库”,其核心价值体现在内存数据库的轻量级特性大幅缩短 CI/CD 流水线时间,而且无需维护独立数据库实例,测试脚本可随代码一同版本管理。
这时候,可以在测试专用的配置文件下写一个如下配置项来保持测试时候数据库表的隔离性
1 | spring.jpa.hibernate.ddl-auto=create-drop # 测试时自动创建/删除表结构 |
可以进行的测试如下,当然,数据库的CURD测试可以做到,那么控制器层,接口测试和集成测试也就相对应的可以完美实现。
基于 @DataJpaTest 的单元测试(Repository 层)
UsersRepositoryTest - 测试用户仓库的CRUD操作
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116package edu.software.ergoutree.h2andspringboottest.repository;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.DirtiesContext;
import edu.software.ergoutree.h2andspringboottest.entity.Users;
public class UsersRepositoryTest {
private UsersRepository usersRepository;
public void setup() {
// 清空用户表
usersRepository.deleteAll();
}
public void testSaveUser() {
// 准备测试数据
Users user = new Users();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPassword("password123");
user.setCreateTime(Timestamp.from(Instant.now()));
// 保存用户
Users savedUser = usersRepository.save(user);
// 验证结果
assertThat(savedUser.getUserId()).isNotNull();
assertThat(savedUser.getUsername()).isEqualTo("testuser");
assertThat(savedUser.getEmail()).isEqualTo("test@example.com");
}
public void testFindByUsername() {
// 准备测试数据
Users user1 = new Users();
user1.setUsername("user1");
user1.setEmail("user1@example.com");
user1.setPassword("password1");
user1.setCreateTime(Timestamp.from(Instant.now()));
usersRepository.save(user1);
Users user2 = new Users();
user2.setUsername("user2");
user2.setEmail("user2@example.com");
user2.setPassword("password2");
user2.setCreateTime(Timestamp.from(Instant.now()));
usersRepository.save(user2);
// 测试根据用户名查找
Users foundUser = usersRepository.findByUsername("user1");
// 验证结果
assertThat(foundUser).isNotNull();
assertThat(foundUser.getEmail()).isEqualTo("user1@example.com");
}
public void testFindAll() {
// 准备测试数据
Users user1 = new Users();
user1.setUsername("user1");
user1.setEmail("user1@example.com");
user1.setPassword("password1");
user1.setCreateTime(Timestamp.from(Instant.now()));
usersRepository.save(user1);
Users user2 = new Users();
user2.setUsername("user2");
user2.setEmail("user2@example.com");
user2.setPassword("password2");
user2.setCreateTime(Timestamp.from(Instant.now()));
usersRepository.save(user2);
// 测试查找所有用户
List<Users> allUsers = usersRepository.findAll();
// 验证结果
assertThat(allUsers).hasSize(2);
assertThat(allUsers).extracting(Users::getUsername).containsExactlyInAnyOrder("user1", "user2");
}
public void testDeleteUser() {
// 准备测试数据
Users user = new Users();
user.setUsername("userToDelete");
user.setEmail("delete@example.com");
user.setPassword("password");
user.setCreateTime(Timestamp.from(Instant.now()));
Users savedUser = usersRepository.save(user);
// 删除用户
usersRepository.deleteById(savedUser.getUserId());
// 验证结果
Optional<Users> deletedUser = usersRepository.findById(savedUser.getUserId());
assertThat(deletedUser).isEmpty();
}
}OrdersRepositoryTest - 测试订单仓库的api可用性
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137package edu.software.ergoutree.h2andspringboottest.controller;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.software.ergoutree.h2andspringboottest.entity.Orders;
import edu.software.ergoutree.h2andspringboottest.entity.Users;
import edu.software.ergoutree.h2andspringboottest.service.OrdersService;
import edu.software.ergoutree.h2andspringboottest.service.UsersService;
public class OrdersControllerTest {
private MockMvc mockMvc;
private OrdersService ordersService;
private UsersService usersService;
private ObjectMapper objectMapper;
private Users testUser;
public void setup() {
// 创建测试用户
testUser = new Users();
testUser.setUsername("orderControllerTestUser");
testUser.setEmail("ordercontroller@example.com");
testUser.setPassword("password");
testUser.setCreateTime(Timestamp.from(Instant.now()));
testUser = usersService.saveUser(testUser);
}
public void testGetOrderById() throws Exception {
// 准备测试数据
Orders order = new Orders();
order.setUser(testUser);
order.setOrderAmount(new BigDecimal("123.45"));
order.setOrderStatus("processing");
order.setOrderTime(Timestamp.from(Instant.now()));
Orders savedOrder = ordersService.saveOrder(order);
// 执行GET请求并验证结果
mockMvc.perform(get("/api/orders/{id}", savedOrder.getOrderId()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.orderAmount", is(123.45)))
.andExpect(jsonPath("$.orderStatus", is("processing")));
}
public void testGetOrdersByUserIdAndStatus() throws Exception {
// 准备测试数据
Orders order1 = new Orders();
order1.setUser(testUser);
order1.setOrderAmount(new BigDecimal("100.00"));
order1.setOrderStatus("pending");
order1.setOrderTime(Timestamp.from(Instant.now()));
ordersService.saveOrder(order1);
Orders order2 = new Orders();
order2.setUser(testUser);
order2.setOrderAmount(new BigDecimal("200.00"));
order2.setOrderStatus("pending");
order2.setOrderTime(Timestamp.from(Instant.now()));
ordersService.saveOrder(order2);
Orders order3 = new Orders();
order3.setUser(testUser);
order3.setOrderAmount(new BigDecimal("300.00"));
order3.setOrderStatus("completed");
order3.setOrderTime(Timestamp.from(Instant.now()));
ordersService.saveOrder(order3);
// 执行GET请求并验证结果
mockMvc.perform(get("/api/orders/user/{userId}/status/{status}", testUser.getUserId(), "pending"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[*].orderAmount", containsInAnyOrder(100.00, 200.00)))
.andExpect(jsonPath("$[*].orderStatus", everyItem(is("pending"))));
}
public void testCreateOrder() throws Exception {
// 准备测试数据
Orders order = new Orders();
order.setUser(testUser);
order.setOrderAmount(new BigDecimal("999.99"));
order.setOrderStatus("new");
order.setOrderTime(Timestamp.from(Instant.now()));
// 执行POST请求并验证结果
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(order)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.orderAmount", is(999.99)))
.andExpect(jsonPath("$.orderStatus", is("new")));
}
public void testGetNonExistentOrder() throws Exception {
// 执行GET请求并验证结果 - 应该返回404
mockMvc.perform(get("/api/orders/999"))
.andExpect(status().isNotFound());
}
}
可以发现 H2 数据库做测试是很方便而且环境高度隔离的
基于 @SpringBootTest 的集成测试(全流程验证)
1 | package edu.software.ergoutree.h2andspringboottest.integration; |

可以从上述测试过程中看出,从仓库到控制器到项目集成测试的各个层次,充分展示了H2数据库在Spring Boot项目中的测试优势。