Transaction Management
在 TestContext 框架中,事务由 TransactionalTestExecutionListener
管理,该监听器默认情况下已配置,即使你没有在你测试类上显式申报 @TestExecutionListeners
。但是,要启用对事务的支持,你必须在使用 @ContextConfiguration
语义加载的 ApplicationContext
中配置一个 PlatformTransactionManager
bean(稍后将提供更多详细信息)。此外,你必须为你的测试在类级或方法级上申报 Spring 的 @Transactional
注解。
In the TestContext framework, transactions are managed by the
TransactionalTestExecutionListener
, which is configured by default, even if you do not
explicitly declare @TestExecutionListeners
on your test class. To enable support for
transactions, however, you must configure a PlatformTransactionManager
bean in the
ApplicationContext
that is loaded with @ContextConfiguration
semantics (further
details are provided later). In addition, you must declare Spring’s @Transactional
annotation either at the class or the method level for your tests.
Test-managed Transactions
测试管理的事务是通过使用 TransactionalTestExecutionListener
声明式管理的事务,或通过使用 TestTransaction
程序化管理的事务(稍后描述)。您不应将此类事务与 Spring 管理的事务(直接由 Spring 在为测试加载的 ApplicationContext
中管理的事务)或应用程序管理的事务(在测试调用的应用程序代码中通过编程管理的事务)混淆。Spring 管理的事务和应用程序管理的事务通常参与测试管理的事务。但是,如果 Spring 管理的事务或应用程序管理的事务配置的传播类型与 REQUIRED
或 SUPPORTS
以外的任何其他传播类型,则应小心(请参阅有关 transaction propagation 的讨论以了解详情)。
Test-managed transactions are transactions that are managed declaratively by using the
TransactionalTestExecutionListener
or programmatically by using TestTransaction
(described later). You should not confuse such transactions with Spring-managed
transactions (those managed directly by Spring within the ApplicationContext
loaded for
tests) or application-managed transactions (those managed programmatically within
application code that is invoked by tests). Spring-managed and application-managed
transactions typically participate in test-managed transactions. However, you should use
caution if Spring-managed or application-managed transactions are configured with any
propagation type other than REQUIRED
or SUPPORTS
(see the discussion on
transaction propagation for details).
在将测试框架的任何形式的抢占超时与 Spring 的测试管理事务结合使用时,必须小心。
Caution must be taken when using any form of preemptive timeouts from a testing framework in conjunction with Spring’s test-managed transactions.
具体来说,Spring 的测试支持在调用当前测试方法 之前 将事务状态绑定到当前线程(通过 java.lang.ThreadLocal
变量)。如果测试框架在一个新的线程中调用当前测试方法以支持抢占超时,则在当前测试方法中执行的任何操作 不会 在测试管理的事务中被调用。因此,任何此类操作的结果都不会随测试管理的事务一起回滚。相反,这些操作将提交到持久存储 中——例如关系数据库 ——即使 Spring 正确回滚了测试管理的事务也是如此。
Specifically, Spring’s testing support binds transaction state to the current thread (via
a java.lang.ThreadLocal
variable) before the current test method is invoked. If a
testing framework invokes the current test method in a new thread in order to support a
preemptive timeout, any actions performed within the current test method will not be
invoked within the test-managed transaction. Consequently, the result of any such actions
will not be rolled back with the test-managed transaction. On the contrary, such actions
will be committed to the persistent store — for example, a relational database — even
though the test-managed transaction is properly rolled back by Spring.
可能发生这种情况的场景包括但不限于以下内容。
Situations in which this can occur include but are not limited to the following.
-
JUnit 4’s
@Test(timeout = …)
support andTimeOut
rule -
JUnit Jupiter’s
assertTimeoutPreemptively(…)
methods in theorg.junit.jupiter.api.Assertions
class -
TestNG’s
@Test(timeOut = …)
support
Enabling and Disabling Transactions
使用 @Transactional
对测试方法进行注释会导致测试在事务中运行,该事务在默认情况下会在测试完成后自动回滚。如果使用 @Transactional
对一个测试类进行注释,那么该类层次结构中的每个测试方法都在事务中运行。没有使用 @Transactional
(在类级或方法级)对进行注释的测试方法不会在事务中运行。请注意, @Transactional
不支持测试生命周期方法——例如,使用 JUnit Jupiter 的 @BeforeAll
、@BeforeEach
等进行注释的方法。此外,用 @Transactional
进行注释但将 propagation
属性设置为 NOT_SUPPORTED
或 NEVER
的测试不会在事务中运行。
Annotating a test method with @Transactional
causes the test to be run within a
transaction that is, by default, automatically rolled back after completion of the test.
If a test class is annotated with @Transactional
, each test method within that class
hierarchy runs within a transaction. Test methods that are not annotated with
@Transactional
(at the class or method level) are not run within a transaction. Note
that @Transactional
is not supported on test lifecycle methods — for example, methods
annotated with JUnit Jupiter’s @BeforeAll
, @BeforeEach
, etc. Furthermore, tests that
are annotated with @Transactional
but have the propagation
attribute set to
NOT_SUPPORTED
or NEVER
are not run within a transaction.
Attribute | Supported for test-managed transactions |
---|---|
|
yes |
|
only |
|
no |
|
no |
|
no |
|
no: use |
|
no: use |
方法级生命周期方法——例如,使用 JUnit Jupiter 的 Method-level lifecycle methods — for example, methods annotated with JUnit Jupiter’s
如果你需要在套件级或类级生命周期方法中运行事务中的代码,你可能希望将相应的 If you need to run code in a suite-level or class-level lifecycle method within a
transaction, you may wish to inject a corresponding |
请注意,AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
已预先配置为在类级别提供事务支持。
Note that AbstractTransactionalJUnit4SpringContextTests
and
AbstractTransactionalTestNGSpringContextTests
are preconfigured for transactional support at the class level.
以下示例演示了为基于 Hibernate 的 UserRepository
编写集成测试的常见场景:
The following example demonstrates a common scenario for writing an integration test for
a Hibernate-based UserRepository
:
-
Java
-
Kotlin
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
lateinit var repository: HibernateUserRepository
@Autowired
lateinit var sessionFactory: SessionFactory
lateinit var jdbcTemplate: JdbcTemplate
@Autowired
fun setDataSource(dataSource: DataSource) {
this.jdbcTemplate = JdbcTemplate(dataSource)
}
@Test
fun createUser() {
// track initial state in test database:
val count = countRowsInTable("user")
val user = User()
repository.save(user)
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush()
assertNumUsers(count + 1)
}
private fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
private fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
如Transaction Rollback and Commit Behavior 中所述,无需在运行 createUser()
方法后清理数据库,因为对数据库所做的任何更改都将自动由 TransactionalTestExecutionListener
回滚。
As explained in Transaction Rollback and Commit Behavior, there is no need to
clean up the database after the createUser()
method runs, since any changes made to the
database are automatically rolled back by the TransactionalTestExecutionListener
.
Transaction Rollback and Commit Behavior
默认情况下,测试事务将在测试完成后自动回滚;但是,可以通过 @Commit
和 @Rollback
注释以声明方式配置事务提交和回滚行为。有关更多详细信息,请参阅 annotation support 部分中的相应条目。
By default, test transactions will be automatically rolled back after completion of the
test; however, transactional commit and rollback behavior can be configured declaratively
via the @Commit
and @Rollback
annotations. See the corresponding entries in the
annotation support section for further details.
Programmatic Transaction Management
你可以使用 TestTransaction
中的静态方法以编程方式与测试管理的事务进行交互。例如,你可以在测试方法、方法之前和方法之后使用 TestTransaction
来启动或结束当前测试管理的事务,或为回滚或提交配置当前测试管理的事务。只要启用了 TransactionalTestExecutionListener
,就会自动提供对 TestTransaction
的支持。
You can interact with test-managed transactions programmatically by using the static
methods in TestTransaction
. For example, you can use TestTransaction
within test
methods, before methods, and after methods to start or end the current test-managed
transaction or to configure the current test-managed transaction for rollback or commit.
Support for TestTransaction
is automatically available whenever the
TransactionalTestExecutionListener
is enabled.
以下示例演示了 TestTransaction
的一些功能。有关更多详细信息,请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/transaction/TestTransaction.html[TestTransaction
] 中的 javadoc。
The following example demonstrates some of the features of TestTransaction
. See the
javadoc for TestTransaction
for further details.
-
Java
-
Kotlin
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// assert initial state in test database:
assertNumUsers(2);
deleteFromTables("user");
// changes to the database will be committed!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun transactionalTest() {
// assert initial state in test database:
assertNumUsers(2)
deleteFromTables("user")
// changes to the database will be committed!
TestTransaction.flagForCommit()
TestTransaction.end()
assertFalse(TestTransaction.isActive())
assertNumUsers(0)
TestTransaction.start()
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
Running Code Outside of a Transaction
有时,您可能需要在事务性测试方法之前或之后运行某些代码,但位于事务性上下文之外——例如,为了在运行测试之前验证初始数据库状态或在测试运行之后验证预期的事务提交行为(如果将测试配置为提交事务)。TransactionalTestExecutionListener
支持 @BeforeTransaction
和 @AfterTransaction
注释,用于支持上述场景。您可以使用这些注释之一在测试类中的任何 void
方法或者在测试接口中的任何 void
默认方法上添加注释,而 TransactionalTestExecutionListener
确保您的事务前方法或事务后方法在适当的时间运行。
Occasionally, you may need to run certain code before or after a transactional test
method but outside the transactional context — for example, to verify the initial
database state prior to running your test or to verify expected transactional commit
behavior after your test runs (if the test was configured to commit the transaction).
TransactionalTestExecutionListener
supports the @BeforeTransaction
and
@AfterTransaction
annotations for exactly such scenarios. You can annotate any void
method in a test class or any void
default method in a test interface with one of these
annotations, and the TransactionalTestExecutionListener
ensures that your
before-transaction method or after-transaction method runs at the appropriate time.
一般来说, Generally speaking, 但是,从 Spring Framework 6.1 开始,对于使用 However, as of Spring Framework 6.1, for tests using the
|
- Java
-
@BeforeTransaction void verifyInitialDatabaseState(@Autowired DataSource dataSource) { // Use the DataSource to verify the initial state before a transaction is started }
- Kotlin
-
@BeforeTransaction fun verifyInitialDatabaseState(@Autowired dataSource: DataSource) { // Use the DataSource to verify the initial state before a transaction is started }
任何前置方法(例如使用 JUnit Jupiter 的 Any before methods (such as methods annotated with JUnit Jupiter’s 类似地,使用 Similarly, methods annotated with |
Configuring a Transaction Manager
TransactionalTestExecutionListener
预期 ApplicationContext
用于测试的 Spring 中定义了一个 PlatformTransactionManager
bean。如果测试的 ApplicationContext
中有多个 PlatformTransactionManager
实例,则可以使用 @Transactional("myTxMgr")
或 @Transactional(transactionManager =
"myTxMgr")
声明限定符,或者可以通过一个 @Configuration
类来实现 TransactionManagementConfigurer
。查阅 TestContextTransactionUtils.retrieveTransactionManager()
的 javadoc 了解在测试的 ApplicationContext
中查找事务管理器的算法的详细信息。
TransactionalTestExecutionListener
expects a PlatformTransactionManager
bean to be
defined in the Spring ApplicationContext
for the test. If there are multiple instances
of PlatformTransactionManager
within the test’s ApplicationContext
, you can declare a
qualifier by using @Transactional("myTxMgr")
or @Transactional(transactionManager =
"myTxMgr")
, or TransactionManagementConfigurer
can be implemented by an
@Configuration
class. Consult the
javadoc
for TestContextTransactionUtils.retrieveTransactionManager()
for details on the
algorithm used to look up a transaction manager in the test’s ApplicationContext
.
Demonstration of All Transaction-related Annotations
以下基于 JUnit Jupiter 的示例显示了一个虚构的集成测试场景,该场景突出了所有与事务相关的注释。该示例的目的是演示这些注释如何使用,而不是演示最佳实践。有关详细信息和配置示例,请参阅annotation support 部分。Transaction management for @Sql
包含一个附加示例,它使用 @Sql
来执行声明性 SQL 脚本并带有默认事务回滚语义。以下示例显示了相关的注释:
The following JUnit Jupiter based example displays a fictitious integration testing
scenario that highlights all transaction-related annotations. The example is not intended
to demonstrate best practices but rather to demonstrate how these annotations can be
used. See the annotation support section for further
information and configuration examples. Transaction management for @Sql
contains an additional example that uses @Sql
for
declarative SQL script execution with default transaction rollback semantics. The
following example shows the relevant annotations:
-
Java
-
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
fun modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
fun tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
Avoid false positives when testing ORM code
当您测试操纵 Hibernate 会话或 JPA 持久性上下文状态的应用程序代码时,请务必在运行该代码的测试方法中刷新底层工作单元。未能刷新底层工作单元可能会产生错误肯定:您的测试通过,但相同的代码在实际生产环境中会出现异常。请注意,这适用于维护内存工作单元的任何 ORM 框架。在以下基于 Hibernate 的示例测试用例中,一种方法展示了错误肯定,而另一种方法正确地展示刷新会话的结果: When you test application code that manipulates the state of a Hibernate session or JPA persistence context, make sure to flush the underlying unit of work within test methods that run that code. Failing to flush the underlying unit of work can produce false positives: Your test passes, but the same code throws an exception in a live, production environment. Note that this applies to any ORM framework that maintains an in-memory unit of work. In the following Hibernate-based example test case, one method demonstrates a false positive, and the other method correctly exposes the results of flushing the session: |
- Java
-
// ... @Autowired SessionFactory sessionFactory; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInHibernateSession(); // False positive: an exception will be thrown once the Hibernate // Session is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithSessionFlush() { updateEntityInHibernateSession(); // Manual flush is required to avoid false positive in test sessionFactory.getCurrentSession().flush(); } // ...
- Kotlin
-
// ... @Autowired lateinit var sessionFactory: SessionFactory @Transactional @Test // no expected exception! fun falsePositive() { updateEntityInHibernateSession() // False positive: an exception will be thrown once the Hibernate // Session is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) fun updateWithSessionFlush() { updateEntityInHibernateSession() // Manual flush is required to avoid false positive in test sessionFactory.getCurrentSession().flush() } // ...
以下示例显示了 JPA 的匹配方法:
The following example shows matching methods for JPA:
- Java
-
// ... @PersistenceContext EntityManager entityManager; @Transactional @Test // no expected exception! public void falsePositive() { updateEntityInJpaPersistenceContext(); // False positive: an exception will be thrown once the JPA // EntityManager is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) public void updateWithEntityManagerFlush() { updateEntityInJpaPersistenceContext(); // Manual flush is required to avoid false positive in test entityManager.flush(); } // ...
- Kotlin
-
// ... @PersistenceContext lateinit var entityManager:EntityManager @Transactional @Test // no expected exception! fun falsePositive() { updateEntityInJpaPersistenceContext() // False positive: an exception will be thrown once the JPA // EntityManager is finally flushed (i.e., in production code) } @Transactional @Test(expected = ...) void updateWithEntityManagerFlush() { updateEntityInJpaPersistenceContext() // Manual flush is required to avoid false positive in test entityManager.flush() } // ...
Testing ORM entity lifecycle callbacks
类似于有关在测试 ORM 代码时避免 false positives 的注释,如果你的应用程序利用实体生命周期回调(也称为实体侦听器),请确保在运行该代码的测试方法内刷新底层的工作单元。未能_flush_ 或_clear_ 底层的工作单元可能会导致不调用某些生命周期回调。 Similar to the note about avoiding false positives when testing ORM code, if your application makes use of entity lifecycle callbacks (also known as entity listeners), make sure to flush the underlying unit of work within test methods that run that code. Failing to flush or clear the underlying unit of work can result in certain lifecycle callbacks not being invoked. 例如,当使用 JPA 时, For example, when using JPA, 以下示例展示了如何刷新 The following example shows how to flush the |
- Java
-
// ... @Autowired JpaPersonRepository repo; @PersistenceContext EntityManager entityManager; @Transactional @Test void savePerson() { // EntityManager#persist(...) results in @PrePersist but not @PostPersist repo.save(new Person("Jane")); // Manual flush is required for @PostPersist callback to be invoked entityManager.flush(); // Test code that relies on the @PostPersist callback // having been invoked... } // ...
- Kotlin
-
// ... @Autowired lateinit var repo: JpaPersonRepository @PersistenceContext lateinit var entityManager: EntityManager @Transactional @Test fun savePerson() { // EntityManager#persist(...) results in @PrePersist but not @PostPersist repo.save(Person("Jane")) // Manual flush is required for @PostPersist callback to be invoked entityManager.flush() // Test code that relies on the @PostPersist callback // having been invoked... } // ...
请参阅 Spring Framework 测试套件中的 JpaEntityListenerTests,以获取使用所有 JPA 生命周期回调的工作示例。
See JpaEntityListenerTests in the Spring Framework test suite for working examples using all JPA lifecycle callbacks.