SQL スクリプトの実行

リレーショナルデータベースに対して統合テストを作成する場合、SQL スクリプトを実行してデータベーススキーマを変更したり、テストデータをテーブルに挿入したりすることが多くの場合有益です。spring-jdbc モジュールは、Spring ApplicationContext がロードされたときに SQL スクリプトを実行することにより、組み込みまたは既存のデータベースを初期化するためのサポートを提供します。詳細については、組み込みデータベースのサポートおよび組み込みデータベースを使用したデータアクセスロジックのテストを参照してください。

それは ApplicationContext がロードされたときに一度テストするためのデータベースを初期化するために非常に有用であるが、時には統合テスト中にデータベースを変更することができることが不可欠です。次のセクションでは、統合テスト中に SQL スクリプトをプログラムおよび実行により実行する方法について説明します。

プログラムによる SQL スクリプトの実行

Spring には、統合テストメソッド内でプログラムによって SQL スクリプトを実行するための以下のオプションがあります。

  • org.springframework.jdbc.datasource.init.ScriptUtils

  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils は、SQL スクリプトを操作するための静的ユーティリティメソッドのコレクションを提供し、主にフレームワーク内での内部使用を目的としています。ただし、SQL スクリプトの解析方法と実行方法を完全に制御する必要がある場合、ScriptUtils は、後で説明する他の代替手段よりもニーズに適している場合があります。詳細については、ScriptUtils の個々のメソッドの javadoc を参照してください。

ResourceDatabasePopulator は、外部リソースで定義された SQL スクリプトを使用して、プログラムでデータベースにデータを入力、初期化、クリーンアップするためのオブジェクトベースの API を提供します。ResourceDatabasePopulator には、スクリプトの解析および実行時に使用される文字エンコード、ステートメント区切り文字、コメント区切り文字、エラー処理フラグを構成するためのオプションがあります。各構成オプションには、妥当なデフォルト値があります。デフォルト値の詳細については、javadoc を参照してください。ResourceDatabasePopulator で構成されたスクリプトを実行するには、populate(Connection) メソッドを呼び出して、java.sql.Connection に対してポピュレーターを実行するか、execute(DataSource) メソッドを呼び出して、javax.sql.DataSource に対してポピュレーターを実行します。次の例では、テストスキーマとテストデータの SQL スクリプトを指定し、ステートメントセパレーターを @@ に設定し、スクリプトを DataSource に対して実行します。

  • Java

  • Kotlin

@Test
void databaseTest() {
	ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
	populator.addScripts(
			new ClassPathResource("test-schema.sql"),
			new ClassPathResource("test-data.sql"));
	populator.setSeparator("@@");
	populator.execute(this.dataSource);
	// run code that uses the test schema and data
}
@Test
fun databaseTest() {
	val populator = ResourceDatabasePopulator()
	populator.addScripts(
			ClassPathResource("test-schema.sql"),
			ClassPathResource("test-data.sql"))
	populator.setSeparator("@@")
	populator.execute(dataSource)
	// run code that uses the test schema and data
}

ResourceDatabasePopulator は、SQL スクリプトの解析と実行のために内部で ScriptUtils に委譲することに注意してください。同様に、AbstractTransactionalJUnit4SpringContextTests および AbstractTransactionalTestNGSpringContextTests の executeSqlScript(..) メソッドは、内部で ResourceDatabasePopulator を使用して SQL スクリプトを実行します。詳細については、さまざまな executeSqlScript(..) メソッドの Javadoc を参照してください。

@Sql を使用して SQL スクリプトを宣言的に実行する

SQL スクリプトをプログラムで実行するための前述のメカニズムに加えて、Spring TestContext フレームワーク で SQL スクリプトを宣言的に構成できます。具体的には、テストクラスまたはテストメソッドで @Sql アノテーションを宣言して、統合テストクラスまたはテストメソッドの前後に特定のデータベースに対して実行する必要がある個々の SQL ステートメントまたは SQL スクリプトへのリソースパスを構成できます。@Sql のサポートは SqlScriptsTestExecutionListener によって提供され、デフォルトで有効になっています。

メソッドレベルの @Sql 宣言はデフォルトでクラスレベルの宣言をオーバーライドしますが、この動作は @SqlMergeMode を介してテストクラスごとまたはテストメソッドごとに構成できます。詳細については、@SqlMergeMode を使用した構成のマージとオーバーライドを参照してください。

ただし、これは、BEFORE_TEST_CLASS または AFTER_TEST_CLASS 実行フェーズ用に構成されたクラスレベルの宣言には適用されません。このような宣言はオーバーライドできず、メソッドレベルのスクリプトとステートメントに加えて、対応するスクリプトとステートメントがクラスごとに 1 回実行されます。

パスリソースセマンティクス

各パスは Spring Resource として解釈されます。プレーンパス("schema.sql" など)は、テストクラスが定義されているパッケージに関連するクラスパスリソースとして扱われます。スラッシュで始まるパスは、絶対クラスパスリソースとして扱われます(たとえば、"/org/example/schema.sql")。URL を参照するパス(たとえば、接頭辞 classpath:file:http: のパス)は、指定されたリソースプロトコルを使用してロードされます。

次の例は、JUnit Jupiter ベースの統合テストクラス内で、クラスレベルおよびメソッドレベルで @Sql を使用する方法を示しています。

  • Java

  • Kotlin

@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

	@Test
	void emptySchemaTest() {
		// run code that uses the test schema without any test data
	}

	@Test
	@Sql({"/test-schema.sql", "/test-user-data.sql"})
	void userTest() {
		// run code that uses the test schema and test data
	}
}
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

	@Test
	fun emptySchemaTest() {
		// run code that uses the test schema without any test data
	}

	@Test
	@Sql("/test-schema.sql", "/test-user-data.sql")
	fun userTest() {
		// run code that uses the test schema and test data
	}
}

デフォルトのスクリプト検出

SQL スクリプトまたはステートメントが指定されていない場合、@Sql が宣言されている場所に応じて、default スクリプトの検出が試行されます。デフォルトを検出できない場合、IllegalStateException がスローされます。

  • クラスレベルの宣言: アノテーション付きテストクラスが com.example.MyTest の場合、対応するデフォルトスクリプトは classpath:com/example/MyTest.sql です。

  • メソッドレベルの宣言: アノテーション付きテストメソッドの名前が testMethod() で、クラス com.example.MyTest で定義されている場合、対応するデフォルトスクリプトは classpath:com/example/MyTest.testMethod.sql です。

SQL スクリプトとステートメントのログ記録

どの SQL スクリプトが実行されているかを確認したい場合は、org.springframework.test.context.jdbc ログカテゴリを DEBUG に設定します。

どの SQL ステートメントが実行されているかを確認したい場合は、org.springframework.jdbc.datasource.init ログカテゴリを DEBUG に設定します。

複数の @Sql セットの宣言

特定のテストクラスまたはテストメソッドに対して SQL スクリプトの複数のセットを構成する必要があるが、セットごとに異なる構文構成、異なるエラー処理ルール、異なる実行フェーズを使用する必要がある場合は、@Sql の複数のインスタンスを宣言できます。@Sql を反復可能なアノテーションとして使用することも、@SqlGroup アノテーションを @Sql の複数のインスタンスを宣言するための明示的なコンテナーとして使用することもできます。

次の例は、@Sql を反復可能なアノテーションとして使用する方法を示しています。

  • Java

  • Kotlin

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
	// run code that uses the test schema and test data
}
@Test
@Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
fun userTest() {
	// run code that uses the test schema and test data
}

前の例で示したシナリオでは、test-schema.sql スクリプトは単一行コメントに異なる構文を使用します。

次の例は、@Sql 宣言が @SqlGroup 内でグループ化されていることを除いて、前の例と同じです。@SqlGroup の使用はオプションですが、他の JVM 言語との互換性のために @SqlGroup の使用が必要になる場合があります。

  • Java

  • Kotlin

@Test
@SqlGroup({
	@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
	@Sql("/test-user-data.sql")
)}
void userTest() {
	// run code that uses the test schema and test data
}
@Test
@SqlGroup(
	Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
	Sql("/test-user-data.sql")
)
fun userTest() {
	// Run code that uses the test schema and test data
}

スクリプト実行フェーズ

デフォルトでは、SQL スクリプトは対応するテストメソッドの前に実行されます。ただし、テストメソッドの後に特定のスクリプトセットを実行する必要がある場合 (たとえば、データベース状態をクリーンアップするため)、次の例に示すように、@Sql の executionPhase 属性を AFTER_TEST_METHOD に設定できます。

  • Java

  • Kotlin

@Test
@Sql(
	scripts = "create-test-data.sql",
	config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
	scripts = "delete-test-data.sql",
	config = @SqlConfig(transactionMode = ISOLATED),
	executionPhase = AFTER_TEST_METHOD
)
void userTest() {
	// run code that needs the test data to be committed
	// to the database outside of the test's transaction
}
@Test
@Sql("create-test-data.sql",
	config = SqlConfig(transactionMode = ISOLATED))
@Sql("delete-test-data.sql",
	config = SqlConfig(transactionMode = ISOLATED),
	executionPhase = AFTER_TEST_METHOD)
fun userTest() {
	// run code that needs the test data to be committed
	// to the database outside of the test's transaction
}
ISOLATED と AFTER_TEST_METHOD は、それぞれ Sql.TransactionMode と Sql.ExecutionPhase から静的にインポートされます。

Spring Framework 6.1 以降では、次の例に示すように、クラスレベルの @Sql 宣言の executionPhase 属性を BEFORE_TEST_CLASS または AFTER_TEST_CLASS に設定することで、テストクラスの前後に特定のスクリプトセットを実行できます。

  • Java

  • Kotlin

@SpringJUnitConfig
@Sql(scripts = "/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {

	@Test
	void emptySchemaTest() {
		// run code that uses the test schema without any test data
	}

	@Test
	@Sql("/test-user-data.sql")
	void userTest() {
		// run code that uses the test schema and test data
	}
}
@SpringJUnitConfig
@Sql("/test-schema.sql", executionPhase = BEFORE_TEST_CLASS)
class DatabaseTests {

	@Test
	fun emptySchemaTest() {
		// run code that uses the test schema without any test data
	}

	@Test
	@Sql("/test-user-data.sql")
	fun userTest() {
		// run code that uses the test schema and test data
	}
}
BEFORE_TEST_CLASS は Sql.ExecutionPhase から静的にインポートされます。

@SqlConfig を使用したスクリプト構成

@SqlConfig アノテーションを使用して、スクリプトの解析とエラー処理を構成できます。統合テストクラスでクラスレベルのアノテーションとして宣言されている場合、@SqlConfig は、テストクラス階層内のすべての SQL スクリプトのグローバル構成として機能します。@Sql アノテーションの config 属性を使用して直接宣言された場合、@SqlConfig は、囲んでいる @Sql アノテーション内で宣言された SQL スクリプトのローカル構成として機能します。@SqlConfig のすべての属性には暗黙のデフォルト値があり、対応する属性の javadoc に記載されています。Java 言語仕様でアノテーション属性に定義されている規則により、残念ながら、null の値をアノテーション属性に割り当てることはできません。継承されたグローバル構成のオーバーライドをサポートするために、@SqlConfig 属性には、"" (文字列の場合)、{} (配列の場合)、DEFAULT (列挙の場合)のいずれかの明示的なデフォルト値があります。このアプローチにより、@SqlConfig のローカル宣言は、""{}DEFAULT 以外の値を提供することにより、@SqlConfig のグローバル宣言から個々の属性を選択的にオーバーライドできます。グローバル @SqlConfig 属性は、ローカル @SqlConfig 属性が ""{}DEFAULT 以外の明示的な値を提供しない場合は常に継承されます。明示的なローカル構成はグローバル構成をオーバーライドします。

@Sql および @SqlConfig によって提供される構成オプションは、ScriptUtils および ResourceDatabasePopulator によってサポートされるものと同等ですが、<jdbc:initialize-database/> XML 名前空間要素によって提供される構成オプションのスーパーセットです。詳細については、@Sql (Javadoc) および @SqlConfig (Javadoc) の個々の属性の javadoc を参照してください。

@Sql のトランザクション管理

デフォルトでは、SqlScriptsTestExecutionListener は @Sql を使用して構成されたスクリプトに必要なトランザクションセマンティクスを推測します。具体的には、SQL スクリプトは、transactionMode 属性の設定値に応じて、トランザクションなしで、既存の Spring 管理トランザクション(たとえば、@Transactional アノテーションが付けられたテスト用の TransactionalTestExecutionListener によって管理されるトランザクション)、または分離されたトランザクション内で実行されます。@SqlConfig およびテストの ApplicationContext の PlatformTransactionManager の存在。ただし、最低限、テストの ApplicationContext に javax.sql.DataSource が存在する必要があります。

SqlScriptsTestExecutionListener が DataSource および PlatformTransactionManager を検出してトランザクションセマンティクスを推測するために使用するアルゴリズムがニーズに合わない場合は、@SqlConfig の dataSource および transactionManager 属性を設定して明示的な名前を指定できます。さらに、@SqlConfig (たとえば、スクリプトを分離されたトランザクションで実行するかどうか) の transactionMode 属性を設定することにより、トランザクション伝播の動作を制御できます。@Sql でサポートされているすべてのトランザクション管理オプションについての詳細な説明は、このリファレンスマニュアルの範囲外ですが、@SqlConfig (Javadoc) SqlScriptsTestExecutionListener (Javadoc) の javadoc には詳細な情報が記載されています。次の例は、JUnit Jupiter と @Sql によるトランザクションテストを使用した一般的なテストシナリオを示しています。

  • Java

  • Kotlin

@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

	final JdbcTemplate jdbcTemplate;

	@Autowired
	TransactionalSqlScriptsTests(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	@Test
	@Sql("/test-data.sql")
	void usersTest() {
		// verify state in test database:
		assertNumUsers(2);
		// run code that uses the test data...
	}

	int countRowsInTable(String tableName) {
		return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
	}

	void assertNumUsers(int expected) {
		assertEquals(expected, countRowsInTable("user"),
			"Number of rows in the [user] table.");
	}
}
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {

	val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)

	@Test
	@Sql("/test-data.sql")
	fun usersTest() {
		// verify state in test database:
		assertNumUsers(2)
		// run code that uses the test data...
	}

	fun countRowsInTable(tableName: String): Int {
		return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
	}

	fun assertNumUsers(expected: Int) {
		assertEquals(expected, countRowsInTable("user"),
				"Number of rows in the [user] table.")
	}
}

データベースに加えられた変更 (テストメソッド内または /test-data.sql スクリプト内) はすべて TransactionalTestExecutionListener によって自動的にロールバックされるため、usersTest() メソッドの実行後にデータベースをクリーンアップする必要がないことに注意してください (詳細についてはトランザクション管理を参照))。

@SqlMergeMode を使用した構成のマージとオーバーライド

Spring Framework 5.2 以降、メソッドレベルの @Sql 宣言をクラスレベルの宣言とマージすることが可能です。例: これにより、データベーススキーマまたはいくつかの一般的なテストデータの構成をテストクラスごとに 1 回提供し、テストメソッドごとに追加のユースケース固有のテストデータを提供できます。@Sql マージを有効にするには、テストクラスまたはテストメソッドに @SqlMergeMode(MERGE) でアノテーションを付けます。特定のテストメソッド(または特定のテストサブクラス)のマージを無効にするには、@SqlMergeMode(OVERRIDE) を使用してデフォルトモードに戻すことができます。例と詳細については、@SqlMergeMode アノテーションドキュメントセクションを参照してください。