初始化 DataSource

org.springframework.jdbc.datasource.init 套件提供了初始化現有 DataSource 的支援。嵌入式資料庫支援提供了一種為應用程式建立和初始化 DataSource 的選項。但是,有時您可能需要初始化在某處伺服器上執行的實例。

使用 Spring XML 初始化資料庫

如果您想要初始化資料庫,並且可以提供對 DataSource Bean 的參考,則可以使用 spring-jdbc 命名空間中的 initialize-database 標籤

<jdbc:initialize-database data-source="dataSource">
	<jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
	<jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
</jdbc:initialize-database>

上述範例針對資料庫執行兩個指定的腳本。第一個腳本建立 Schema,第二個腳本使用測試資料集填入表格。腳本位置也可以是帶有萬用字元的模式,其樣式與 Spring 中用於資源的 Ant 樣式相同 (例如,classpath*:/com/foo/**/sql/*-data.sql)。如果您使用模式,則腳本會依其 URL 或檔案名稱的詞彙順序執行。

資料庫初始化程式的預設行為是無條件執行提供的腳本。這可能不總是您想要的 — 例如,如果您針對已包含測試資料的資料庫執行腳本。遵循常見模式 (如先前所示) 先建立表格,然後再插入資料,可以降低意外刪除資料的可能性。如果表格已存在,則第一個步驟會失敗。

但是,為了更有效地控制現有資料的建立和刪除,XML 命名空間提供了一些額外的選項。第一個選項是開啟和關閉初始化的旗標。您可以根據環境設定此旗標 (例如,從系統屬性或環境 Bean 中提取布林值)。以下範例從系統屬性取得值

<jdbc:initialize-database data-source="dataSource"
	enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
	<jdbc:script location="..."/>
</jdbc:initialize-database>
1 從名為 INITIALIZE_DATABASE 的系統屬性取得 enabled 的值。

控制現有資料處理方式的第二個選項是對失敗更寬容。為此,您可以控制初始化程式忽略其從腳本執行的 SQL 中某些錯誤的能力,如下列範例所示

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
	<jdbc:script location="..."/>
</jdbc:initialize-database>

在上述範例中,我們表示預期有時腳本會針對空的資料庫執行,並且腳本中存在一些 DROP 陳述式,因此會失敗。因此,失敗的 SQL DROP 陳述式將被忽略,但其他失敗將導致例外。如果您的 SQL 方言不支援 DROP …​ IF EXISTS (或類似語法),但您想要在重新建立所有測試資料之前無條件移除它們,這會很有用。在這種情況下,第一個腳本通常是一組 DROP 陳述式,後跟一組 CREATE 陳述式。

ignore-failures 選項可以設定為 NONE (預設值)、DROPS (忽略失敗的 Drop) 或 ALL (忽略所有失敗)。

如果腳本中完全沒有 ; 字元,則每個陳述式應以 ; 或換行符號分隔。您可以全域或逐個腳本控制此設定,如下列範例所示

<jdbc:initialize-database data-source="dataSource" separator="@@"> (1)
	<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> (2)
	<jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
	<jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>
1 將分隔符號腳本設定為 @@
2 db-schema.sql 的分隔符號設定為 ;

在此範例中,兩個 test-data 腳本使用 @@ 作為陳述式分隔符號,而只有 db-schema.sql 使用 ;。此組態指定預設分隔符號為 @@,並覆寫 db-schema 腳本的預設分隔符號。

如果您需要比從 XML 命名空間獲得的更多控制,您可以直接使用 DataSourceInitializer,並將其定義為應用程式中的組件。

相依於資料庫的其他組件的初始化

很大一部分應用程式 (那些在 Spring Context 啟動後才使用資料庫的應用程式) 可以使用資料庫初始化程式,而不會產生進一步的複雜性。如果您的應用程式不是其中之一,您可能需要閱讀本節的其餘部分。

資料庫初始化程式相依於 DataSource 實例,並在其初始化回呼 (類似於 XML Bean 定義中的 init-method、組件中的 @PostConstruct 方法,或實作 InitializingBean 的組件中的 afterPropertiesSet() 方法) 中執行提供的腳本。如果其他 Bean 相依於相同的資料來源,並在初始化回呼中使用資料來源,則可能會發生問題,因為資料尚未初始化。一個常見的範例是快取,它會急切地初始化,並在應用程式啟動時從資料庫載入資料。

為了繞過這個問題,您有兩個選項:將快取初始化策略變更為稍後的階段,或確保資料庫初始化程式首先初始化。

如果應用程式在您的控制之下,並且沒有其他情況,則變更快取初始化策略可能很容易。關於如何實作此策略的一些建議包括

  • 使快取在首次使用時延遲初始化,這可以改善應用程式啟動時間。

  • 讓您的快取或初始化快取的個別組件實作 LifecycleSmartLifecycle。當應用程式 Context 啟動時,您可以透過設定其 autoStartup 旗標來自動啟動 SmartLifecycle,並且您可以透過在封閉的 Context 上呼叫 ConfigurableApplicationContext.start() 來手動啟動 Lifecycle

  • 使用 Spring ApplicationEvent 或類似的自訂觀察器機制來觸發快取初始化。ContextRefreshedEvent 始終在 Context 準備好使用時 (在所有 Bean 都已初始化之後) 由 Context 發布,因此這通常是一個有用的 Hook (這就是 SmartLifecycle 預設的運作方式)。

確保資料庫初始化程式首先初始化也很容易。關於如何實作此策略的一些建議包括

  • 依賴 Spring BeanFactory 的預設行為,即 Bean 依註冊順序初始化。您可以透過採用 XML 組態中一組常見的 <import/> 元素來輕鬆安排這一點,這些元素會對您的應用程式模組進行排序,並確保資料庫和資料庫初始化首先列出。

  • 分隔 DataSource 和使用它的業務組件,並透過將它們放在個別的 ApplicationContext 實例中來控制它們的啟動順序 (例如,父 Context 包含 DataSource,而子 Context 包含業務組件)。這種結構在 Spring Web 應用程式中很常見,但可以更廣泛地應用。