嵌入式資料庫支援

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。如果您使用 Builder API,請使用 EmbeddedDatabaseType.HSQL 呼叫 setType(EmbeddedDatabaseType) 方法。

使用 H2

Spring 支援 H2 資料庫。若要啟用 H2,請將 embedded-database 標籤的 type 屬性設定為 H2。如果您使用 Builder API,請使用 EmbeddedDatabaseType.H2 呼叫 setType(EmbeddedDatabaseType) 方法。

使用 Derby

Spring 支援 Apache Derby 10.5 及更高版本。若要啟用 Derby,請將 embedded-database 標籤的 type 屬性設定為 DERBY。如果您使用 Builder 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 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 定義 Profile 方面有所不同。

這些錯誤的根本原因是 Spring 的 EmbeddedDatabaseFactory<jdbc:embedded-database> XML Namespace 元素和 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 社群貢獻擴充功能。