728x90
반응형

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 을 생성하고자 하는 경우에는 위와 같이 동작함을 이해해야 한다.

728x90
반응형
블로그 이미지

nineDeveloper

안녕하세요 현직 개발자 입니다 ~ 빠르게 변화하는 세상에 뒤쳐지지 않도록 우리모두 열심히 공부합시다 ~! 개발공부는 넘나 재미있는 것~!

,