Loading...
Spring Framework Reference Documentation 7.0.2의 Transaction Management의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
TestContext framework에서 transaction은 TransactionalTestExecutionListener에 의해
관리되며, 이는 test class에서 @TestExecutionListeners를 명시적으로 선언하지
않더라도 기본적으로 설정됩니다. 그러나 transaction에 대한 지원을 활성화하려면
@ContextConfiguration 시맨틱스로 로드되는 ApplicationContext에
PlatformTransactionManager 빈을 설정해야 합니다(자세한 내용은 후술).
또한 test에 대해 Spring의 @Transactional 어노테이션을 class 또는 method
레벨에 선언해야 합니다.
Test-managed transaction은 TransactionalTestExecutionListener를 사용하여
선언적으로 관리되거나(또는 이후에 설명되는) TestTransaction을 사용하여
프로그램적으로 관리되는 transaction입니다. 이러한 transaction을 test를 위해
로드된 ApplicationContext 내에서 Spring에 의해 직접 관리되는 transaction
(Spring-managed transaction)이나 test에 의해 호출되는 애플리케이션 code 내에서
프로그램적으로 관리되는 transaction(application-managed transaction)과 혼동해서는
안 됩니다.
Spring-managed transaction과 application-managed transaction은 일반적으로
test-managed transaction에 참여합니다. 그러나 Spring-managed transaction이나
application-managed transaction이 REQUIRED 또는 SUPPORTS 이외의 아무 propagation
type으로 설정되어 있다면 주의해야 합니다(자세한 내용은
transaction propagation에 대한 논의를 참고).
Preemptive timeouts and test-managed transactions Spring의 test-managed transaction과 함께 testing framework에서 어떤 형태로든 preemptive timeout을 사용할 때는 주의를 기울여야 합니다. 구체적으로, Spring의 testing support는 현재 test method가 호출되기 전에 (즉,
java.lang.ThreadLocal변수를 통해) transaction 상태를 현재 thread에 바인딩합니다. 만약 testing framework가 preemptive timeout을 지원하기 위해 현재 test method를 새 thread에서 호출한다면, 현재 test method 내에서 수행되는 모든 동작은 test-managed transaction 내에서 호출되지 않습니다. 따라서 그러한 동작의 결과는 test-managed transaction과 함께 rollback되지 않습니다. 반대로, 그러한 동작은 비록 test-managed transaction이 Spring에 의해 적절히 rollback되더라도, 예를 들어 관계형 database와 같은 persistent store에 commit됩니다. 이러한 상황이 발생할 수 있는 경우에는 다음과 같은 것들이 포함되지만 이에 국한되지는 않습니다.
- JUnit 4의
@Test(timeout = …)support 및TimeOutrule- JUnit Jupiter의
org.junit.jupiter.api.Assertionsclass에 있는assertTimeoutPreemptively(…)method- TestNG의
@Test(timeOut = …)support
Test method에 @Transactional을 어노테이션으로 지정하면 test가 transaction 내에서
실행되며, 기본적으로 test가 완료된 후 자동으로 rollback됩니다. Test class에
@Transactional이 어노테이션으로 지정된 경우 해당 class hierarchy 내의 각 test
method는 transaction 내에서 실행됩니다.
Class 또는 method 레벨에서
@Transactional이 어노테이션으로 지정되지 않은 test method는 transaction 내에서
실행되지 않습니다. @Transactional은 JUnit Jupiter의 @BeforeAll,
@BeforeEach 등으로 어노테이션된 method와 같은 test lifecycle method에서는
지원되지 않는다는 점에 유의해야 합니다. 또한 @Transactional로 어노테이션되었지만
propagation attribute가 NOT_SUPPORTED 또는 NEVER로 설정된 test는
transaction 내에서 실행되지 않습니다.
| Attribute | Supported for test-managed transactions |
|---|---|
value and transactionManager | yes |
propagation | only Propagation.NOT_SUPPORTED and Propagation.NEVER are supported |
isolation | no |
timeout | no |
readOnly | no |
rollbackFor and rollbackForClassName | no: use TestTransaction.flagForRollback() instead |
noRollbackFor and noRollbackForClassName | no: use TestTransaction.flagForCommit() instead |
Table 1. @Transactional attribute support
Method-level lifecycle method(예: JUnit Jupiter의
@BeforeEach또는@AfterEach로 어노테이션된 method)는 test-managed transaction 내에서 실행됩니다. 반면에, suite-level 및 class-level lifecycle method(예: JUnit Jupiter의@BeforeAll또는@AfterAll로 어노테이션된 method 및 TestNG의@BeforeSuite,@AfterSuite,@BeforeClass,@AfterClass로 어노테이션된 method)는 test-managed transaction 내에서 실행되지 않습니다. Suite-level 또는 class-level lifecycle method 내에서 transaction 안에서 code를 실행해야 한다면, 해당PlatformTransactionManager를 test class에 주입한 다음 프로그램적 transaction management를 위해TransactionTemplate과 함께 사용할 수 있습니다.
AbstractTransactionalJUnit4SpringContextTests
와
AbstractTransactionalTestNGSpringContextTests
는 class 레벨에서 transactional support를 위해 미리 설정되어 있습니다.
다음 예제는 Hibernate 기반의 UserRepository에 대한 integration test를 작성하기 위한
일반적인 시나리오를 보여줍니다.
1@SpringJUnitConfig(TestConfig.class) 2@Transactional 3class HibernateUserRepositoryTests { 4 5 @Autowired 6 HibernateUserRepository repository; 7 8 @Autowired 9 SessionFactory sessionFactory; 10 11 JdbcTemplate jdbcTemplate; 12 13 @Autowired 14 void setDataSource(DataSource dataSource) { 15 this.jdbcTemplate = new JdbcTemplate(dataSource); 16 } 17 18 @Test 19 void createUser() { 20 // track initial state in test database: 21 final int count = countRowsInTable("user"); 22 23 User user = new User(...); 24 repository.save(user); 25 26 // Manual flush is required to avoid false positive in test 27 sessionFactory.getCurrentSession().flush(); 28 assertNumUsers(count + 1); 29 } 30 31 private int countRowsInTable(String tableName) { 32 return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); 33 } 34 35 private void assertNumUsers(int expected) { 36 assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); 37 } 38}
1@SpringJUnitConfig(TestConfig::class) 2@Transactional 3class HibernateUserRepositoryTests { 4 5 @Autowired 6 lateinit var repository: HibernateUserRepository 7 8 @Autowired 9 lateinit var sessionFactory: SessionFactory 10 11 lateinit var jdbcTemplate: JdbcTemplate 12 13 @Autowired 14 fun setDataSource(dataSource: DataSource) { 15 this.jdbcTemplate = JdbcTemplate(dataSource) 16 } 17 18 @Test 19 fun createUser() { 20 // track initial state in test database: 21 val count = countRowsInTable("user") 22 23 val user = User() 24 repository.save(user) 25 26 // Manual flush is required to avoid false positive in test 27 sessionFactory.getCurrentSession().flush() 28 assertNumUsers(count + 1) 29 } 30 31 private fun countRowsInTable(tableName: String): Int { 32 return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) 33 } 34 35 private fun assertNumUsers(expected: Int) { 36 assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) 37 } 38}
Transaction Rollback and Commit Behavior에
설명된 대로, createUser() method가 실행된 후 database를 정리할 필요는 없습니다.
Database에 가해진 모든 변경 사항은 TransactionalTestExecutionListener에 의해
자동으로 rollback되기 때문입니다.
기본적으로 test transaction은 test가 완료된 후 자동으로 rollback됩니다. 그러나
transactional commit 및 rollback 동작은 @Commit 및 @Rollback 어노테이션을 통해
선언적으로 설정할 수 있습니다.
자세한 내용은 annotation support 섹션의 해당 항목을 참고하십시오.
TestTransaction의 static method를 사용하여 프로그램적으로 test-managed
transaction과 상호 작용할 수 있습니다. 예를 들어, test-managed transaction을
rollback 또는 commit하도록 설정하거나 현재 test-managed transaction을 시작 또는
종료하기 위해 TestTransaction을 test method, before method, after method 내에서
사용할 수 있습니다.
TransactionalTestExecutionListener가 활성화되어 있으면
TestTransaction에 대한 지원이 자동으로 제공됩니다.
다음 예제는 TestTransaction의 몇 가지 기능을 보여줍니다. 자세한 내용은
TestTransaction
에 대한 javadoc을 참고하십시오.
1@ContextConfiguration(classes = TestConfig.class) 2public class ProgrammaticTransactionManagementTests extends 3 AbstractTransactionalJUnit4SpringContextTests { 4 5 @Test 6 public void transactionalTest() { 7 // assert initial state in test database: 8 assertNumUsers(2); 9 10 deleteFromTables("user"); 11 12 // changes to the database will be committed! 13 TestTransaction.flagForCommit(); 14 TestTransaction.end(); 15 assertFalse(TestTransaction.isActive()); 16 assertNumUsers(0); 17 18 TestTransaction.start(); 19 // perform other actions against the database that will 20 // be automatically rolled back after the test completes... 21 } 22 23 protected void assertNumUsers(int expected) { 24 assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); 25 } 26}
1@ContextConfiguration(classes = [TestConfig::class]) 2class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() { 3 4 @Test 5 fun transactionalTest() { 6 // assert initial state in test database: 7 assertNumUsers(2) 8 9 deleteFromTables("user") 10 11 // changes to the database will be committed! 12 TestTransaction.flagForCommit() 13 TestTransaction.end() 14 assertFalse(TestTransaction.isActive()) 15 assertNumUsers(0) 16 17 TestTransaction.start() 18 // perform other actions against the database that will 19 // be automatically rolled back after the test completes... 20 } 21 22 protected fun assertNumUsers(expected: Int) { 23 assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")) 24 } 25}
가끔은 transactional test method 실행 전후에 transactional context 밖에서 특정 code를 실행해야 할 수도 있습니다. 예를 들어, test를 실행하기 전에 초기 database 상태를 검증하거나(또는 test가 transaction을 commit하도록 설정된 경우) test 실행 후 예상되는 transactional commit 동작을 검증해야 할 수 있습니다.
TransactionalTestExecutionListener는 바로 이러한 시나리오를 위해
@BeforeTransaction 및 @AfterTransaction 어노테이션을 지원합니다. Test class의
어떤 void method나 test interface의 어떤 void default method에 이 어노테이션
중 하나를 지정할 수 있으며, TransactionalTestExecutionListener는
before-transaction method 또는 after-transaction method가 적절한 시점에 실행되도록
보장합니다.
일반적으로
@BeforeTransaction및@AfterTransactionmethod는 어떤 argument도 받아서는 안 됩니다. 그러나 JUnit Jupiter와 함께SpringExtension을 사용하는 test의 경우,@BeforeTransaction및@AfterTransactionmethod는 선택적으로SpringExtension과 같은 등록된 JUnitParameterResolverextension에 의해 resolve될 argument를 받을 수 있습니다. 이는TestInfo와 같은 JUnit-specific argument나 test의ApplicationContext에 있는 빈이@BeforeTransaction및@AfterTransactionmethod에 제공될 수 있음을 의미하며, 다음 예제에 그 사용법이 나와 있습니다.1@BeforeTransaction 2void verifyInitialDatabaseState(@Autowired DataSource dataSource) { 3 // Use the DataSource to verify the initial state before a transaction is started 4}1@BeforeTransaction 2fun verifyInitialDatabaseState(@Autowired dataSource: DataSource) { 3 // Use the DataSource to verify the initial state before a transaction is started 4}
모든 before method(예: JUnit Jupiter의
@BeforeEach로 어노테이션된 method)와 모든 after method(예: JUnit Jupiter의@AfterEach로 어노테이션된 method)는 transactional test method에 대해 test-managed transaction 내에서 실행됩니다. 마찬가지로,@BeforeTransaction또는@AfterTransaction으로 어노테이션된 method는 transactional test method에 대해서만 실행됩니다.
TransactionalTestExecutionListener는 test를 위한 Spring ApplicationContext에
PlatformTransactionManager 빈이 정의되어 있다고 가정합니다. Test의
ApplicationContext 내에 PlatformTransactionManager instance가 여러 개 있는 경우
@Transactional("myTxMgr") 또는 @Transactional(transactionManager = "myTxMgr")를 사용하여 qualifier를 선언하거나, @Configuration class에서
TransactionManagementConfigurer를 구현할 수 있습니다.
Test의 ApplicationContext에서 transaction manager를 조회하는 데 사용되는 알고리즘에
대한 자세한 내용은
javadoc for TestContextTransactionUtils.retrieveTransactionManager()
를 참고하십시오.
다음 JUnit Jupiter 기반 예제는 모든 transaction 관련 어노테이션을 강조하는 가상의 integration testing 시나리오를 보여줍니다. 이 예제는 best practice를 보여주기 위한 것이 아니라 이러한 어노테이션이 어떻게 사용될 수 있는지를 보여주기 위한 것입니다.
자세한 정보와 설정 예제는
annotation support
섹션을 참고하십시오. @Sql에 대한 Transaction management
에는 기본 transaction rollback 시맨틱과 함께 선언적인 SQL script 실행을 위해
@Sql을 사용하는 추가 예제가 포함되어 있습니다. 다음 예제는 관련 어노테이션을
보여줍니다.
1@SpringJUnitConfig 2@Transactional(transactionManager = "txMgr") 3@Commit 4class FictitiousTransactionalTest { 5 6 @BeforeTransaction 7 void verifyInitialDatabaseState() { 8 // logic to verify the initial state before a transaction is started 9 } 10 11 @BeforeEach 12 void setUpTestDataWithinTransaction() { 13 // set up test data within the transaction 14 } 15 16 @Test 17 // overrides the class-level @Commit setting 18 @Rollback 19 void modifyDatabaseWithinTransaction() { 20 // logic which uses the test data and modifies database state 21 } 22 23 @AfterEach 24 void tearDownWithinTransaction() { 25 // run "tear down" logic within the transaction 26 } 27 28 @AfterTransaction 29 void verifyFinalDatabaseState() { 30 // logic to verify the final state after transaction has rolled back 31 } 32 33}
1@SpringJUnitConfig 2@Transactional(transactionManager = "txMgr") 3@Commit 4class FictitiousTransactionalTest { 5 6 @BeforeTransaction 7 fun verifyInitialDatabaseState() { 8 // logic to verify the initial state before a transaction is started 9 } 10 11 @BeforeEach 12 fun setUpTestDataWithinTransaction() { 13 // set up test data within the transaction 14 } 15 16 @Test 17 // overrides the class-level @Commit setting 18 @Rollback 19 fun modifyDatabaseWithinTransaction() { 20 // logic which uses the test data and modifies database state 21 } 22 23 @AfterEach 24 fun tearDownWithinTransaction() { 25 // run "tear down" logic within the transaction 26 } 27 28 @AfterTransaction 29 fun verifyFinalDatabaseState() { 30 // logic to verify the final state after transaction has rolled back 31 } 32 33}
Avoid false positives when testing ORM code Hibernate session이나 JPA persistence context의 상태를 조작하는 애플리케이션 code를 test할 때는 해당 code를 실행하는 test method 내에서 underlying unit of work를 반드시 flush해야 합니다. Underlying unit of work를 flush하지 않으면 false positive가 발생할 수 있습니다. 즉, test는 통과하지만 동일한 code가 실제 production 환경에서는 exception을 발생시킬 수 있습니다. 이는 in-memory unit of work를 유지하는 모든 ORM framework에 적용된다는 점에 유의해야 합니다. 다음 Hibernate 기반 예제 test case에서 한 method는 false positive를 보여주고, 다른 method는 session을 flush한 결과를 올바르게 드러냅니다.
1// ... 2@Autowired 3SessionFactory sessionFactory; 4 5@Transactional 6@Test // no expected exception! 7public void falsePositive() { 8 updateEntityInHibernateSession(); 9 // False positive: an exception will be thrown once the Hibernate 10 // Session is finally flushed (i.e., in production code) 11} 12 13@Transactional 14@Test(expected = ...) 15public void updateWithSessionFlush() { 16 updateEntityInHibernateSession(); 17 // Manual flush is required to avoid false positive in test 18 sessionFactory.getCurrentSession().flush(); 19} 20// ...1// ... 2@Autowired 3lateinit var sessionFactory: SessionFactory 4 5@Transactional 6@Test // no expected exception! 7fun falsePositive() { 8 updateEntityInHibernateSession() 9 // False positive: an exception will be thrown once the Hibernate 10 // Session is finally flushed (i.e., in production code) 11} 12 13@Transactional 14@Test(expected = ...) 15fun updateWithSessionFlush() { 16 updateEntityInHibernateSession() 17 // Manual flush is required to avoid false positive in test 18 sessionFactory.getCurrentSession().flush() 19} 20// ...다음 예제는 JPA에 대해 일치하는 method를 보여줍니다.
1// ... 2@PersistenceContext 3EntityManager entityManager; 4 5@Transactional 6@Test // no expected exception! 7public void falsePositive() { 8 updateEntityInJpaPersistenceContext(); 9 // False positive: an exception will be thrown once the JPA 10 // EntityManager is finally flushed (i.e., in production code) 11} 12 13@Transactional 14@Test(expected = ...) 15public void updateWithEntityManagerFlush() { 16 updateEntityInJpaPersistenceContext(); 17 // Manual flush is required to avoid false positive in test 18 entityManager.flush(); 19} 20// ...1// ... 2@PersistenceContext 3lateinit var entityManager: EntityManager 4 5@Transactional 6@Test // no expected exception! 7fun falsePositive() { 8 updateEntityInJpaPersistenceContext() 9 // False positive: an exception will be thrown once the JPA 10 // EntityManager is finally flushed (i.e., in production code) 11} 12 13@Transactional 14@Test(expected = ...) 15fun updateWithEntityManagerFlush() { 16 updateEntityInJpaPersistenceContext() 17 // Manual flush is required to avoid false positive in test 18 entityManager.flush() 19} 20// ...Testing ORM entity lifecycle callbacks ORM code를 test할 때 false positives를 피하는 것에 대한 note와 비슷하게, 애플리케이션에서 entity lifecycle callback(entity listener라고도 함)을 사용하는 경우 해당 code를 실행하는 test method 내에서 underlying unit of work를 반드시 flush해야 합니다. Underlying unit of work를 _flush_하거나 _clear_하지 않으면 특정 lifecycle callback이 호출되지 않을 수 있습니다. 예를 들어, JPA를 사용할 때
@PostPersist,@PreUpdate,@PostUpdatecallback은 entity가 저장되거나 업데이트된 후entityManager.flush()가 호출되지 않으면 호출되지 않습니다. 마찬가지로, entity가 이미 현재 unit of work(현재 persistence context와 연결됨)에 attach되어 있는 경우, entity를 다시 로드하려는 시도는entityManager.clear()가 entity를 다시 로드하려는 시도 전에 호출되지 않으면@PostLoadcallback을 발생시키지 않습니다.다음 예제는 entity가 persist될 때
@PostPersistcallback이 호출되도록 보장하기 위해EntityManager를 flush하는 방법을 보여줍니다. 예제에서 사용되는Personentity에@PostPersistcallback method를 가진 entity listener가 등록되어 있습니다.1// ... 2@Autowired 3JpaPersonRepository repo; 4 5@PersistenceContext 6EntityManager entityManager; 7 8@Transactional 9@Test 10void savePerson() { 11 // EntityManager#persist(...) results in @PrePersist but not @PostPersist 12 repo.save(new Person("Jane")); 13 // Manual flush is required for @PostPersist callback to be invoked 14 entityManager.flush(); 15 // Test code that relies on the @PostPersist callback 16 // having been invoked... 17} 18// ...1// ... 2@Autowired 3lateinit var repo: JpaPersonRepository 4 5@PersistenceContext 6lateinit var entityManager: EntityManager 7 8@Transactional 9@Test 10fun savePerson() { 11 // EntityManager#persist(...) results in @PrePersist but not @PostPersist 12 repo.save(Person("Jane")) 13 // Manual flush is required for @PostPersist callback to be invoked 14 entityManager.flush() 15 // Test code that relies on the @PostPersist callback 16 // having been invoked... 17} 18// ...Spring Framework test suite에서 모든 JPA lifecycle callback을 사용하는 동작 예제는 JpaEntityListenerTests를 참고하십시오.
Testing Request- and Session-scoped Beans
Executing SQL Scripts