Spring transaction 선언 시 propagation mode 를 설정할 수 있다. 예를 들면 다음과 같다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
propagation 을 설정하지 않으면 default 값은 REQUIRED 이다.
이 포스팅에서는 설정 가능한 값 중 가장 많이 사용 하는 REQUIRES_NEW 와 REQUIRED 의 차이점을 설명 한다.
- REQUIRED : 실행 중인 Transaction context 가 있으면 해당 Transaction 내에서 실행하고, 없으면 새로운 Transaction 생성
- REQUIRES_NEW : 기존에 실행 중인 Transaction 유무와 상관 없이 무조건 새로운 Transaction 생성
대부분의 책에서 위의 수준으로 언급하고 있으나, 실제로 제대로 동작하기 위해서 고려해야 할 내용은 언급하고 있지 않다. 다음은 여기에 대해 시행 착오를 거친 내용이다.
예를 들어 insert1(), insert2() 를 차례대로 실행하는 경우 하나의 transaction 으로 처리하고 싶어서 다음과 같이 설정해 보았다.
1 @Transactional(propagation = Propagation.REQUIRED) 2 public void insert1(){ 3 A1 a1 = new A1(); 4 a1.col1 = "col1"; 5 a1.col2 = "col2"; 6 mapper1.insertA1(a1); 7 } // End of insert1() 8 9 @Transactional(propagation = Propagation.REQUIRED) 10 public void insert2(){ 11 A2 a2 = new A2(); 12 a2.col1 = "col1"; 13 a2.col2 = "col2"; 14 mapper1.insertA2(a2); 15 } // End of insert2() |
위의 method를 호출하는 Bean에서는 다음과 같이 호출하도록 구현하였다.
1 public void someMethod(){ 2 targetBean.insert1(); 3 targetBean.insert2(); 4 } |
그러나 위와 같이 실행 했을 경우 로그를 살펴 보면 원하는 형태로 실행되지 않았다.
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [tkstone.test.transaction.TransactionInvoker.insert1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [jdbc:mysql://localhost:3306/test, UserName=test@localhost, MySQL Connector Java] for JDBC transaction ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [tkstone.test.transaction.TransactionInvoker.insert2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [jdbc:mysql://localhost:3306/test, UserName=test@localhost, MySQL Connector Java] for JDBC transaction ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [jdbc:mysql://localhost:3306/test, UserName=test@localhost, MySQL Connector Java] after transaction ... |
위의 로그에서 보면 insert1() 과 insert2()가 별도의 Transaction 으로 실행 되었음을 할 수 있다. 여기에 대한 해법은 다음과 같다.
<Transaction context를 관리하는 중간 클래스 추가>
1 package tkstone.test.transaction; 2 3 import org.springframework.transaction.annotation.Propagation; 4 import org.springframework.transaction.annotation.Transactional; 5 6 public class TransactionGateway { 7 private TransactionInvoker invoker; 8 9 public void setTransactionInvoker(TransactionInvoker invoker){ 10 this.invoker = invoker; 11 } 12 13 @Transactional(propagation = Propagation.REQUIRED) 14 public void invoke(){ 15 this.invoker.insert1(); 16 this.invoker.insert2(); 17 } 18 } 19 |
기존 클래스에 Gateway method를 추가하지 않고 새로운 클래스를 생성한 이유는 Spring Transaction 이 Proxy 로 동작하기 때문에 외부에서 호출하는 경우에만 @Transactional 이 동작하기 때문이다.
<호출하는 Bean 수정 >
1 public void someMethod(){ 2 this.transactionGateway.invoke(); 3 } |
위와 같이 수정한 후 실행하면 원래 의도한 Propagation 정책이 적용 되었음을 알 수 있다.
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [tkstone.test.transaction.TransactionGateway.invoke]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection for JDBC transaction DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection to manual commit DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating in existing transaction DEBUG: org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Participating in existing transaction DEBUG: org.mybatis.spring.SqlSessionUtils - Fetched SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession DEBUG: org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection after transaction DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource |
위에서 보면 TransactionGateway에서 새로운 Transaction이 시작 되고 insert1()과 insert2() 에서는 "Participating in existing transaction"이라는 메세지와 함께 기존 Transaction 내에서 SQL 을 실행하고 있다.
여기서 알 수 있는 점은 다음과 같다.
Propagation 정책은 transaction 이 선언된 method 내에서 transaction이 선언된 다른 method를 호출할 때 적용된다.
여기서 insert2()의 정책을 REQUIRES_NEW 로 변경 하면 다음과 같이 실행 된다.
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [tkstone.test.transaction.TransactionGateway.invoke]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection for JDBC transaction ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Suspending current transaction, creating new transaction with name [tkstone.test.transaction.TransactionInvoker.insert2] ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection for JDBC transaction ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection ... DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection ... |
위의 로그 내용은 크게 다음과 같이 구성 되어 있다.
- TransactionGateway 에서 Transaction 시작 (T1)
- insert1() 에서는 기존 Transaction 내에서 실행
- insert2() 에서는 새로운 Transaction 시작(T2) (Suspending current transaction, creating new transaction with ~) 이것을 위해 새로운 DB Connection 을 맺음
- insert2() 에 대해서 commit 수행 (T2)
- TransactionGateway 실행 완료 시 commit 수행 (T1)
대부분의 경우에는 REQUIRED로 충분하나 간혹 REQUIRES_NEW를 통해 새로운 Transaction 을 생성하고자 하는 경우에는 위와 같이 동작함을 이해해야 한다.
'Mybatis > 노하우정보' 카테고리의 다른 글
spring mybatis @Repository (0) | 2016.04.28 |
---|---|
mybatis-resultType을 배열로 (1) | 2016.04.27 |
MyBatis에서 Batch처리(SqlSession과 foreach) (0) | 2016.04.27 |
MyBatis 내장 cache 에 대해서 (0) | 2016.04.27 |
Spring Transaction 내부 동작 메커니즘 (0) | 2016.04.27 |
TransactionTemplate 을 이용한 Spring Transaction 사용 (0) | 2016.04.27 |
Spring Transaction 사용법 (0) | 2016.04.27 |
Spring - Mybatis 에서 Mapper interface 를 주입받는 방법과 SqlSession 을 주입받는 방법 (0) | 2016.04.27 |