此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

执行 SQL 脚本

在针对关系数据库编写集成测试时,通常有利于 运行 SQL 脚本以修改数据库模式或将测试数据插入表中。这spring-jdbc模块支持初始化嵌入式或现有数据库 通过在 SpringApplicationContext已加载。有关详细信息,请参阅嵌入式数据库支持使用嵌入式数据库测试数据访问逻辑spring-doc.cadn.net.cn

虽然初始化数据库进行一次测试非常有用,但当ApplicationContext加载时,有时必须能够修改 集成测试期间的数据库。以下部分介绍如何运行 SQL 在集成测试期间以编程和声明方式编写脚本。spring-doc.cadn.net.cn

以编程方式执行 SQL 脚本

Spring 提供了以下选项,用于在 集成测试方法。spring-doc.cadn.net.cn

ScriptUtils提供了用于处理 SQL 的静态实用程序方法的集合 脚本,主要供框架内内部使用。但是,如果您 需要完全控制 SQL 脚本的解析和运行方式,ScriptUtils可能适合 您的需求比后面描述的其他一些替代方案更好。请参阅个人的 javadoc 方法ScriptUtils了解更多详情。spring-doc.cadn.net.cn

ResourceDatabasePopulator提供了一个基于对象的 API,用于以编程方式填充, 使用外部 资源。ResourceDatabasePopulator提供配置字符的选项 编码、语句分隔符、注释分隔符和错误处理标志 解析和运行脚本。每个配置选项都有一个合理的 默认值。请参阅 javadoc 有关默认值的详细信息。要运行在ResourceDatabasePopulator,您可以调用populate(Connection)method 设置为 针对java.sql.Connectionexecute(DataSource)方法 以针对javax.sql.DataSource.以下示例 为测试模式和测试数据指定 SQL 脚本,将语句分隔符设置为 ,并针对@@DataSource:spring-doc.cadn.net.cn

@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(..)方法AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests在内部使用ResourceDatabasePopulator运行 SQL 脚本。请参阅 Javadoc 中的 各种executeSqlScript(..)方法了解更多详情。spring-doc.cadn.net.cn

使用 @Sql 以声明方式执行 SQL 脚本

除了上述以编程方式运行 SQL 脚本的机制之外, 您可以在 Spring TestContext 框架中以声明方式配置 SQL 脚本。 具体来说,您可以声明@Sql将测试类或测试方法上的注释设置为 配置单个 SQL 语句或 SQL 脚本的资源路径,这些 在集成测试类或测试方法之前或之后针对给定数据库运行。 对@SqlSqlScriptsTestExecutionListener,已启用 默认情况下。spring-doc.cadn.net.cn

方法级@Sql默认情况下,声明会覆盖类级声明,但这个 可以通过以下方式为每个测试类或每个测试方法配置行为@SqlMergeMode.看合并和覆盖配置@SqlMergeMode了解更多详情。spring-doc.cadn.net.cn

但是,这不适用于为BEFORE_TEST_CLASSAFTER_TEST_CLASS执行阶段。此类声明不能 重写,相应的脚本和语句将每个类执行一次 除了任何方法级脚本和语句之外。spring-doc.cadn.net.cn

路径资源语义

每条路径都被解释为一个弹簧Resource.普通路径(例如"schema.sql") 被视为相对于 定义了测试类。以斜杠开头的路径被视为绝对路径 classpath 资源(例如"/org/example/schema.sql").引用 URL(例如,以classpath:,file:,http:) 通过使用 指定的资源协议。spring-doc.cadn.net.cn

从 Spring Framework 6.2 开始,路径可能包含属性占位符(${…​})将 被存储在Environment的测试ApplicationContext.spring-doc.cadn.net.cn

以下示例演示如何使用@Sql在类级别和方法级别 在基于 JUnit Jupiter 的集成测试类中:spring-doc.cadn.net.cn

@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被抛出。spring-doc.cadn.net.cn

  • 类级声明:如果带注释的测试类是com.example.MyTest这 对应的默认脚本是classpath:com/example/MyTest.sql.spring-doc.cadn.net.cn

  • 方法级声明:如果带注释的测试方法名为testMethod()并且是 在类中定义com.example.MyTest,对应的默认脚本是classpath:com/example/MyTest.testMethod.sql.spring-doc.cadn.net.cn

记录 SQL 脚本和语句

如果您想查看正在执行哪些 SQL 脚本,请将org.springframework.test.context.jdbc日志记录类别设置为DEBUG.spring-doc.cadn.net.cn

如果您想查看正在执行哪些 SQL 语句,请将org.springframework.jdbc.datasource.init日志记录类别设置为DEBUG.spring-doc.cadn.net.cn

声明多个@Sql

如果您需要为给定的测试类或测试配置多组 SQL 脚本 方法,但具有不同的语法配置、不同的错误处理规则,或 每个集合不同的执行阶段,您可以声明多个实例@Sql.您可以 任一用途@Sql作为可重复的注释,或者您可以使用@SqlGroup注解 作为显式容器,用于声明@Sql.spring-doc.cadn.net.cn

以下示例演示如何使用@Sql作为可重复的注释:spring-doc.cadn.net.cn

@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脚本使用 单行注释的语法不同。spring-doc.cadn.net.cn

以下示例与前面的示例相同,不同之处在于@Sql声明在@SqlGroup.使用@SqlGroup是可选的, 但您可能需要使用@SqlGroup与其他 JVM 语言兼容。spring-doc.cadn.net.cn

@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属性@SqlAFTER_TEST_METHOD,如以下示例所示:spring-doc.cadn.net.cn

@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 开始,可以在 在测试类之后,通过设置executionPhase类级别的属性@Sql声明给BEFORE_TEST_CLASSAFTER_TEST_CLASS,如以下示例所示:spring-doc.cadn.net.cn

@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.因此,显式本地配置将覆盖全局配置。spring-doc.cadn.net.cn

提供的配置选项@Sql@SqlConfig等同于那些 基金资助ScriptUtilsResourceDatabasePopulator但是这些的超集 由<jdbc:initialize-database/>XML 命名空间元素。请参阅 各个属性@Sql@SqlConfig了解详情。spring-doc.cadn.net.cn

事务管理@Sql

默认情况下,SqlScriptsTestExecutionListener推断出所需的交易 使用@Sql.具体来说,运行 SQL 脚本 没有事务,则在现有的 Spring 管理事务(例如,一个 事务由TransactionalTestExecutionListener对于标注为@Transactional),或在隔离事务中,具体取决于配置的值 的transactionMode属性@SqlConfig并且存在PlatformTransactionManager在测试的ApplicationContext.作为最低限度, 但是,一个javax.sql.DataSource必须存在于测试的ApplicationContext.spring-doc.cadn.net.cn

如果SqlScriptsTestExecutionListener检测DataSourcePlatformTransactionManager并推断事务语义不适合你的需求, 您可以通过设置dataSourcetransactionManager属性@SqlConfig.此外,您可以控制事务传播 通过将transactionMode属性@SqlConfig(例如,是否 脚本应在隔离事务中运行)。虽然对所有 支持的事务管理选项@Sql超出了这个范围 参考手册,Javadoc@SqlConfigSqlScriptsTestExecutionListener提供详细信息,以下示例显示了一个典型的测试场景 使用 JUnit Jupiter 和事务测试@Sql:spring-doc.cadn.net.cn

@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.sqlscript)由TransactionalTestExecutionListener(请参阅事务管理 细节)。spring-doc.cadn.net.cn

合并和覆盖配置@SqlMergeMode

可以合并方法级@Sql声明与 类级声明。例如,这允许您为 数据库模式或一些常见的测试数据,然后提供额外的 每个测试方法的特定用例测试数据。启用@Sql合并,注释 您的测试类或测试方法@SqlMergeMode(MERGE).禁用合并 特定测试方法(或特定测试子类),可以切换回默认模式 通过@SqlMergeMode(OVERRIDE).请查阅@SqlMergeMode注释文档部分有关示例和更多详细信息。spring-doc.cadn.net.cn