对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

执行SQL脚本

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

虽然在加载 ApplicationContext 时一次性初始化数据库以进行测试非常有用,但有时在集成测试期间能够修改数据库是必不可少的。以下部分解释了如何在集成测试期间程序化和声明性地运行SQL脚本。spring-doc.cadn.net.cn

通过编程方式执行SQL脚本

Spring 提供了以下选项,用于在集成测试方法中程序化地执行 SQL 脚本。spring-doc.cadn.net.cn

ScriptUtils 提供了一组静态实用方法,用于处理 SQL 脚本,并主要用于框架内部。但是,如果你需要完全控制 SQL 脚本的解析和运行方式,ScriptUtils 可能比后面描述的一些其他替代方案更适合你的需求。有关 ScriptUtils 中各个方法的详细信息,请参阅 javadocspring-doc.cadn.net.cn

ResourceDatabasePopulator 提供了一个基于对象的API,用于通过使用外部资源中定义的SQL脚本编程地填充、初始化或清理数据库。ResourceDatabasePopulator 提供了配置解析和执行脚本时使用的字符编码、语句分隔符、注释分隔符以及错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参阅 javadoc。要运行在ResourceDatabasePopulator 中配置的脚本,您可以调用populate(Connection) 方法来针对java.sql.Connectionexecute(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 脚本。类似地,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests 中的 executeSqlScript(..) 方法内部使用 ResourceDatabasePopulator 来运行 SQL 脚本。有关各种 executeSqlScript(..) 方法的详细信息,请参阅 Javadoc。spring-doc.cadn.net.cn

声明式执行SQL脚本与@Sql

除了上述用于编程运行SQL脚本的机制外,你还可以在Spring TestContext框架中声明式地配置SQL脚本。具体来说,你可以在测试类或测试方法上声明@Sql注解来配置单个SQL语句或应该针对给定数据库在集成测试方法之前或之后运行的SQL脚本的资源路径。@Sql的支持由SqlScriptsTestExecutionListener提供,默认情况下是启用的。spring-doc.cadn.net.cn

Method-level @Sql 声明默认会覆盖类级别的声明。然而,从 Spring Framework 5.2 开始,这种行为可以通过 @SqlMergeMode 按照测试类或测试方法进行配置。有关详细信息,请参阅 使用 @SqlMergeMode 合并和覆盖配置

路径资源语义

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

以下示例展示了如何在基于JUnit Jupiter的集成测试类中在类级别和方法级别使用@Sqlspring-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的声明位置。如果无法检测到默认值,则会抛出 IllegalStateExceptionspring-doc.cadn.net.cn

  • 类级别声明:如果注解的测试类是com.example.MyTest,则对应的默认脚本是classpath:com/example/MyTest.sqlspring-doc.cadn.net.cn

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

声明多个@Sql集合

如果你需要为给定的测试类或测试方法配置多组SQL脚本,但每组具有不同的语法配置、不同的错误处理规则或不同的执行阶段,你可以声明多个@Sql实例。从Java 8开始,你可以使用@Sql作为可重复注解。否则,你可以使用@SqlGroup注解作为显式的容器来声明多个@Sql。从Java 8开始,你可以使用@Sql作为可重复注解。否则,你可以使用@SqlGroup注解作为显式的容器来声明多个@Sql实例。spring-doc.cadn.net.cn

以下示例展示了如何在Java 8中使用@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
}
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin

在前面示例中呈现的情景下,test-schema.sql 脚本使用了不同的语法来表示单行注释。spring-doc.cadn.net.cn

以下示例与前面的示例相同,只是将 @Sql 声明组合在一起放在 @SqlGroup 中。从 Java 8 及更高版本开始,使用 @SqlGroup 是可选的,但您可能需要使用 @SqlGroup 以与其他 JVM 语言(如 Kotlin)兼容。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属性在@Sql中,如下例所示: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
@SqlGroup(
	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-doc.cadn.net.cn

脚本配置与@SqlConfig

您可以通过使用@SqlConfig注解来配置脚本解析和错误处理。 当作为集成测试类上的类级注解声明时,@SqlConfig 作为测试类层次结构中所有SQL脚本的全局配置。当 直接使用@Sql注解的config属性声明时,@SqlConfig 作为封装的@Sql注解内声明的SQL脚本的本地配置。@SqlConfig中的每个属性都有一个隐式默认值,这在相应属性的Javadoc中有文档说明。由于Java语言规范中定义的注解属性规则,不幸的是,无法将null值分配给注解属性。因此,为了支持继承的全局配置的覆盖,@SqlConfig属性具有显式的默认值""(对于字符串)、{}(对于数组)或DEFAULT(对于枚举)。当 使用@Sql注解的config属性直接声明时,@SqlConfig 作为本地配置。因此,为了支持继承的全局配置的覆盖,@SqlConfig属性具有""(对于字符串)、{}(对于数组)或DEFAULT(对于枚举)的显式默认值。这种做法使得@SqlConfig注解内的SQL脚本的本地配置。@SqlConfig注解内的SQL脚本的本地配置。每个属性在@SqlConfig中都有一个隐式默认值,这在相应属性的Javadoc中有文档说明。因此,@SqlConfig注解内的SQL脚本的本地配置。每个属性在@SqlConfig中都有一个隐式默认值。全局配置@SqlConfig 注解内的SQL脚本的本地配置。@SqlConfig注解内的SQL脚本的本地配置。@SqlConfig注解内的SQL脚本的本地配置。""注解内的SQL脚本的本地配置。{}注解内的SQL脚本的本地配置。DEFAULT注解内的SQL脚本的本地配置。22注解内的SQL脚本的本地配置。23注解内的SQL脚本的本地配置。24注解内的SQL脚本的本地配置。25注解内的SQL脚本的本地配置。26注解内的SQL脚本的本地配置。27注解内的SQL脚本的本地配置。28注解内的SQL脚本的本地配置。29注解内的SQL脚本的本地配置。30注解内的SQL脚本的本地配置。31注解内的SQL脚本的本地配置。32注解内的SQL脚本的本地配置。33注解内的SQL脚本的本地配置。34注解内的SQL脚本的本地配置。35注解内的SQL脚本的本地配置。36注解内的SQL脚本的本地配置。37注解内的SQL脚本的本地配置。38注解内的SQL脚本的本地配置。39注解内的SQL脚本的本地配置。40注解内的SQL脚本的本地配置。41注解内的SQL脚本的本地配置。42注解内的SQL脚本的本地配置。43注解内的SQL脚本的本地配置。44注解内的SQL脚本的本地配置。45注解内的SQL脚本的本地配置。46注解内的SQL脚本的本地配置。47注解内的SQL脚本的本地配置。48注解内的SQL脚本的本地配置。49注解内的SQL脚本的本地配置。50注解内的SQL脚本的本地配置。51注解内的SQL脚本的本地配置。52注解内的SQL脚本的本地配置。53注解内的SQL脚本的本地配置。54注解内的SQL脚本的本地配置。55注解内的SQL脚本的本地配置。56注解内的SQL脚本的本地配置。57注解内的SQL脚本的本地配置。58注解内的SQL脚本的本地配置。59注解内的SQL脚本的本地配置。60注解内的SQL脚本的本地配置。61注解内的SQL脚本的本地配置。62注解内的SQL脚本的本地配置。63注解内的SQL脚本的本地配置。64注解内的SQL脚本的本地配置。65注解内的SQL脚本的本地配置。66注解内的SQL脚本的本地配置。67注解内的SQL脚本的本地配置。68注解内的SQL脚本的本地配置。69注解内的SQL脚本的本地配置。70注解内的SQL脚本的本地配置。71注解内的SQL脚本的本地配置。72注解内的SQL脚本的本地配置。73注解内的SQL脚本的本地配置。74注解内的SQL脚本的本地配置。75注解内的SQL脚本的本地配置。76注解内的SQL脚本的本地配置。77注解内的SQL脚本的本地配置。78注解内的SQL脚本的本地配置。79注解内的SQL脚本的本地配置。80注解内的SQL脚本的本地配置。81注解内的SQL脚本的本地配置。82注解内的SQL脚本的本地配置。83注解内的SQL脚本的本地配置。84注解内的SQL脚本的本地配置。85注解内的SQL脚本的本地配置。86注解内的SQL脚本的本地配置。87注解内的SQL脚本的本地配置。88注解内的SQL脚本的本地配置。89注解内的SQL脚本的本地配置。90注解内的SQL脚本的本地配置。91注解内的SQL脚本的本地配置。92注解内的SQL脚本的本地配置。93注解内的SQL脚本的本地配置。94注解内的SQL脚本的本地配置。95注解内的SQL脚本的本地配置。96注解内的SQL脚本的本地配置。97注解内的SQL脚本的本地配置。98注解内的SQL脚本的本地配置。99注解内的SQL脚本的本地配置。100注解内的SQL脚本的本地配置。101注解内的SQL脚本的本地配置。102注解内的SQL脚本的本地配置。103注解内的SQL脚本的本地配置。104注解内的SQL脚本的本地配置。105注解内的SQL脚本的本地配置。106注解内的SQL脚本的本地配置。107注解内的SQL脚本的本地配置。108注解内的SQL脚本的本地配置。109注解内的SQL脚spring-doc.cadn.net.cn

The configuration options provided by @Sql and @SqlConfig are equivalent to those supported by ScriptUtils and ResourceDatabasePopulator but are a superset of those provided by the <jdbc:initialize-database/> XML namespace element. See the javadoc of individual attributes in @Sql and @SqlConfig for details.spring-doc.cadn.net.cn

事务管理用于@Sqlspring-doc.cadn.net.cn

默认情况下,SqlScriptsTestExecutionListener 会推断使用 @Sql 配置的脚本所需的事务语义。具体来说,SQL 脚本将在没有事务的情况下运行,在现有 Spring 管理的事务中运行(例如,由 TransactionalTestExecutionListener 管理的事务,对于带有 @Transactional 注解的测试),或在隔离事务中运行,这取决于在 @SqlConfig 中配置的 transactionMode 属性的值以及测试的 ApplicationContext 中是否存在 PlatformTransactionManager。作为最低要求,测试的 ApplicationContext 中必须存在一个 javax.sql.DataSourcespring-doc.cadn.net.cn

如果用于检测DataSourcePlatformTransactionManager并推断事务语义的算法不满足您的需求,您可以通过设置@SqlConfigdataSourcetransactionManager属性来指定明确的名称。此外,您可以通过设置@SqlConfigtransactionMode属性来控制事务传播行为(例如,脚本是否应在隔离事务中运行)。虽然详细讨论所有支持的事务管理选项超出了本参考手册的范围,但@SqlConfigSqlScriptsTestExecutionListenertransactionMode属性来控制事务传播行为(例如,脚本是否应在一个隔离的事务中运行)。尽管使用@Sql进行事务管理的所有支持选项的详尽讨论超出了本参考手册的范围,但@SqlConfigSqlScriptsTestExecutionListener的Javadoc提供了详细信息,以下示例展示了一个典型的测试场景,该场景使用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()方法运行后不需要清理数据库,因为对数据库所做的任何更改(无论是在测试方法中还是在/test-data.sql脚本中)都会被TransactionalTestExecutionListener自动回滚(有关详细信息,请参见事务管理)。spring-doc.cadn.net.cn

合并和覆盖配置与@SqlMergeMode

从Spring Framework 5.2开始,可以在方法级别合并@Sql声明与类级别声明。例如,这允许您为测试类提供一次数据库模式或一些通用测试数据的配置,然后为每个测试方法提供额外的、特定于用例的测试数据。要启用@Sql合并,请使用@SqlMergeMode(MERGE)注解标记您的测试类或测试方法。要为特定测试方法(或特定测试子类)禁用合并,可以通过@SqlMergeMode(OVERRIDE)切换回默认模式。有关@SqlMergeMode注解文档部分的示例和详细信息,请参阅@SqlMergeMode注解文档部分spring-doc.cadn.net.cn