嵌入式数据库支持

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

为什么使用嵌入式数据库?

由于其轻量级特性,嵌入式数据库在项目的开发阶段非常有用。其优势包括易于配置、快速启动时间、可测试性和能够在开发过程中快速改进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.sqltest-data.sql资源中的SQL进行填充。此外,作为最佳实践,嵌入式数据库被分配了一个唯一生成的名称。嵌入式数据库作为类型为javax.sql.DataSource的bean提供给Spring容器,然后可以根据需要将其注入到数据访问对象中。

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

选择嵌入式数据库类型

本节介绍如何选择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)方法。

自定义嵌入式数据库类型

虽然每种支持的类型都带有默认的连接设置,但如果需要,可以自定义它们。以下示例使用具有自定义驱动程序的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框架并在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(在XML命名空间元素<jdbc:embedded-database>和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实现,例如用于管理嵌入式数据库连接的连接池。

我们鼓励您将扩展贡献到 Spring 社区,请访问 GitHub Issues