此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
执行 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
方法ScriptUtils
了解更多详情。
ResourceDatabasePopulator
提供了一个基于对象的 API,用于以编程方式填充,
使用外部
资源。ResourceDatabasePopulator
提供配置字符的选项
编码、语句分隔符、注释分隔符和错误处理标志
解析和运行脚本。每个配置选项都有一个合理的
默认值。请参阅 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(..)
方法AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
在内部使用ResourceDatabasePopulator
运行 SQL 脚本。请参阅 Javadoc 中的
各种executeSqlScript(..)
方法了解更多详情。
使用 @Sql 以声明方式执行 SQL 脚本
除了上述以编程方式运行 SQL 脚本的机制之外,
您可以在 Spring TestContext 框架中以声明方式配置 SQL 脚本。
具体来说,您可以声明@Sql
将测试类或测试方法上的注释设置为
配置单个 SQL 语句或 SQL 脚本的资源路径,这些
在集成测试类或测试方法之前或之后针对给定数据库运行。
对@Sql
由SqlScriptsTestExecutionListener
,已启用
默认情况下。
方法级 但是,这不适用于为 |
路径资源语义
每条路径都被解释为一个弹簧Resource
.普通路径(例如"schema.sql"
) 被视为相对于
定义了测试类。以斜杠开头的路径被视为绝对路径
classpath 资源(例如"/org/example/schema.sql"
).引用
URL(例如,以classpath:
,file:
,http:
) 通过使用
指定的资源协议。
从 Spring Framework 6.2 开始,路径可能包含属性占位符(${…}
)将
被存储在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
脚本,取决于@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
是可选的,
但您可能需要使用@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 脚本在相应的测试方法之前运行。但是,如果您
需要在测试方法之后运行一组特定的脚本(例如,清理
数据库状态),您可以将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 开始,可以在
在测试类之后,通过设置executionPhase
类级别的属性@Sql
声明给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 脚本的全局配置。什么时候
直接使用config
属性的@Sql
注解@SqlConfig
用作在封闭中声明的 SQL 脚本的本地配置@Sql
注解。中的每个属性@SqlConfig
有一个隐式默认值,即
记录在相应属性的 javadoc 中。由于定义了
注解属性,不幸的是,它不是
可以分配一个null
添加到注释属性。因此,为了
支持继承的全局配置的覆盖,@SqlConfig
属性具有
显式默认值(对于字符串)、(对于数组)或""
{}
DEFAULT
(对于
枚举)。这种方法允许本地声明@SqlConfig
选择性覆盖
来自全局声明的各个属性@SqlConfig
通过提供值 other
比 、 或""
{}
DEFAULT
.全球@SqlConfig
属性在
当地@SqlConfig
属性不提供除 、 或 以外的显式值""
{}
DEFAULT
.因此,显式本地配置将覆盖全局配置。
提供的配置选项@Sql
和@SqlConfig
等同于那些
基金资助ScriptUtils
和ResourceDatabasePopulator
但是这些的超集
由<jdbc:initialize-database/>
XML 命名空间元素。请参阅
各个属性@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
超出了这个范围
参考手册,Javadoc@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()
方法是
run,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sql
script)由TransactionalTestExecutionListener
(请参阅事务管理
细节)。
合并和覆盖配置@SqlMergeMode
可以合并方法级@Sql
声明与
类级声明。例如,这允许您为
数据库模式或一些常见的测试数据,然后提供额外的
每个测试方法的特定用例测试数据。启用@Sql
合并,注释
您的测试类或测试方法@SqlMergeMode(MERGE)
.禁用合并
特定测试方法(或特定测试子类),可以切换回默认模式
通过@SqlMergeMode(OVERRIDE)
.请查阅@SqlMergeMode
注释文档部分有关示例和更多详细信息。