嵌入式数据库支持

org.springframework.jdbc.datasource.embedded 包为嵌入式 Java 数据库引擎提供支持。原生提供对 HSQLH2Derby 的支持。您还可以使用可扩展的 API 来插入新的嵌入式数据库类型和 DataSource 实现。

为何使用嵌入式数据库?

嵌入式数据库由于其轻量级特性,在项目的开发阶段非常有用。优点包括易于配置、启动时间快、可测试性以及在开发过程中快速演化 SQL 的能力。

使用 Spring XML 创建嵌入式数据库

如果您想在 Spring ApplicationContext 中将嵌入式数据库实例公开为 Bean,则可以在 spring-jdbc 命名空间中使用 embedded-database 标记

<jdbc:embedded-database id="dataSource" generate-name="true">
	<jdbc:script location="classpath:schema.sql"/>
	<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

前面的配置创建了一个嵌入式 HSQL 数据库,该数据库使用类路径根目录中的 schema.sqltest-data.sql 资源中的 SQL 进行填充。此外,作为最佳实践,嵌入式数据库被分配了一个唯一生成的名称。嵌入式数据库作为类型为 javax.sql.DataSource 的 Bean 提供给 Spring 容器,然后可以根据需要将其注入到数据访问对象中。

以编程方式创建嵌入式数据库

EmbeddedDatabaseBuilder 类提供了一个流畅的 API,用于以编程方式构建嵌入式数据库。当您需要在独立环境或独立集成测试中创建嵌入式数据库时可以使用它,如下例所示

  • Java

  • Kotlin

EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
		.generateUniqueName(true)
		.setType(H2)
		.setScriptEncoding("UTF-8")
		.ignoreFailedDrops(true)
		.addScript("schema.sql")
		.addScripts("user_data.sql", "country_data.sql")
		.build();

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()
val db = EmbeddedDatabaseBuilder()
		.generateUniqueName(true)
		.setType(H2)
		.setScriptEncoding("UTF-8")
		.ignoreFailedDrops(true)
		.addScript("schema.sql")
		.addScripts("user_data.sql", "country_data.sql")
		.build()

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()

有关所有受支持选项的更多详细信息,请参阅 EmbeddedDatabaseBuilderjavadoc

您还可以使用 EmbeddedDatabaseBuilder 通过 Java 配置创建嵌入式数据库,如下例所示

  • Java

  • Kotlin

@Configuration
public class DataSourceConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
				.generateUniqueName(true)
				.setType(H2)
				.setScriptEncoding("UTF-8")
				.ignoreFailedDrops(true)
				.addScript("schema.sql")
				.addScripts("user_data.sql", "country_data.sql")
				.build();
	}
}
@Configuration
class DataSourceConfig {

	@Bean
	fun dataSource(): DataSource {
		return EmbeddedDatabaseBuilder()
				.generateUniqueName(true)
				.setType(H2)
				.setScriptEncoding("UTF-8")
				.ignoreFailedDrops(true)
				.addScript("schema.sql")
				.addScripts("user_data.sql", "country_data.sql")
				.build()
	}
}

选择嵌入式数据库类型

本节介绍如何选择 Spring 支持的三种嵌入式数据库之一。它包括以下主题

使用 HSQL

Spring 支持 HSQL 1.8.0 及更高版本。如果未明确指定类型,HSQL 是默认的嵌入式数据库。要明确指定 HSQL,请将 embedded-database 标记的 type 属性设置为 HSQL。如果您使用构建器 API,请使用 EmbeddedDatabaseType.HSQL 调用 setType(EmbeddedDatabaseType) 方法。

使用 H2

Spring 支持 H2 数据库。要启用 H2,请将 embedded-database 标记的 type 属性设置为 H2。如果您使用构建器 API,请使用 EmbeddedDatabaseType.H2 调用 setType(EmbeddedDatabaseType) 方法。

使用 Derby

Spring 支持 Apache Derby 10.5 及更高版本。要启用 Derby,请将 embedded-database 标记的 type 属性设置为 DERBY。如果您使用构建器 API,请使用 EmbeddedDatabaseType.DERBY 调用 setType(EmbeddedDatabaseType) 方法。

使用嵌入式数据库测试数据访问逻辑

嵌入式数据库提供了一种轻量级的方式来测试数据访问代码。下一个示例是一个使用嵌入式数据库的数据访问集成测试模板。当不需要在测试类之间重复使用嵌入式数据库时,使用这样的模板可能很有用。但是,如果您希望创建一个在测试套件中共享的嵌入式数据库,请考虑使用 Spring TestContext 框架,并将嵌入式数据库配置为 Spring ApplicationContext 中的一个 bean,如 使用 Spring XML 创建嵌入式数据库以编程方式创建嵌入式数据库 中所述。以下清单显示了测试模板

  • 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 社区贡献扩展。