嵌入式数据库支持
为什么使用嵌入式数据库?
嵌入式数据库在项目的开发阶段可能非常有用,因为它具有轻量级的特性。其优点包括配置简单、启动时间快、易于测试,以及在开发过程中能够快速演变 SQL。
创建一个嵌入式数据库
您可以将嵌入式数据库实例作为 bean 暴露,如以下示例所示:
- Java
- Kotlin
- Xml
@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
资源中的 SQL 进行填充。此外,作为最佳实践,嵌入式数据库被分配了一个唯一生成的名称。嵌入式数据库作为 javax.sql.DataSource
类型的 bean 提供给 Spring 容器,然后可以根据需要注入到数据访问对象中。
有关所有支持选项的更多详细信息,请参见 EmbeddedDatabaseBuilder 的 javadoc。
选择嵌入式数据库类型
本节介绍如何选择 Spring 支持的三种嵌入式数据库之一。内容包括以下主题:
使用 HSQL
Spring 支持 HSQL 1.8.0 及以上版本。如果没有显式指定类型,HSQL 是默认的嵌入式数据库。要显式指定 HSQL,请将 embedded-database
标签的 type
属性设置为 HSQL
。如果使用构建器 API,请调用 setType(EmbeddedDatabaseType)
方法并传入 EmbeddedDatabaseType.HSQL
。
使用 H2
Spring 支持 H2 数据库。要启用 H2,将 embedded-database
标签的 type
属性设置为 H2
。如果使用构建器 API,调用 setType(EmbeddedDatabaseType)
方法并传入 EmbeddedDatabaseType.H2
。
使用 Derby
Spring 支持 Apache Derby 10.5 及以上版本。要启用 Derby,将 embedded-database
标签的 type
属性设置为 DERBY
。如果使用构建器 API,请调用 setType(EmbeddedDatabaseType)
方法,并传入 EmbeddedDatabaseType.DERBY
。
自定义嵌入式数据库类型
虽然每种支持的类型都有默认的连接设置,但如果需要,可以对其进行自定义。以下示例使用 H2 和一个自定义驱动程序:
- Java
- Kotlin
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(H2, this::customize))
.addScript("schema.sql")
.build();
}
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
super.configureConnectionProperties(properties, databaseName);
properties.setDriverClass(CustomDriver.class);
}
};
}
}
@Configuration
class DataSourceConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) })
.addScript("schema.sql")
.build()
}
private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer {
return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
override fun configureConnectionProperties(
properties: ConnectionProperties,
databaseName: String
) {
super.configureConnectionProperties(properties, databaseName)
properties.setDriverClass(CustomDriver::class.java)
}
}
}
}
使用嵌入式数据库测试数据访问逻辑
嵌入式数据库提供了一种轻量级的方式来测试数据访问代码。下一个示例是一个使用嵌入式数据库的数据访问集成测试模板。使用这样的模板对于一次性测试很有用,当嵌入式数据库不需要在测试类之间重用时。然而,如果您希望创建一个在测试套件中共享的嵌入式数据库,考虑使用 Spring TestContext Framework 并按照创建嵌入式数据库中描述的那样将嵌入式数据库配置为 Spring ApplicationContext
中的一个 bean。以下列表显示了测试模板:
- Java
- Kotlin
public class DataAccessIntegrationTestTemplate {
private EmbeddedDatabase db;
@BeforeEach
public void setUp() {
// creates an HSQL in-memory database populated from default scripts
// classpath:schema.sql and classpath:data.sql
db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build();
}
@Test
public void testDataAccess() {
JdbcTemplate template = new JdbcTemplate(db);
template.query( /* ... */ );
}
@AfterEach
public void tearDown() {
db.shutdown();
}
}
class DataAccessIntegrationTestTemplate {
private lateinit var db: EmbeddedDatabase
@BeforeEach
fun setUp() {
// creates an HSQL in-memory database populated from default scripts
// classpath:schema.sql and classpath:data.sql
db = EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.addDefaultScripts()
.build()
}
@Test
fun testDataAccess() {
val template = JdbcTemplate(db)
template.query( /* ... */)
}
@AfterEach
fun tearDown() {
db.shutdown()
}
}
为嵌入式数据库生成唯一名称
开发团队在使用嵌入式数据库时经常会遇到错误,尤其是在测试套件无意中尝试重新创建同一数据库的多个实例时。这种情况很容易发生,例如,如果一个 XML 配置文件或 @Configuration
类负责创建一个嵌入式数据库,而相应的配置在同一测试套件(即同一 JVM 进程)中的多个测试场景中被重用——例如,针对嵌入式数据库的集成测试,其 ApplicationContext
配置仅在于哪个 bean 定义配置文件处于活动状态时有所不同。
此类错误的根本原因在于 Spring 的 EmbeddedDatabaseFactory
(在 <jdbc:embedded-database>
XML 命名空间元素和用于 Java 配置的 EmbeddedDatabaseBuilder
中内部使用)在未指定的情况下将嵌入式数据库的名称设置为 testdb
。对于 <jdbc:embedded-database>
的情况,嵌入式数据库通常被分配一个等于 bean 的 id
的名称(通常是类似 dataSource
的名称)。因此,后续尝试创建嵌入式数据库时不会生成新数据库。相反,相同的 JDBC 连接 URL 被重用,尝试创建新的嵌入式数据库实际上指向的是从相同配置创建的现有嵌入式数据库。
为了解决这个常见问题,Spring Framework 4.2 提供了对生成嵌入式数据库唯一名称的支持。要启用生成名称的使用,可以使用以下选项之一。
-
EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()
-
EmbeddedDatabaseBuilder.generateUniqueName()
-
<jdbc:embedded-database generate-name="true" … >
扩展嵌入式数据库支持
您可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:
-
实现
EmbeddedDatabaseConfigurer
以支持新的嵌入式数据库类型。 -
实现
DataSourceFactory
以支持新的DataSource
实现,例如用于管理嵌入式数据库连接的连接池。
我们鼓励您在 GitHub Issues 上为 Spring 社区贡献扩展。