Article
第一章:H2数据库概述
1.1 什么是H2数据库
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| H2数据库 | 一个用 Java 编写的开源关系型数据库管理系统(RDBMS),轻量、快速、支持标准 SQL。 | 仅适用于 JVM 环境(如 Java、Kotlin、Scala)。 |
| 开源协议 | 基于 Mozilla Public License 2.0(MPL 2.0)和 Eclipse Public License(EPL)。 | 可免费用于商业和开源项目。 |
| 作者与维护 | 由 Thomas Mueller 开发并维护,社区活跃。 | 官方网站为 h2database.com。 |
| 核心目标 | 提供嵌入式、内存或服务器模式的轻量级数据库,适合开发、测试和小型应用。 | 不适合高并发、大规模生产 OLTP 场景。 |
1.2 H2的主要特性与优势
| 特性名称 | 说明 | 注意事项 |
|---|---|---|
| 纯 Java 实现 | 无需本地依赖,跨平台运行,只需 JRE。 | 启动快,部署简单。 |
| 多种运行模式 | 支持嵌入式(in-process)、服务器(TCP/IP)、混合模式。 | 可灵活切换,但连接字符串不同。 |
| 内存数据库支持 | 可在内存中创建临时数据库,性能极高。 | 进程结束后数据丢失,适合测试。 |
| 兼容主流 SQL 标准 | 支持大部分 ANSI SQL-92/99/2003,兼容 MySQL、PostgreSQL、Oracle 语法模式。 | 需通过 MODE 参数启用兼容模式。 |
| Web 控制台 | 内置基于浏览器的管理界面(H2 Console),便于调试和管理。 | 默认端口 8082,需注意安全暴露。 |
| 加密与安全 | 支持数据库文件加密(AES)、用户认证。 | 加密会带来一定性能开销。 |
| 自动重连与恢复 | 文件模式下支持崩溃后自动恢复(Write-Ahead Logging)。 | 需启用 LOG=TRUE(默认开启)。 |
| 小体积 | JAR 包通常小于 2MB,适合嵌入到应用中。 | 功能完整但资源占用极低。 |
1.3 H2的运行模式(嵌入式、服务器、混合)
| 模式名称 | 说明 | 连接字符串示例 | 注意事项 |
|---|---|---|---|
| 嵌入式模式 | 数据库与应用程序运行在同一 JVM 进程中,无网络开销,最快访问速度。 | jdbc:h2:~/testjdbc:h2:mem:test | 仅当前应用可访问;内存模式数据不持久。 |
| 服务器模式 | H2 作为独立 TCP 服务运行,多个客户端可通过网络连接。 | jdbc:h2:tcp://localhost/~/test | 需先启动 H2 TCP Server;支持多进程访问。 |
| 混合模式 | 先以嵌入式方式启动,再启动 TCP 服务,允许本地和远程同时访问。 | 嵌入式连接 + 启动 org.h2.tools.Server.createTcpServer() | 常用于开发调试,兼顾本地性能与远程访问。 |
| 内存模式 | 数据仅存在于内存中,关闭连接后自动销毁(除非使用 named memory DB)。 | jdbc:h2:mem:testdbjdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 | 使用 DB_CLOSE_DELAY=-1 可保持内存数据库存活。 |
1.4 适用场景与限制
| 类别 | 说明 | 注意事项 |
|---|---|---|
| 适用场景 | ||
| 单元/集成测试 | 替代真实数据库进行快速、隔离的测试,无需外部依赖。 | Spring Boot 默认测试数据库。 |
| 原型开发 | 快速搭建 MVP 或演示系统,无需配置复杂数据库。 | 可配合 H2 Console 实时查看数据。 |
| 桌面应用 | 嵌入到 Java 桌面程序中作为本地存储(如配置、日志、缓存)。 | 文件模式可持久化。 |
| 教学与学习 | 学习 SQL 和 JDBC 的理想工具,启动简单、文档齐全。 | 支持多种 SQL 方言,便于对比学习。 |
| 限制与不足 | ||
| 并发能力有限 | 虽支持多线程,但高并发写入性能不如 PostgreSQL、MySQL。 | 不建议用于高负载生产环境。 |
| 功能精简 | 缺少高级功能(如分区表、复杂复制、集群)。 | 适合轻量级需求。 |
| 社区规模 | 相比主流数据库,社区和第三方工具生态较小。 | 遇到问题主要依赖官方文档。 |
| 数据规模 | 单文件数据库,适合 GB 级以下数据;超大表性能下降明显。 | 可通过索引优化,但非 OLAP 场景首选。 |
第二章:安装与配置
2.1 下载与安装H2
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 访问官网 | 打开 H2 官方网站:https://www.h2database.com | 确保使用最新稳定版本(如 2.x)。 |
| 下载 JAR 包 | 点击 “Download” 链接,下载 h2-*.jar 文件(通常为 h2-2.x.x.jar) | 无需解压,单个 JAR 即可运行。 |
| 验证 Java 环境 | 确保系统已安装 JRE 或 JDK(建议 Java 8+) | 执行 java -version 确认版本。 |
| 运行 H2 | 在命令行执行:java -jar h2-*.jar | 默认会启动 Web Console 并打开浏览器。 |
| 添加到项目依赖 | Maven:<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version></dependency>Gradle: implementation 'com.h2database:h2:2.2.224' | 测试环境建议使用 testImplementation(Gradle)或 scope=test(Maven)。 |
2.2 启动H2控制台(Web Console)
| 步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 命令行启动 | 执行:java -cp h2-*.jar org.h2.tools.Server -web | 默认监听 http://localhost:8082 |
| 指定端口 | 使用 -webPort 参数:java -cp h2-*.jar org.h2.tools.Server -web -webPort 9090 | 避免端口冲突。 |
| 启动后自动打开浏览器 | 默认行为;若未打开,手动访问 http://localhost:8082 | 首次使用需设置 JDBC URL、用户名、密码。 |
| 控制台功能 | 支持 SQL 执行、表结构查看、数据浏览、执行计划分析等。 | 不建议在生产环境暴露 Web Console。 |
| 关闭控制台 | 关闭命令行窗口,或按 Ctrl+C 终止进程。 | 控制台独立于数据库引擎,关闭不影响嵌入式数据库。 |
2.3 配置H2运行环境(内存/文件模式)
| 模式类型 | 配置方式 | 示例路径/参数 | 注意事项 |
|---|---|---|---|
| 内存模式 | JDBC URL 中使用 mem: 前缀 | jdbc:h2:mem:testdb | 数据在 JVM 生命周期内存在;默认关闭连接即销毁。 |
| 持久化内存 | 添加 DB_CLOSE_DELAY=-1 保持内存数据库不随连接关闭而销毁 | jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 | 适用于单元测试中多个测试类共享内存库。 |
| 文件模式 | 使用相对或绝对路径(无前缀或 file:) | jdbc:h2:~/mydbjdbc:h2:/data/mydb | 自动创建 .mv.db 和 .trace.db 文件。 |
| 自动创建 | 首次连接时若数据库不存在,H2 自动创建 | 无需预先建库 | 确保应用有写权限。 |
| 只读模式 | 添加 ;ACCESS_MODE_DATA=r | jdbc:h2:~/mydb;ACCESS_MODE_DATA=r | 防止意外写入,适合只读查询场景。 |
| 压缩与恢复 | 默认启用 MVStore 引擎,支持崩溃恢复 | 无需额外配置 | 日志文件(.trace.db)可用于调试。 |
2.4 连接字符串详解
| 参数名称 | 语法格式 | 用途说明 | 示例 |
|---|---|---|---|
| 基础路径 | jdbc:h2:[path] | 指定数据库位置(内存、文件、远程) | jdbc:h2:mem:test |
| 内存数据库 | jdbc:h2:mem:name | 创建命名内存数据库 | jdbc:h2:mem:myapp |
| 用户目录 | jdbc:h2:~/name | 在用户主目录下创建数据库文件 | jdbc:h2:~/appdb |
| 绝对路径 | jdbc:h2:/absolute/path/name | 指定完整文件路径 | jdbc:h2:/opt/data/mydb |
| TCP 服务器连接 | jdbc:h2:tcp://host[:port]/[path] | 连接远程 H2 服务器 | jdbc:h2:tcp://localhost/~/remotedb |
| 兼容模式 | ;MODE=MySQL / PostgreSQL / Oracle | 模拟其他数据库的 SQL 语法和函数 | jdbc:h2:mem:test;MODE=MySQL |
| 自动重连 | ;AUTO_RECONNECT=TRUE | 连接断开后自动重试 | 默认 false |
| 初始脚本 | ;INIT=RUNSCRIPT FROM '...sql' | 连接时执行初始化 SQL 脚本 | ;INIT=RUNSCRIPT FROM 'classpath:init.sql' |
| 不自动提交 | ;AUTOCOMMIT=FALSE | 关闭自动提交,需手动 COMMIT | 用于事务测试 |
| 忽略大小写 | ;DATABASE_TO_UPPER=FALSE | 表名、列名保留原始大小写 | 默认 true(转大写) |
| 加密数据库 | ;CIPHER=AES | 启用 AES 加密(需配合 FILEPASSWORD 和 PASSWORD) | 需额外配置密钥 |
| 延迟关闭 | ;DB_CLOSE_DELAY=秒数 或 -1 | 控制内存数据库在最后连接关闭后保留时间(-1 表示永不清除) | ;DB_CLOSE_DELAY=-1 |
| 禁用跟踪日志 | ;TRACE_LEVEL_FILE=0 | 减少磁盘 I/O,提升性能 | 默认为 2(记录详细日志) |
注意: 多个参数用分号
;连接,顺序无关。例如:jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=0
第三章:SQL语法支持
3.1 DDL语句(CREATE, ALTER, DROP)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| CREATE TABLE | CREATE TABLE table_name (col_def [, ...]) [AS select] | 创建新表 | CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50)); | 支持自增列:ID INT AUTO_INCREMENT;主键可省略 CONSTRAINT 名称 |
| CREATE TABLE AS | CREATE TABLE new_table AS SELECT ... | 基于查询结果创建表 | CREATE TABLE active_users AS SELECT * FROM users WHERE active = 1; | 新表结构由 SELECT 推断,不复制索引或约束 |
| ALTER TABLE ADD COLUMN | ALTER TABLE table_name ADD COLUMN col_def | 添加新列 | ALTER TABLE users ADD COLUMN email VARCHAR(100); | 列默认值可用 DEFAULT 指定 |
| ALTER TABLE DROP COLUMN | ALTER TABLE table_name DROP COLUMN col_name | 删除列 | ALTER TABLE users DROP COLUMN temp_flag; | 不支持同时删除多个列(需逐条执行) |
| ALTER TABLE ALTER COLUMN | ALTER TABLE table_name ALTER COLUMN col_name {SET DATA TYPE | SET DEFAULT | DROP DEFAULT} | 修改列属性 | ALTER TABLE users ALTER COLUMN name SET DATA TYPE VARCHAR(100); | 不能直接重命名列(需用 RENAME COLUMN) |
| ALTER TABLE RENAME COLUMN | ALTER TABLE table_name RENAME COLUMN old TO new | 重命名列 | ALTER TABLE users RENAME COLUMN uname TO username; | H2 2.x 起支持 |
| DROP TABLE | DROP TABLE [IF EXISTS] table_name [CASCADE | RESTRICT] | 删除表 | DROP TABLE IF EXISTS users CASCADE; | CASCADE 自动删除依赖视图;默认 RESTRICT |
| CREATE INDEX | CREATE [UNIQUE] INDEX index_name ON table (col [, ...]) | 创建索引 | CREATE INDEX idx_user_name ON users(name); | 支持表达式索引:CREATE INDEX idx_upper ON users(UPPER(name)) |
| DROP INDEX | DROP INDEX [IF EXISTS] index_name | 删除索引 | DROP INDEX IF EXISTS idx_user_name; | 索引名在数据库范围内唯一 |
| CREATE VIEW | CREATE [OR REPLACE] VIEW view_name AS SELECT ... | 创建视图 | CREATE VIEW user_summary AS SELECT id, name FROM users; | 视图不可更新(除非简单投影) |
| DROP VIEW | DROP VIEW [IF EXISTS] view_name | 删除视图 | DROP VIEW IF EXISTS user_summary; | 若被其他对象引用,需 CASCADE |
3.2 DML语句(INSERT, UPDATE, DELETE)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| INSERT INTO | INSERT INTO table VALUES (vals) | SELECT ... | 插入数据 | INSERT INTO users (name) VALUES ('Alice'); | 支持批量插入:VALUES (...), (...), (...) |
| INSERT IGNORE | INSERT IGNORE INTO ... | 忽略主键/唯一冲突(需兼容模式) | INSERT IGNORE INTO users (id, name) VALUES (1, 'Bob'); | 需启用 MODE=MySQL 才支持 IGNORE |
| UPDATE | UPDATE table SET col = expr [, ...] [WHERE cond] | 更新记录 | UPDATE users SET name = 'Charlie' WHERE id = 1; | 无 WHERE 会更新全表,谨慎使用 |
| UPDATE with JOIN | UPDATE table1 SET ... FROM table2 WHERE join_cond | 多表更新(H2 特有语法) | UPDATE orders SET total = p.price FROM products p WHERE orders.pid = p.id; | 标准 SQL 不支持,H2 提供扩展 |
| DELETE | DELETE FROM table [WHERE cond] | 删除记录 | DELETE FROM users WHERE id = 1; | 无 WHERE 删除全表 |
| TRUNCATE TABLE | TRUNCATE TABLE table_name | 快速清空表(重置自增计数器) | TRUNCATE TABLE logs; | 比 DELETE FROM 更高效,不可回滚(自动 COMMIT) |
3.3 查询语句(SELECT, JOIN, 子查询)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| SELECT | SELECT [DISTINCT] cols FROM table [WHERE | GROUP BY | HAVING | ORDER BY | LIMIT] | 基本查询 | SELECT name FROM users WHERE age > 18 ORDER BY name LIMIT 10; | LIMIT 支持 OFFSET:LIMIT 10 OFFSET 20 |
| INNER JOIN | SELECT ... FROM t1 INNER JOIN t2 ON cond | 内连接 | SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id; | 支持 USING(col) 简写 |
| LEFT JOIN | SELECT ... FROM t1 LEFT JOIN t2 ON cond | 左外连接 | SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id; | 右表无匹配时填充 NULL |
| RIGHT JOIN | SELECT ... FROM t1 RIGHT JOIN t2 ON cond | 右外连接 | SELECT * FROM t1 RIGHT JOIN t2 ON t1.id = t2.ref_id; | 较少使用,可用 LEFT JOIN 重写 |
| FULL OUTER JOIN | SELECT ... FROM t1 FULL JOIN t2 ON cond | 全外连接 | SELECT * FROM t1 FULL JOIN t2 ON t1.id = t2.id; | H2 支持 FULL JOIN |
| 子查询(标量) | SELECT (SELECT MAX(age) FROM users) AS max_age; | 返回单值 | SELECT name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count FROM users u; | 性能可能较差,建议用 JOIN 优化 |
| 子查询(IN) | SELECT * FROM t1 WHERE col IN (SELECT ...) | 成员判断 | SELECT * FROM users WHERE id IN (SELECT user_id FROM orders); | 支持 NOT IN |
| EXISTS 子查询 | SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE ...) | 存在性判断 | SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id); | 比 IN 更高效,尤其对 NULL 安全 |
| WITH (CTE) | WITH cte AS (SELECT ...) SELECT * FROM cte; | 公共表表达式 | WITH active_users AS (SELECT * FROM users WHERE active = 1) SELECT * FROM active_users; | 支持递归 CTE(需指定 SEARCH 或 CYCLE) |
3.4 内置函数与表达式
| 函数类别 | 函数名称 | 语法 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|---|
| 字符串 | UPPER / LOWER | UPPER(str) | 大小写转换 | SELECT UPPER(name) FROM users; | 支持多字节字符 |
| 字符串 | SUBSTRING | SUBSTRING(str FROM start FOR len) | 截取子串 | SELECT SUBSTRING(name FROM 1 FOR 3) FROM users; | 起始位置从 1 开始 |
| 字符串 | CONCAT | CONCAT(str1, str2, ...) | 字符串拼接 | SELECT CONCAT(first, ' ', last) FROM users; | 也支持 || 运算符 |
| 数值 | ROUND / CEIL / FLOOR | ROUND(x, d) | 四舍五入等 | SELECT ROUND(price, 2) FROM products; | d 为小数位数 |
| 日期时间 | NOW() / CURRENT_TIMESTAMP | NOW() | 当前时间戳 | SELECT NOW(); | 返回 TIMESTAMP 类型 |
| 日期时间 | DATEADD | DATEADD(unit, amount, date) | 日期加减 | SELECT DATEADD('DAY', 7, NOW()); | unit 如 'DAY', 'MONTH' |
| 聚合 | COUNT / SUM / AVG / MIN / MAX | COUNT(*) | 聚合计算 | SELECT AVG(age) FROM users; | 支持 DISTINCT:COUNT(DISTINCT col) |
| 条件 | CASE WHEN | CASE WHEN cond THEN val ELSE val END | 条件表达式 | SELECT CASE WHEN age < 18 THEN 'Minor' ELSE 'Adult' END FROM users; | 支持简单 CASE:CASE col WHEN 1 THEN ... |
| 系统 | DATABASE() | DATABASE() | 返回当前数据库名 | SELECT DATABASE(); | 用于调试 |
| 序列 | NEXTVAL | NEXTVAL('seq_name') | 获取序列下一个值 | SELECT NEXTVAL('user_seq'); | 需先 CREATE SEQUENCE |
注: H2 还支持正则(REGEXP)、JSON(H2 2.0+)、窗口函数(
ROW_NUMBER(),RANK())等高级功能。
3.5 事务控制(BEGIN, COMMIT, ROLLBACK)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| BEGIN / START TRANSACTION | BEGIN; 或 START TRANSACTION; | 显式开始事务 | BEGIN;UPDATE accounts SET balance = balance - 100 WHERE id = 1;COMMIT; | 默认自动提交(AUTOCOMMIT=TRUE),需关闭才生效 |
| COMMIT | COMMIT; | 提交当前事务 | COMMIT; | 持久化所有更改 |
| ROLLBACK | ROLLBACK; | 回滚当前事务 | ROLLBACK; | 撤销所有未提交更改 |
| SAVEPOINT | SAVEPOINT name; | 设置保存点 | SAVEPOINT sp1;...ROLLBACK TO sp1; | 支持部分回滚 |
| ROLLBACK TO | ROLLBACK TO SAVEPOINT name; | 回滚到保存点 | ROLLBACK TO sp1; | 保存点之后的操作被撤销,之前仍保留 |
| SET AUTOCOMMIT | SET AUTOCOMMIT {TRUE | FALSE}; | 控制自动提交模式 | SET AUTOCOMMIT FALSE; | JDBC 中通常通过 Connection.setAutoCommit() 控制 |
| 事务隔离级别 | SET LOCK_MODE {0|1|2|3};或 SET TRANSACTION ISOLATION LEVEL ... | 设置并发控制行为 | SET LOCK_MODE 0; -- 无锁(快照读) | H2 默认使用 MVCC,LOCK_MODE 0 最高性能 |
注意: H2 使用多版本并发控制(MVCC),读操作通常不阻塞写操作。在文件模式下,事务日志确保崩溃恢复。
第四章:Java集成开发
4.1 使用JDBC连接H2
| 方法名称 | 语法/操作 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 加载驱动(可选) | Class.forName("org.h2.Driver") | 显式加载 JDBC 驱动(Java 6+ 通常无需) | Class.forName("org.h2.Driver"); | 现代 JVM 自动发现驱动,可省略 |
| 建立连接 | DriverManager.getConnection(url, user, password) | 获取数据库连接 | Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", ""); | 默认用户名 sa,密码为空 |
| 执行查询 | Statement / PreparedStatement + executeQuery() | 执行 SELECT | PreparedStatement ps = conn.prepareStatement("SELECT name FROM users WHERE id = ?");ps.setInt(1, 1);ResultSet rs = ps.executeQuery(); | 推荐使用 PreparedStatement 防 SQL 注入 |
| 执行更新 | executeUpdate() | 执行 INSERT/UPDATE/DELETE | int rows = stmt.executeUpdate("INSERT INTO users (name) VALUES ('Alice')"); | 返回受影响行数 |
| 关闭资源 | close() on Connection, Statement, ResultSet | 释放数据库资源 | rs.close(); stmt.close(); conn.close(); | 建议使用 try-with-resources 自动关闭 |
| 内存数据库共享 | 使用 named memory DB + DB_CLOSE_DELAY=-1 | 多线程/多连接共享内存库 | "jdbc:h2:mem:shared;DB_CLOSE_DELAY=-1" | 否则每个连接创建独立内存实例 |
4.2 Spring Boot集成H2
| 配置项 | 属性名(application.properties) | 用途 | 示例值 | 注意事项 |
|---|---|---|---|---|
| 数据库驱动 | spring.datasource.driver-class-name | 指定 H2 驱动类 | org.h2.Driver | 若依赖中只有 H2,可省略 |
| JDBC URL | spring.datasource.url | 指定 H2 连接地址 | jdbc:h2:mem:testdbjdbc:h2:~/appdb | 内存模式适合测试,文件模式可持久化 |
| 用户名 | spring.datasource.username | 数据库用户 | sa | H2 默认用户 |
| 密码 | spring.datasource.password | 数据库密码 | (空字符串) | 默认无密码 |
| 启用 Web Console | spring.h2.console.enabled | 开启 H2 控制台 | true | 开发环境使用,生产禁用 |
| 控制台路径 | spring.h2.console.path | 自定义控制台访问路径 | /h2-console | 默认为 /h2-console |
| DDL 自动建表 | spring.jpa.hibernate.ddl-auto | JPA 自动建表策略 | create-drop(测试)update(开发) | create-drop 在关闭时删除表 |
| 方言设置 | spring.jpa.database-platform | 指定 Hibernate 方言 | org.hibernate.dialect.H2Dialect | Spring Boot 2.7+ 可自动推断 |
| 日志 SQL | spring.jpa.show-sql | 打印执行的 SQL | true | 便于调试 |
| 初始化模式 | spring.sql.init.mode | 控制 schema.sql/data.sql 执行 | always / embedded / never | 默认 embedded(仅嵌入式数据库执行) |
典型依赖(Maven):
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
4.3 单元测试中使用H2
| 场景 | 配置方式 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|
| 替换真实数据库 | 使用 @DataJpaTest 或 @SpringBootTest + H2 依赖 | 隔离测试,避免污染生产数据 | @DataJpaTestclass UserRepositoryTest { ... } | Spring Boot 测试默认启用 H2(若在 classpath) |
| 内存数据库隔离 | 每个测试类使用唯一数据库名 | 避免测试间数据干扰 | @TestPropertySource(properties = "spring.datasource.url=jdbc:h2:mem:test1") | 或使用随机名:jdbc:h2:mem:${random.uuid} |
| 自动建表 | 利用 schema.sql / data.sql | 初始化表结构和测试数据 | src/test/resources/schema.sqlsrc/test/resources/data.sql | 文件需放在 test/resources 下 |
| 回滚事务 | 使用 @Transactional on test class | 测试结束后自动回滚 | @DataJpaTest@Transactionalclass ServiceTest { ... } | 默认行为,确保测试幂等 |
| 验证 SQL | 启用 SQL 日志 | 调试生成的查询 | logging.level.org.hibernate.SQL=DEBUG | 可结合 AssertJ 或 TestEntityManager 验证 |
注意: H2 在单元测试中性能高、启动快,是 Spring Boot 官方推荐的测试数据库。
4.4 初始化脚本与数据填充
| 脚本类型 | 文件名(默认) | 触发时机 | 用途 | 示例内容 | 注意事项 |
|---|---|---|---|---|---|
| Schema 初始化 | schema.sql | 应用启动时(在 JPA DDL 之后) | 创建表、索引、视图 | CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50)); | 若使用 JPA ddl-auto=create,可能重复建表 |
| 数据填充 | data.sql | schema.sql 执行后 | 插入初始/测试数据 | INSERT INTO users VALUES (1, 'Admin'); | 仅当 spring.sql.init.mode 允许时执行 |
| 自定义脚本路径 | spring.sql.init.schema-locations | 指定 schema 脚本位置 | 多环境不同初始化 | classpath:db/init/schema-prod.sql | 支持多个路径,逗号分隔 |
| 自定义数据路径 | spring.sql.init.data-locations | 指定 data 脚本位置 | 按 profile 加载数据 | classpath:db/data/test-data.sql | — |
| 条件执行 | spring.sql.init.mode=always/embedded/never | 控制是否执行脚本 | 仅在嵌入式 DB 执行 | embedded(默认) | 生产环境建议 never |
| 编码指定 | spring.sql.init.encoding | 设置脚本文件编码 | 避免中文乱码 | UTF-8 | 默认为系统编码 |
| 分隔符 | spring.sql.init.separator | 自定义语句分隔符 | 支持存储过程等 | ;; | 默认为 ;,但不支持跨行注释内分号 |
执行顺序(Spring Boot):
- JPA/Hibernate 根据
ddl-auto建表(如create-drop) - 执行
schema.sql - 执行
data.sql
提示: 在纯 JDBC 项目中,可通过
INIT参数在连接字符串中执行脚本:jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'classpath:init.sql'
第五章:高级功能
5.1 用户与权限管理
| 操作名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 创建用户 | CREATE USER user_name PASSWORD 'password' [ADMIN] | 新建数据库用户 | CREATE USER alice PASSWORD 'secret'; | ADMIN 用户拥有所有权限 |
| 修改密码 | ALTER USER user_name SET PASSWORD 'new_password' | 更新用户密码 | ALTER USER alice SET PASSWORD 'new123'; | 需具备管理员权限 |
| 删除用户 | DROP USER user_name | 移除用户 | DROP USER alice; | 不能删除当前连接用户 |
| 授予权限 | GRANT {SELECT | INSERT | UPDATE | DELETE | ALL} ON table_name TO user_name | 赋予表级权限 | GRANT SELECT, INSERT ON users TO alice; | 支持 GRANT ALL PRIVILEGES |
| 撤销权限 | REVOKE {privilege} ON table_name FROM user_name | 回收权限 | REVOKE DELETE ON users FROM alice; | 权限变更立即生效 |
| 查看权限 | SELECT * FROM INFORMATION_SCHEMA.USER_PRIVILEGES | 查询用户权限 | SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES WHERE GRANTEE = 'ALICE'; | 权限信息存储在系统表中 |
| 默认用户 | sa(System Administrator) | 初始超级用户 | — | 密码默认为空,建议生产环境修改或禁用 |
| 连接认证 | 使用用户名/密码连接 | 控制访问入口 | jdbc:h2:~/mydb;USER=alice;PASSWORD=secret | 若未启用认证,任何用户可连接 |
注意: H2 的权限模型较简单,不支持角色(ROLE)或行级安全(RLS)。适用于轻量级多用户场景。
5.2 加密数据库
| 功能名称 | 配置方式 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|
| 启用文件加密 | 在 JDBC URL 中指定 CIPHER=AES | 加密 .mv.db 数据文件 | jdbc:h2:~/securedb;CIPHER=AES | 必须同时提供 FILEPASSWORD 和 PASSWORD |
| 设置文件密码 | FILEPASSWORD='file_pass' | 用于加密数据库文件的密钥 | jdbc:h2:~/securedb;CIPHER=AES;FILEPASSWORD='mypass' | 文件密码 ≠ 用户密码 |
| 设置用户密码 | PASSWORD='user_pass' | 数据库用户登录密码 | jdbc:h2:~/securedb;CIPHER=AES;FILEPASSWORD='fp';PASSWORD='up' | 用户密码用于认证,文件密码用于解密存储 |
| 加密算法 | 仅支持 AES(128 位) | 对称加密保护数据 | — | 依赖 Java JCE 策略(默认支持) |
| 性能影响 | I/O 加解密开销 | 安全性 vs 性能权衡 | — | 写入/读取速度下降约 10%~30% |
| 备份限制 | 加密数据库无法直接读取 | 防止未授权访问 | — | 必须通过 H2 使用正确密码打开 |
| 兼容性 | 仅 H2 可读 | 不与其他数据库兼容 | — | 不能将加密 H2 文件导入 MySQL 等 |
完整连接示例:
jdbc:h2:~/mydb;CIPHER=AES;FILEPASSWORD='file123';PASSWORD='user123'
警告: 若丢失 FILEPASSWORD,数据将永久不可恢复!
5.3 全文搜索(Full-Text Search)
| 操作名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 创建全文索引 | CREATE ALIAS IF NOT EXISTS FTL_INIT FOR "org.h2.fulltext.FullTextLucene.init";CALL FTL_INIT();CALL FTL_CREATE_INDEX('PUBLIC', 'TABLE_NAME', 'COL1,COL2'); | 基于 Lucene 构建全文索引 | CALL FTL_CREATE_INDEX('PUBLIC', 'ARTICLES', 'TITLE,CONTENT'); | 首次需初始化 FTL 模块 |
| 执行全文搜索 | SELECT * FROM FTL_SEARCH('query', 0, 0) T JOIN ARTICLES A ON T.TABLE_SCHEMA='PUBLIC' AND T.TABLE_NAME='ARTICLES' AND T.KEYS[1]=A.ID; | 搜索包含关键词的记录 | SELECT * FROM FTL_SEARCH('database performance', 0, 0) ... | 返回匹配文档的主键 |
| 删除全文索引 | CALL FTL_DROP_ALL(); 或 FTL_DROP_INDEX(...) | 清理全文索引 | CALL FTL_DROP_INDEX('PUBLIC', 'ARTICLES'); | 索引独立于表,需手动维护 |
| 搜索语法 | 支持 Lucene 查询语法 | 高级搜索(布尔、短语等) | "fast database" OR (performance AND tuning) | 关键词默认分词(空格、标点) |
| 自动同步 | 插入/更新/删除时自动更新索引 | 保持索引一致性 | — | 由触发器实现,对性能有轻微影响 |
| 依赖要求 | 需 h2-*.jar 内置 Lucene 模块 | 全文功能非默认启用 | — | H2 2.x 已内置,无需额外 JAR |
建议: 可创建视图或存储函数封装
FTL_SEARCH的复杂 JOIN。
5.4 自定义函数与存储过程
| 类型 | 方法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 创建 Java 函数(别名) | CREATE ALIAS func_name FOR "ClassName.methodName"; | 将 Java 静态方法映射为 SQL 函数 | CREATE ALIAS SPLIT FOR "java.lang.String.split"; | 方法必须是 public static |
| 调用自定义函数 | SELECT func_name(args) FROM ... | 在 SQL 中使用 | SELECT SPLIT('a,b,c', ',') FROM DUAL; | 返回值需为基本类型或数组 |
| 创建存储过程(Java) | CREATE ALIAS proc_name AS $$ ... $$; | 实现复杂逻辑 | 见下方示例 | 使用 $$...$$ 包裹 Java 代码(H2 特有) |
| 调用存储过程 | CALL proc_name(args); | 执行过程 | CALL LOG_MESSAGE('User logged in'); | 无返回值,用于副作用操作 |
| 参数类型 | 支持 Java 基本类型、String、Connection 等 | 传递上下文 | void myFunc(String s, int i, Connection conn) | Connection 可用于执行内部 SQL |
| 安全限制 | 默认禁止动态编译(需设置 ALLOW_LITERALS=ALL) | 防止代码注入 | jdbc:h2:mem:test;ALLOW_LITERALS=ALL | 生产环境慎用,建议预编译函数 |
| 删除别名 | DROP ALIAS func_name; | 移除自定义函数/过程 | DROP ALIAS SPLIT; | 无依赖检查 |
示例:自定义字符串反转函数
CREATE ALIAS REVERSE_STRING AS $$
String reverse(String s) {
return new StringBuilder(s).reverse().toString();
}
$$;
SELECT REVERSE_STRING('hello'); -- 返回 'olleh'
注意: 自定义函数在嵌入式模式下有效;服务器模式需确保类在服务端 classpath。
第六章:性能与调优
6.1 内存 vs 文件模式性能对比
| 对比维度 | 内存模式(mem:) | 文件模式(~/ 或 /path) | 注意事项 |
|---|---|---|---|
| 数据持久性 | ❌ 进程结束即丢失(除非 DB_CLOSE_DELAY=-1) | ✅ 数据写入 .mv.db 文件,持久保存 | 内存模式仅适合临时数据 |
| 读写速度 | ⚡ 极快(无磁盘 I/O) | 🐢 受磁盘 I/O 限制,但使用 MVStore 引擎优化 | 内存模式吞吐量高 5~10 倍 |
| 并发支持 | ✅ 支持多线程读写(MVCC) | ✅ 同样支持 MVCC,并可被多个进程通过 TCP 访问 | 文件模式需注意文件锁 |
| 内存占用 | 🔺 全部数据驻留堆内存,大表易导致 OOM | 🔻 仅缓存热点数据,适合 GB 级数据 | 内存模式不适合大数据集 |
| 启动时间 | ⚡ 几毫秒 | 🐢 首次启动需加载文件(随数据量增长) | 文件模式冷启动较慢 |
| 适用场景 | 单元测试、原型验证、缓存中间结果 | 桌面应用、小型服务、需持久化的嵌入式系统 | 生产环境优先选文件模式 |
| 崩溃恢复 | ❌ 无恢复能力 | ✅ 通过 WAL(Write-Ahead Log)支持崩溃后自动恢复 | 默认启用日志(.trace.db) |
| 备份便利性 | ❌ 无法直接备份 | ✅ 可复制 .mv.db 文件(需关闭或使用 BACKUP SQL) | 热备份需谨慎 |
建议: 开发/测试用内存模式,生产用文件模式;避免在内存模式下存储 >100MB 数据。
6.2 索引优化
| 操作名称 | 语法/方法 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|
| 创建普通索引 | CREATE INDEX idx_name ON table(col) | 加速 WHERE、ORDER BY 查询 | CREATE INDEX idx_user_email ON users(email); | 高选择性列优先建索引 |
| 创建唯一索引 | CREATE UNIQUE INDEX idx_name ON table(col) | 保证列值唯一 + 提升查询速度 | CREATE UNIQUE INDEX idx_user_login ON users(login); | 插入重复值会报错 |
| 创建复合索引 | CREATE INDEX idx_name ON table(col1, col2) | 优化多列条件查询 | CREATE INDEX idx_order_status_date ON orders(status, created_at); | 列顺序影响效果(最左前缀) |
| 表达式索引 | CREATE INDEX idx_name ON table(UPPER(col)) | 加速函数化查询 | CREATE INDEX idx_upper_name ON users(UPPER(name)); | 查询时需使用相同表达式 |
| 查看索引 | SELECT * FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME='USERS' | 诊断现有索引 | — | 可结合 EXPLAIN 分析是否命中 |
| 删除无效索引 | DROP INDEX idx_name | 移除冗余或低效索引 | DROP INDEX idx_unused; | 索引增加写开销,定期清理 |
| 主键自动索引 | 定义 PRIMARY KEY 时自动创建 | 主键查询极快 | CREATE TABLE t(id INT PRIMARY KEY); | 无需手动创建 |
| 覆盖索引(Covering) | 索引包含 SELECT 所有字段 | 避免回表,提升性能 | CREATE INDEX idx_cover ON orders(user_id, total, status);SELECT total, status FROM orders WHERE user_id = 1; | H2 支持但需显式包含列 |
| 索引统计更新 | ANALYZE [TABLE table_name] | 更新表统计信息,帮助优化器决策 | ANALYZE TABLE users; | 大量数据变更后建议执行 |
反模式:
- 对低区分度列(如
gender)建索引 - 在频繁 UPDATE/DELETE 的表上建过多索引
6.3 查询执行计划分析
| 方法名称 | 语法 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|
| EXPLAIN | EXPLAIN [PLAN FOR] SELECT ... | 显示逻辑执行计划(优化器输出) | EXPLAIN SELECT * FROM users WHERE email = 'mailto:a@b.com'; | 输出为树状结构,含操作类型 |
| EXPLAIN ANALYZE | EXPLAIN ANALYZE SELECT ... | 显示实际执行计划 + 耗时(H2 2.0+) | EXPLAIN ANALYZE SELECT COUNT(*) FROM logs; | 需启用 TRACE_LEVEL_SYSTEM_OUT=3 查看详细日志 |
| 执行计划字段 | OPERATION, CARDINALITY, COST | 解读计划关键指标 | — | CARDINALITY 为预估行数,COST 为相对代价 |
| 常见操作类型 | TABLE_SCAN, INDEX_SCAN, JOIN, AGGREGATE | 识别性能瓶颈 | 若出现 TABLE_SCAN 而应走索引,需检查索引或统计信息 | 全表扫描在小表可接受 |
| 强制索引提示 | /*+ INDEX(table index_name) */ | 手动指定索引(调试用) | SELECT /*+ INDEX(users idx_email) */ * FROM users WHERE email = 'x'; | 仅用于测试,不推荐生产使用 |
| 查看优化器决策 | SET TRACE_LEVEL_SYSTEM_OUT=3 | 输出优化器详细日志到控制台 | — | 日志量大,仅临时开启 |
典型优化路径:
- 用 EXPLAIN 确认是否走索引
- 若未命中,检查 WHERE 条件是否匹配索引列及顺序
- 执行
ANALYZE更新统计信息 - 考虑添加复合索引或表达式索引
6.4 缓存机制
| 缓存类型 | 配置参数 | 作用范围 | 默认值 | 调优建议 |
|---|---|---|---|---|
| 页面缓存(Page Cache) | CACHE_SIZE (KB) | 缓存数据页(.mv.db 块) | 16384(16MB) | 大内存机器可增至 65536(64MB) |
| 结果集缓存 | 不直接支持 | — | — | 应用层自行缓存(如 Caffeine) |
| 执行计划缓存 | 内部自动缓存 PreparedStatement | 相同 SQL 重用计划 | ✅ 启用 | 使用参数化查询以复用计划 |
| 查询结果缓存 | ❌ 不支持 | — | — | 需依赖外部缓存(Redis/Ehcache) |
| 设置缓存大小 | jdbc:h2:...;CACHE_SIZE=32768 | 启动时指定 | — | 单位 KB,最大约 64MB(受 JVM 堆限制) |
| 清空缓存 | SHUTDOWN DEFRAG | 重启时压缩并清缓存 | — | 可减少文件碎片,但非运行时操作 |
| 缓存监控 | 无直接视图 | — | — | 通过性能压测间接评估 |
重要说明:
- H2 的缓存主要用于磁盘 I/O 优化,非查询结果缓存。
- 内存模式下缓存意义不大(数据已在内存)。
- 过大
CACHE_SIZE会挤占应用堆内存,需平衡。
设置示例:jdbc:h2:~/mydb;CACHE_SIZE=65536 → 分配 64MB 缓存
第七章:工具与运维
7.1 H2 Console使用指南
| 功能名称 | 操作方式 / 路径 | 用途说明 | 注意事项 |
|---|---|---|---|
| 启动 Web 控制台 | 命令行执行:java -jar h2-*.jar或 java -cp h2-*.jar org.h2.tools.Server -web | 启动内嵌 Web 服务器并自动打开浏览器 | 默认端口 8082;可加 -webPort 9090 修改 |
| 访问地址 | http://localhost:8082 | 手动访问控制台(若未自动打开) | 需确保端口未被占用 |
| 连接配置 | 在登录页填写: - JDBC URL - 用户名(默认 sa) - 密码(默认空) | 连接到目标 H2 数据库 | 支持内存、文件、远程 TCP 模式 |
| SQL 执行区 | 页面中部文本框 | 编写并执行任意 SQL 语句 | 支持多语句(用 ; 分隔) |
| 结果展示 | 执行后下方表格显示 | 查看查询结果或操作影响行数 | 可导出为 CSV |
| 表结构浏览 | 左侧 “Tables” 树形菜单 → 点击表名 | 查看列定义、主键、索引 | 支持直接编辑数据(双击单元格) |
| 执行计划查看 | 勾选 “Explain” 复选框后执行 SELECT | 显示查询的执行计划 | 有助于性能分析 |
| 自动补全 | 输入时按 Ctrl+Space | 触发 SQL 关键字/表名列名提示 | 提升编写效率 |
| 安全建议 | — | 控制台仅用于开发/测试环境 | ❌ 切勿在生产环境暴露 8082 端口 |
7.2 备份与恢复
| 操作名称 | 方法 / 语法 | 用途 | 示例 | 注意事项 |
|---|---|---|---|---|
| SQL 备份(逻辑) | BACKUP TO 'file.zip' | 将当前数据库导出为 ZIP 格式逻辑备份 | BACKUP TO '/backup/mydb.zip'; | 可跨平台/版本恢复;包含 schema + data |
| SQL 恢复(逻辑) | RUNSCRIPT FROM 'file.zip' | 从 ZIP 备份重建数据库 | RUNSCRIPT FROM '/backup/mydb.zip'; | 需新建空库后执行 |
| 文件复制(物理) | 直接复制 .mv.db 文件 | 快速物理备份(需数据库关闭) | cp mydb.mv.db mydb_backup.mv.db | 若数据库运行中,可能损坏(需 SHUTDOWN FIRST) |
| 热备份(运行中) | 使用 BACKUP SQL(推荐) | 无需停机,安全一致 | CALL BACKUP TO 'hot.zip'; | H2 2.x 支持在线备份 |
| 自动备份脚本 | 定时任务调用 JDBC 执行 BACKUP | 实现定期自动备份 | Linux cron: 0 2 * * * java -cp h2.jar BackupTool | 需编写简单 Java 工具类 |
| 恢复到新实例 | 将 .mv.db 文件放入新路径,启动连接 | 快速还原物理备份 | jdbc:h2:/restore/mydb | 确保 H2 版本兼容 |
| 增量备份 | ❌ 不支持 | — | — | H2 无 WAL 归档机制,仅支持全量 |
最佳实践:
- 开发/测试:用
BACKUP TO+ 版本控制 - 生产嵌入式应用:每日定时
BACKUP TO ZIP+ 保留多份 - 恢复前验证 ZIP 内容(可用
unzip -l查看)
7.3 日志与调试
| 日志类型 | 配置方式 / 文件名 | 用途 | 默认行为 | 调优建议 |
|---|---|---|---|---|
| 数据库跟踪日志 | 自动生成 .trace.db 文件 | 记录错误、警告、内部操作 | ✅ 启用(TRACE_LEVEL_FILE=2) | 可设为 0 关闭以提升性能 |
| 控制台输出日志 | 设置 TRACE_LEVEL_SYSTEM_OUT=1~3 | 将日志打印到标准输出(如 IDE 控制台) | 0(关闭) | 调试时设为 2 或 3 |
| SQL 语句日志 | 应用层配置(如 Spring Boot) | 打印执行的 SQL | 关闭 | logging.level.org.hibernate.SQL=DEBUG |
| 日志级别含义 | 0=OFF, 1=ERROR, 2=INFO, 3=DEBUG | 控制日志详细程度 | FILE=2, SYSTEM_OUT=0 | 高级别日志影响性能 |
| 清理日志文件 | 删除 .trace.db 或设置 TRACE_MAX_FILE_SIZE | 防止日志无限增长 | 无限制 | 可设 TRACE_MAX_FILE_SIZE=10(MB) |
| 调试连接问题 | 检查 .trace.db 或开启 SYSTEM_OUT | 诊断连接失败、权限错误等 | — | 常见错误:文件锁冲突、路径权限不足 |
| 慢查询日志 | ❌ H2 无内置慢查询日志 | — | — | 需应用层自行记录执行时间 |
连接字符串示例(开启详细日志):
jdbc:h2:~/mydb;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3;TRACE_MAX_FILE_SIZE=20
7.4 版本升级与兼容性
| 事项 | 说明 | 操作建议 | 注意事项 |
|---|---|---|---|
| 向后兼容性 | H2 通常保证向后兼容(新版本可读旧版 .mv.db) | 直接替换 JAR 即可 | 极少数大版本(如 1.x → 2.x)需迁移 |
| 跨大版本升级(1.x→2.x) | MVStore 引擎变更,旧格式不兼容 | 1. 用旧版 H2 导出为 SQL 脚本 2. 用新版 H2 执行脚本重建 | 不可直接替换 .mv.db 文件 |
| 导出为 SQL 脚本 | 使用 H2 Console 或命令行工具 | CALL SCRIPT TO 'dump.sql'; | 包含 schema + data,文本格式通用 |
| 自动升级(小版本) | 如 2.1.214 → 2.2.224 | 替换 JAR,重启应用 | 通常无缝,建议先测试 |
| 兼容模式变更 | 新版可能调整 MySQL/PostgreSQL 模拟行为 | 检查 RELEASE NOTES | 如分页语法 LIMIT/OFFSET 行为微调 |
| JDBC 驱动类名 | 始终为 org.h2.Driver | 无需修改代码 | 驱动 API 保持稳定 |
| 降级风险 | 新版创建的数据库不能被旧版读取 | ❌ 禁止降级 | 升级前务必备份 |
| 检查版本 | SELECT H2VERSION(); | 确认当前运行版本 | 或通过 JAR MANIFEST.MF 查看 |
升级流程(安全):
- 备份数据库(
BACKUP TO或复制.mv.db) - 在测试环境验证新版本功能与性能
- 若跨大版本,使用 SCRIPT/BACKUP 迁移
- 更新生产依赖,重启应用
官方兼容声明: H2 项目承诺同一主版本内(如 2.x)完全兼容;主版本升级需手动迁移。
第八章:与其他数据库对比
8.1 与SQLite对比
| 对比维度 | H2 数据库 | SQLite | 说明 |
|---|---|---|---|
| 语言实现 | 纯 Java | C 语言 | H2 仅适用于 JVM 环境;SQLite 可嵌入任何语言 |
| 运行模式 | 嵌入式、服务器、内存 | 仅嵌入式(无独立服务进程) | H2 支持多进程通过 TCP 访问;SQLite 需文件共享 |
| 并发写入 | ✅ 多线程写(MVCC) | ❌ 全局写锁(同一时间仅一个写操作) | H2 更适合高并发写场景 |
| SQL 标准支持 | 高度兼容 ANSI SQL + MySQL/PG/Oracle 模式 | 基本 SQL-92,部分扩展 | H2 语法更丰富,函数更全 |
| 类型系统 | 强类型(INT, VARCHAR, TIMESTAMP 等) | 动态类型(基于值而非列定义) | H2 更接近传统 RDBMS |
| 内存数据库 | ✅ 原生支持(jdbc:h2:mem:...) | ✅ 支持(:memory:) | 两者均适合单元测试 |
| 文件格式 | 单一 .mv.db 文件 | 单一 .db 文件 | 均便于分发 |
| 加密 | ✅ 内置 AES 加密(CIPHER=AES) | ❌ 官方不支持(需 SEE 商业版或第三方扩展) | H2 安全性更强 |
| Web 控制台 | ✅ 内置 H2 Console | ❌ 无(需第三方工具如 DB Browser) | H2 开发调试更便捷 |
| 适用场景 | Java 应用、Spring Boot 测试、桌面软件 | 移动 App(Android/iOS)、小型 C/C++ 工具 | 技术栈决定选型 |
8.2 与HSQLDB对比
| 对比维度 | H2 数据库 | HSQLDB(HyperSQL) | 说明 |
|---|---|---|---|
| 性能(内存模式) | ⚡ 更快(尤其写入和复杂查询) | 🐢 较慢 | H2 优化更激进 |
| 启动速度 | ⚡ 极快(<50ms) | 🐢 较慢(>100ms) | H2 更适合频繁启停的测试 |
| SQL 兼容性 | ✅ 支持 MySQL/PostgreSQL/Oracle 模式 | ⚠️ 有限兼容(主要 ANSI SQL) | H2 更易迁移遗留 SQL |
| 全文搜索 | ✅ 内置 Lucene 集成 | ❌ 不支持 | H2 功能更全面 |
| 事务隔离 | MVCC(快照读) | 传统锁机制 | H2 读写并发更高 |
| 社区活跃度 | ✅ 高(GitHub 活跃,Spring Boot 默认测试库) | ⚠️ 低(更新缓慢) | H2 生态更现代 |
| 文件大小 | ~2MB JAR | ~1.5MB JAR | 两者均轻量 |
| 自定义函数 | ✅ 支持 Java 方法映射 | ✅ 支持 | 能力相当 |
| 默认用户 | sa(空密码) | SA(空密码) | 安全配置类似 |
| 适用场景 | 现代 Java 项目首选 | 老旧 Java 项目、特定合规要求 | 新项目推荐 H2 |
8.3 与Derby对比
| 对比维度 | H2 数据库 | Apache Derby | 说明 |
|---|---|---|---|
| 所属生态 | 独立开源项目 | Apache 基金会(曾为 IBM Cloudscape) | Derby 企业背景强 |
| 部署方式 | 单 JAR(~2MB) | 多 JAR(需 derby.jar + derbytools.jar 等) | H2 更简洁 |
| 启动复杂度 | ⚡ 极简(java -jar h2.jar) | 🐢 需启动 NetworkServerControl 或嵌入式初始化 | H2 上手更快 |
| 内存模式 | ✅ 原生支持 | ⚠️ 支持但配置繁琐(jdbc:derby:memory:db;create=true) | H2 更直观 |
| SQL 方言 | 灵活(多兼容模式) | 严格 ANSI SQL | H2 更灵活 |
| 工具支持 | ✅ 内置 Web Console | ❌ 依赖 ij 命令行或第三方工具 | H2 开发体验更好 |
| 性能 | ⚡ 优于 Derby(尤其在内存模式) | 🐢 较慢 | 多项基准测试证实 |
| 社区与更新 | ✅ 活跃(年更多次) | ⚠️ 缓慢(年更 0~1 次) | Derby 近年趋于维护状态 |
| 许可证 | MPL 2.0 / EPL | Apache License 2.0 | 均可用于商业项目 |
| 适用场景 | 快速开发、测试、轻量应用 | 需 Apache 生态集成、特定企业标准 | 新项目优先 H2 |
8.4 在测试环境中的替代方案选择
| 方案 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| H2(内存模式) | ✅ 启动极快 ✅ Spring Boot 默认 ✅ 功能完整 | ❌ 与生产 DB(如 MySQL)存在方言差异 | 大多数 Spring Boot 单元/集成测试 |
| Testcontainers + 真实 DB | ✅ 100% 生产一致性 ✅ 支持复杂特性(存储过程、分区等) | ❌ 启动慢(秒级) ❌ 依赖 Docker | 需要完全一致性的集成测试、E2E 测试 |
| H2(文件模式) | ✅ 数据持久化 ✅ 可调试 | ❌ 仍存在方言差异 ❌ 非真实 DB 行为 | 需跨测试类共享状态的场景 |
| SQLite(JDBC) | ✅ 轻量 ✅ 文件即数据库 | ❌ 写并发差 ❌ 类型系统弱 ❌ 无兼容模式 | 非 Java 项目或简单只读测试 |
| HSQLDB | ✅ ANSI SQL 严格 | ❌ 性能较差 ❌ 社区停滞 | 遗留系统兼容测试 |
| Derby | ✅ Apache 生态 | ❌ 配置复杂 ❌ 启动慢 | 特定企业合规要求 |
选型决策树:
- 是否要求 100% 生产一致性? → 是:用 Testcontainers + 真实数据库 → 否:进入下一步
- 是否使用 Spring Boot? → 是:默认用 H2 内存模式(最快最简)
- 是否涉及复杂 SQL(如窗口函数、JSON)? → 是:检查 H2 是否支持,否则考虑 Testcontainers
- 是否需多进程共享测试数据? → 是:用 H2 服务器模式或文件模式
最佳实践:
- 单元测试:H2 内存模式(
@DataJpaTest) - 集成测试:H2(若逻辑简单)或 Testcontainers(若需真实行为)
- 避免在测试中使用与生产差异过大的方言(如 H2 的 MERGE 语法 vs MySQL 的 ON DUPLICATE KEY)