初始化 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 相依於相同的資料來源,並在初始化回呼中使用資料來源,則可能會發生問題,因為資料尚未初始化。一個常見的範例是快取,它會急切地初始化,並在應用程式啟動時從資料庫載入資料。
為了繞過這個問題,您有兩個選項:將快取初始化策略變更為稍後的階段,或確保資料庫初始化程式首先初始化。
如果應用程式在您的控制之下,並且沒有其他情況,則變更快取初始化策略可能很容易。關於如何實作此策略的一些建議包括
-
使快取在首次使用時延遲初始化,這可以改善應用程式啟動時間。
-
讓您的快取或初始化快取的個別組件實作
Lifecycle
或SmartLifecycle
。當應用程式 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 應用程式中很常見,但可以更廣泛地應用。