此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
执行 SQL 脚本
在针对关系数据库编写集成测试时,通常
运行 SQL 脚本以修改数据库架构或将测试数据插入表中。这spring-jdbc
模块支持初始化嵌入式或现有数据库
通过在 SpringApplicationContext
已加载。有关详细信息,请参阅嵌入式数据库支持和使用嵌入式数据库测试数据访问逻辑。
尽管初始化数据库以进行测试一次非常有用,但当ApplicationContext
加载时,有时必须能够修改
数据库。以下部分介绍如何运行 SQL
在集成测试期间以编程和声明方式编写脚本。
以编程方式执行 SQL 脚本
Spring 提供了以下选项,用于在 集成测试方法。
-
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
可能适合
您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc
methods 中的ScriptUtils
了解更多详情。
ResourceDatabasePopulator
提供基于对象的 API,用于以编程方式填充,
使用外部
资源。ResourceDatabasePopulator
提供用于配置字符的选项
encoding、语句分隔符、注释分隔符和错误处理标志
解析并运行脚本。每个配置选项都有一个合理的
default 值。请参阅 javadoc 以获取
有关默认值的详细信息。要运行在ResourceDatabasePopulator
中,您可以调用populate(Connection)
method 设置为
对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 脚本。同样,executeSqlScript(..)
methods 中的AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
内部使用ResourceDatabasePopulator
运行 SQL 脚本。请参阅 Javadoc 以获取
各种executeSqlScript(..)
方法了解更多详细信息。
使用 @Sql 以声明方式执行 SQL 脚本
除了上述以编程方式运行 SQL 脚本的机制外,
你可以在 Spring TestContext Framework 中以声明方式配置 SQL 脚本。
具体来说,您可以声明@Sql
注解添加到
配置单个 SQL 语句或 SQL 脚本的资源路径,这些脚本应该是
在集成测试类或测试方法之前或之后针对给定数据库运行。
支持@Sql
由SqlScriptsTestExecutionListener
,该
默认情况下。
方法级 但是,这不适用于为 |
路径资源语义
每个路径都解释为一个 SpringResource
.普通路径(例如"schema.sql"
) 被视为相对于
定义测试类。以斜杠开头的路径被视为绝对路径
Classpath 资源(例如"/org/example/schema.sql"
).引用
URL(例如,前缀为classpath:
,file:
,http:
) 使用
指定的资源协议。
从 Spring Framework 6.2 开始,paths 可以包含属性占位符(${…}
) 这将
替换为存储在Environment
测试的ApplicationContext
.
以下示例演示如何使用@Sql
在类级别和方法级别
在基于 JUnit Jupiter 的集成测试类中:
-
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 脚本或语句,则尝试检测default
script 的@Sql
被声明。如果无法检测到默认值,则IllegalStateException
被抛出。
-
类级声明:如果带注释的测试类为
com.example.MyTest
这 对应的默认脚本是classpath:com/example/MyTest.sql
. -
方法级声明:如果带注释的测试方法命名为
testMethod()
和 is 在类中定义com.example.MyTest
,对应的默认脚本为classpath:com/example/MyTest.testMethod.sql
.
记录 SQL 脚本和语句
如果要查看正在执行哪些 SQL 脚本,请将org.springframework.test.context.jdbc
logging 类别设置为DEBUG
.
如果要查看正在执行的 SQL 语句,请将org.springframework.jdbc.datasource.init
logging 类别设置为DEBUG
.
声明多个@Sql
集
如果需要为给定的测试类或测试配置多组 SQL 脚本
方法,但具有不同的语法配置、不同的错误处理规则,或者
每个 set 的执行阶段不同,你可以声明@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
script 使用
单行注释的不同语法。
以下示例与前面的示例相同,只是@Sql
声明在@SqlGroup
.的使用@SqlGroup
是可选的,
但您可能需要使用@SqlGroup
与其他 JVM 语言兼容。
-
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 脚本在相应的测试方法之前运行。但是,如果您
需要在测试方法之后运行一组特定的脚本(例如,为了清理
database 状态),则可以设置executionPhase
属性@Sql
自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 开始,可以在
在 test 类之后,通过设置executionPhase
类级别中的属性@Sql
declaration 添加到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 . |
Script Configuration 替换为@SqlConfig
您可以使用@SqlConfig
注解。
当在集成测试类上声明为类级注释时,@SqlConfig
用作测试类层次结构中所有 SQL 脚本的全局配置。什么时候
使用config
属性的@Sql
注解@SqlConfig
用作在封闭中声明的 SQL 脚本的本地配置@Sql
注解。中的每个属性@SqlConfig
具有隐式默认值,即
记录在相应属性的 javadoc 中。由于为
annotation 属性,遗憾的是,它不是
可以分配null
添加到 annotation 属性中。因此,为了
支持继承的全局配置的覆盖,@SqlConfig
attributes 具有
显式默认值 (for Strings)、(for arrays) 或""
{}
DEFAULT
(对于
枚举)。这种方法允许@SqlConfig
选择性覆盖
全局声明中的各个属性@SqlConfig
通过提供值 Other
than 、 或""
{}
DEFAULT
.全球@SqlConfig
每当
当地@SqlConfig
attributes 不提供除 、 、 或""
{}
DEFAULT
.因此,显式本地配置将覆盖全局配置。
提供的配置选项@Sql
和@SqlConfig
等同于那些
支持单位ScriptUtils
和ResourceDatabasePopulator
而是那些
由<jdbc:initialize-database/>
XML 命名空间元素。请参阅 javadoc
中的各个属性@Sql
和@SqlConfig
了解详情。
事务管理@Sql
默认情况下,SqlScriptsTestExecutionListener
推断所需的交易
使用@Sql
.具体来说,SQL 脚本是运行的
如果没有事务,则在现有的 Spring 管理的事务中(例如,
事务由TransactionalTestExecutionListener
对于带有@Transactional
)或在隔离的事务中,具体取决于配置的值
的transactionMode
属性@SqlConfig
以及PlatformTransactionManager
在测试的ApplicationContext
.作为最低限度,
但是,javax.sql.DataSource
必须存在于测试的ApplicationContext
.
如果SqlScriptsTestExecutionListener
要检测DataSource
和PlatformTransactionManager
并推断事务语义不适合您的需求,
您可以通过设置dataSource
和transactionManager
的属性@SqlConfig
.此外,您可以控制事务传播
行为,方法是将transactionMode
属性@SqlConfig
(例如,如果
脚本应在隔离的事务中运行)。虽然对所有
事务管理支持的选项@Sql
超出了此范围
参考手册,用于@SqlConfig
和SqlScriptsTestExecutionListener
提供详细信息,以下示例显示了典型的测试场景
它使用 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()
method 为
run,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sql
脚本)由TransactionalTestExecutionListener
(请参阅 事务管理
详细信息)。
合并和覆盖配置@SqlMergeMode
可以合并方法级别@Sql
声明
类级声明。例如,这允许您为
数据库架构或一些常见的测试数据,然后提供额外的
每个测试方法的用例特定测试数据。要启用@Sql
mergeging 中,注释
您的 test 类或 test method 替换为@SqlMergeMode(MERGE)
.要禁用
Specific Test Method(或Specific Test Subclass),可以切换回默认模式
通过@SqlMergeMode(OVERRIDE)
.查阅@SqlMergeMode
注释文档部分以获取示例和更多详细信息。