内嵌数据库支持 (Embedded Database Support)
org.springframework.jdbc.datasource.embedded 包提供了对内嵌 Java 数据库引擎的支持。原生支持 HSQL、H2 和 Derby。你也可以使用可扩展的 API 来插入新的内嵌数据库类型和 DataSource 实现。
为什么使用内嵌数据库?
内嵌数据库在项目的开发阶段非常有用,因为它们具有轻量级的特性。其优点包括配置简单、启动速度快、易于测试,以及在开发过程中能快速演化 SQL 脚本。
创建内嵌数据库
你可以将内嵌数据库实例暴露为 Spring Bean,如下例所示:
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.addScripts("schema.sql", "test-data.sql")
.build();
}@Bean
fun dataSource() = EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.addScripts("schema.sql", "test-data.sql")
.build()<jdbc:embedded-database id="dataSource" generate-name="true" type="H2">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>上述配置创建了一个内嵌的 H2 数据库,并使用类路径根目录下的 schema.sql 和 test-data.sql 资源进行初始化。此外,作为最佳实践,内嵌数据库被分配了一个唯一生成的名称。该内嵌数据库以 javax.sql.DataSource 类型的 Bean 形式提供给 Spring 容器。
选择内嵌数据库类型
Spring 支持以下三种内嵌数据库:
- HSQL: Spring 支持 HSQL 1.8.0 及以上版本。如果没有显式指定类型,HSQL 是默认的内嵌数据库。
- H2: Spring 支持 H2 数据库。
- Derby: Spring 支持 Apache Derby 10.5 及以上版本。
生成唯一名称
开发团队在使用内嵌数据库时经常会遇到错误,原因是测试套件无意中尝试重新创建相同数据库的多个实例。如果多个测试场景复用了相同的配置(例如在同一个 JVM 进程中运行),Spring 内部默认会将数据库命名为 testdb。
为了解决这个问题,Spring 提供了生成唯一名称的支持:
- Java 控制:
.generateUniqueName(true) - XML 控制:
generate-name="true"
使用内嵌数据库测试数据访问逻辑
内嵌数据库为测试数据访问代码提供了一种轻量级方案。
public class DataAccessIntegrationTestTemplate {
private EmbeddedDatabase db;
@BeforeEach
public void setUp() {
// 创建一个唯一的 HSQL 内存数据库,并加载默认脚本
db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build();
}
@Test
public void testDataAccess() {
JdbcTemplate template = new JdbcTemplate(db);
template.query( /* ... */ );
}
@AfterEach
public void tearDown() {
db.shutdown();
}
}补充教学
1. 为什么 H2 是目前最受欢迎的选择?
虽然 Spring 支持 HSQL 和 Derby,但 H2 在现代开发中几乎成为了标准。原因如下:
- 兼容模式:H2 拥有非常强大的模式模拟功能,可以模拟 MySQL, Oracle, PostgreSQL 等主流数据库的行为。这让你可以用“内嵌数据库”跑“生产级数据库”的方言。
- 控制台交互:H2 提供了一个基于浏览器的 Web 控制台,你可以在测试运行期间打开浏览器查看内存里的数据,调试非常方便。
- 性能:H2 的执行速度通常优于 HSQL。
2. generateUniqueName(true) 的生存法则
在运行单元测试时,Spring 的测试上下文缓存机制(Context Caching)虽然加快了测试速度,但也带来了副作用:如果你在多个配置文件里都定义了同一个名字的内嵌数据库,由于它们是在同一个 JVM 里运行的,第二个测试可能会尝试修改第一个测试正在使用的数据库,或者因为数据库已存在而报错。 始终开启唯一名称命名是一个好习惯,它能确保每个 ApplicationContext 都有自己独立的数据库“沙盒”。
3. 不要用内嵌数据库做性能压力测试
请记住,内嵌数据库(尤其是内存模式)与生产环境的传统磁盘数据库(如 MySQL, Oracle)在性能特征上完全不同:
- 内存数据库没有磁盘 I/O 瓶颈。
- 内存数据库通常是单进程的,没有复杂的网络开销。 如果你的 SQL 在 H2 里跑得很快,并不代表在生产环境里也很快。性能优化的验证必须在真实的数据库环境中进行。