執行 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 提供了一個基於物件的 API,用於透過使用外部資源中定義的 SQL 指令碼以程式設計方式填充、初始化或清理資料庫。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 內部委託給 ScriptUtils 來解析和執行 SQL 指令碼。同樣,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests 中的 executeSqlScript(..) 方法內部使用 ResourceDatabasePopulator 來執行 SQL 指令碼。有關詳細資訊,請參閱各種 executeSqlScript(..) 方法的 Javadoc。

使用 @Sql 宣告式執行 SQL 指令碼

除了上述以程式設計方式執行 SQL 指令碼的機制外,您還可以在 Spring TestContext 框架中宣告性地配置 SQL 指令碼。具體來說,您可以在測試類或測試方法上宣告 @Sql 註解,以配置在整合測試類或測試方法之前或之後應針對給定資料庫執行的單個 SQL 語句或 SQL 指令碼的資源路徑。對 @Sql 的支援由 SqlScriptsTestExecutionListener 提供,該監聽器預設啟用。

方法級的 @Sql 宣告預設會覆蓋類級的宣告,但可以透過 @SqlMergeMode 為每個測試類或每個測試方法配置此行為。有關詳細資訊,請參閱使用 @SqlMergeMode 合併和覆蓋配置

然而,這不適用於為 BEFORE_TEST_CLASSAFTER_TEST_CLASS 執行階段配置的類級宣告。此類宣告無法被覆蓋,並且相應的指令碼和語句將除了任何方法級指令碼和語句之外,在每個類中執行一次。

路徑資源語義

每個路徑都被解釋為 Spring Resource。一個純路徑(例如,"schema.sql")被視為相對於定義測試類的包的類路徑資源。以斜槓開頭的路徑被視為絕對類路徑資源(例如,"/org/example/schema.sql")。引用 URL 的路徑(例如,以 classpath:file:http: 為字首的路徑)透過指定的資源協議載入。

從 Spring Framework 6.2 開始,路徑可以包含屬性佔位符 (${…​}),這些佔位符將由儲存在測試 ApplicationContextEnvironment 中的屬性替換。

以下示例展示瞭如何在基於 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 的宣告位置檢測一個預設指令碼。如果無法檢測到預設指令碼,則會丟擲 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
}
ISOLATEDAFTER_TEST_METHOD 分別從 Sql.TransactionModeSql.ExecutionPhase 靜態匯入。

從 Spring Framework 6.1 開始,可以透過將類級 @Sql 宣告中的 executionPhase 屬性設定為 BEFORE_TEST_CLASSAFTER_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 屬性不提供除 ""{}DEFAULT 之外的顯式值時,將繼承全域性 @SqlConfig 屬性。因此,顯式本地配置會覆蓋全域性配置。

@Sql@SqlConfig 提供的配置選項等同於 ScriptUtilsResourceDatabasePopulator 支援的選項,但它們是 <jdbc:initialize-database/> XML 名稱空間元素提供的選項的超集。有關詳細資訊,請參閱 @Sql@SqlConfig 中各個屬性的 javadoc。

@Sql 的事務管理

預設情況下,SqlScriptsTestExecutionListener 會推斷使用 @Sql 配置的指令碼所需的事務語義。具體來說,SQL 指令碼將在沒有事務的情況下執行,在現有 Spring 管理的事務中(例如,由 TransactionalTestExecutionListener 為用 @Transactional 註解的測試管理的事務),或在隔離事務中執行,具體取決於 @SqlConfigtransactionMode 屬性的配置值以及測試的 ApplicationContext 中是否存在 PlatformTransactionManager。但是,最起碼,測試的 ApplicationContext 中必須存在 javax.sql.DataSource

如果 SqlScriptsTestExecutionListener 用於檢測 DataSourcePlatformTransactionManager 以及推斷事務語義的演算法不符合您的需求,您可以透過設定 @SqlConfigdataSourcetransactionManager 屬性來指定顯式名稱。此外,您可以透過設定 @SqlConfigtransactionMode 屬性來控制事務傳播行為(例如,指令碼是否應該在隔離事務中執行)。儘管對 @Sql 的事務管理所有受支援選項的全面討論超出了本參考手冊的範圍,但 @SqlConfigSqlScriptsTestExecutionListener 的 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.")
	}
}

請注意,在執行 usersTest() 方法後無需清理資料庫,因為對資料庫所做的任何更改(無論是在測試方法內還是在 /test-data.sql 指令碼內)都會由 TransactionalTestExecutionListener 自動回滾(有關詳細資訊,請參閱事務管理)。

使用 @SqlMergeMode 合併和覆蓋配置

可以將方法級 @Sql 宣告與類級宣告合併。例如,這允許您為資料庫模式或一些通用測試資料為每個測試類提供一次配置,然後為每個測試方法提供額外的、特定於用例的測試資料。要啟用 @Sql 合併,請使用 @SqlMergeMode(MERGE) 註解您的測試類或測試方法。要停用特定測試方法(或特定測試子類)的合併,您可以透過 @SqlMergeMode(OVERRIDE) 切換回預設模式。有關示例和更多詳細資訊,請參閱 @SqlMergeMode 註解文件部分

© . This site is unofficial and not affiliated with VMware.