Embedded Database Support

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

Why Use an Embedded Database?

嵌入式数据库在项目的开发阶段可能会很有用,因为它具有轻量级特性。优点包括易于配置、快速启动时间、可测试性以及在开发过程中可以快速更新 SQL。

Creating an Embedded Database

可以将嵌入式数据库实例公开为 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.sqltest-data.sql 资源中的 SQL 进行填充。此外,最佳实践是为嵌入式数据库分配一个唯一生成的名称。该嵌入式数据库以 javax.sql.DataSource 类型的 bean 的形式提供给 Spring 容器,然后可以根据需要注入到数据访问对象中。

请参阅 javadoc for EmbeddedDatabaseBuilder,了解所有支持选项的更多详细信息。

Selecting the Embedded Database Type

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

Using HSQL

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

Using H2

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

Using Derby

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

Customizing the Embedded Database Type

虽然每种受支持的类型都带有默认连接设置,但在必要时,可以对其进行自定义。以下示例将 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)
			}
		}
	}
}

Testing Data Access Logic with an Embedded Database

嵌入式数据库提供了一种轻量级的方法来测试数据访问代码。下一个示例是使用嵌入式数据库的数据访问集成测试模板。当嵌入式数据库不需要在测试类之间重复使用时,使用这样的模板会很有用。但是,如果你希望创建在测试套件内共享的嵌入式数据库,请考虑使用 Spring TestContext Framework 并按照 Creating an Embedded Database 中所述将嵌入式数据库配置为 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()
	}
}

Generating Unique Names for Embedded Databases

如果开发团队的测试套件不经意间尝试重新创建同一数据库的其他实例,他们通常会遇到嵌入式数据库错误。如果 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" …​ >

Extending the Embedded Database Support

可以通过两种方式扩展 Spring JDBC 嵌入式数据库支持:

  • 实现 EmbeddedDatabaseConfigurer,以支持新的嵌入式数据库类型。

  • 实现 DataSourceFactory,以支持新的 DataSource 实现,例如管理嵌入式数据库连接的连接池。

我们鼓励您在 GitHub Issues 向 Spring 社区贡献扩展。