DataSource の初期化

org.springframework.jdbc.datasource.init パッケージは、既存の DataSource の初期化をサポートします。組み込みデータベースのサポートは、アプリケーションの DataSource を作成および初期化するための 1 つのオプションを提供します。ただし、サーバー上のどこかで実行されるインスタンスを初期化する必要がある場合があります。

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>

上記の例は、データベースに対して指定された 2 つのスクリプトを実行します。最初のスクリプトはスキーマを作成し、2 番目のスクリプトはテーブルにテストデータセットを取り込みます。スクリプトの場所は、Spring のリソース(classpath*:/com/foo/**/sql/*-data.sql など)に使用される通常の Ant スタイルのワイルドカードを使用したパターンにすることもできます。パターンを使用する場合、スクリプトは URL またはファイル名の字句順に実行されます。

データベース初期化子のデフォルトの動作は、提供されたスクリプトを無条件に実行することです。これは、必ずしも希望するものとは限りません。たとえば、すでにテストデータが含まれているデータベースに対してスクリプトを実行する場合です。データを誤って削除する可能性は、最初にテーブルを作成してからデータを挿入するという一般的なパターン(前述)に従うことで低減されます。テーブルがすでに存在する場合、最初のステップは失敗します。

ただし、既存のデータの作成と削除をさらに制御するために、XML 名前空間にはいくつかの追加オプションがあります。最初のフラグは、初期化のオンとオフを切り替えるフラグです。これは、環境に応じて設定できます(システムプロパティまたは環境 Bean からブール値を取得するなど)。次の例では、システムプロパティから値を取得します。

<jdbc:initialize-database data-source="dataSource"
	enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
	<jdbc:script location="..."/>
</jdbc:initialize-database>
1INITIALIZE_DATABASE というシステムプロパティから enabled の値を取得します。

既存のデータで発生することを制御する 2 番目のオプションは、障害に対する耐性を高めることです。このために、次の例に示すように、イニシャライザーがスクリプトから実行する 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 (失敗したドロップを無視)、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 区切りスクリプトを @@ に設定します。
2db-schema.sql のセパレーターを ; に設定します。

この例では、2 つの test-data スクリプトは @@ をステートメント区切り文字として使用し、db-schema.sql のみが ; を使用します。この構成は、デフォルトの区切り文字が @@ であることを指定し、db-schema スクリプトのデフォルトをオーバーライドします。

XML 名前空間から取得するよりも多くの制御が必要な場合は、DataSourceInitializer を直接使用して、アプリケーションのコンポーネントとして定義できます。

データベースに依存する他のコンポーネントの初期化

大規模なクラスのアプリケーション(Spring コンテキストが開始されるまでデータベースを使用しないアプリケーション)は、データベースイニシャライザーをさらに複雑にすることなく使用できます。アプリケーションがそれらのいずれでもない場合、このセクションの残りを読む必要があるかもしれません。

データベース初期化子は DataSource インスタンスに依存し、その初期化コールバックで提供されるスクリプトを実行します(XML Bean 定義の init-method、コンポーネントの @PostConstruct メソッド、InitializingBean を実装するコンポーネントの afterPropertiesSet() メソッドに類似)。他の Bean が同じデータソースに依存し、初期化コールバックでデータソースを使用する場合、データがまだ初期化されていないために問題が発生する可能性があります。これの一般的な例は、先行初期化され、アプリケーションの起動時にデータベースからデータをロードするキャッシュです。

この課題を回避するには、2 つのオプションがあります。キャッシュ初期化戦略を後のフェーズに変更するか、データベース初期化子が最初に初期化されるようにします。

キャッシュの初期化戦略を変更するのは、アプリケーションが制御下にある場合で、そうでない場合は簡単です。これを実装する方法についてのいくつかの提案は次のとおりです。

  • 最初の使用時にキャッシュを遅延初期化して、アプリケーションの起動時間を改善します。

  • キャッシュまたはキャッシュを初期化する別のコンポーネントに Lifecycle または SmartLifecycle を実装させます。アプリケーションコンテキストが起動すると、autoStartup フラグを設定して SmartLifecycle を自動的に起動できます。また、囲んでいるコンテキストで ConfigurableApplicationContext.start() を呼び出して Lifecycle を手動で起動できます。

  • Spring ApplicationEvent または同様のカスタムオブザーバーメカニズムを使用して、キャッシュの初期化をトリガーします。ContextRefreshedEvent は(すべての Bean が初期化された後)使用準備ができたときに常にコンテキストによって公開されるため、多くの場合、これは便利なフックです(これが SmartLifecycle のデフォルトの動作です)。

データベース初期化子が最初に初期化されるようにすることも簡単です。これを実装する方法に関するいくつかの提案は次のとおりです。

  • Spring BeanFactory のデフォルトの動作に依存します。つまり、Bean は登録順に初期化されます。XML 構成の一連の <import/> 要素の一般的なプラクティスを採用してアプリケーションモジュールを並べ、データベースとデータベースの初期化が最初にリストされるようにすることで、簡単に調整できます。

  • DataSource とそれを使用するビジネスコンポーネントを分離し、個別の ApplicationContext インスタンスに配置して起動順序を制御します(たとえば、親コンテキストには DataSource が含まれ、子コンテキストにはビジネスコンポーネントが含まれます)。この構造は Spring Web アプリケーションでは一般的ですが、より一般的に適用できます。