brunch

You can make anything
by writing

C.S.Lewis

by 최창규 Jan 28. 2022

Transaction marked as rollback

알면 알수록 어려운 트랜잭션의 세계

Spring 트랜잭션을 처리하다 보면 Transaction marked as rollbackOnly 예외를 종종 접하게 됩니다. 트랜잭션 안에서 RuntimeException이 발생해서 트랜잭션이 rollbackOnly로 마킹이 되어 무조건 Rollback이 되는 것입니다.


어라 try-catch 해서 처리했는데?

비록 try-catch 에서 예외를 처리했다 하더라도 RuntimeException 이 트랜잭션 안에서 발생하면 무조건 Rollback으로 표시(mark)가 됩니다. 그럼 위 상황을 우회하려면 어떻게 해야 할까요?


주어진 상황

아래 소스의 historyRepository.save() 에서 DataIntegrityViolationException 이 발생한다고 가정하면 main() 은 sub1()과 sub2()가 모두 실행되지만 commit 시에 예외가 발생하고 트랜잭션이 rollback됩니다. 즉 TransactionSystemException(message="Transaction marked as rollbackOnly") 을 throw 받게 되는 것입니다.

@Transactional 
public void main() {
     sub1();
     sub2(); 


@Transactional 
public void sub1() {
      try {
         logRepository.save();
         historyRepository.save();     // 예외가 발생한다고 가정
      } catch (DataIntegrityViolationException ignore) {

      } 
}

@Transactional 
public void sub2(){
     eventRepository.save();


우회하는 방법

sub1() 에서 try-catch를 했다는 것은 안에서 DataIntegrityViolationException 이 발생해도 무시하고 트랜잭션을 처리하겠다는 의도입니다. 의도에 맞게 수정해 보면


1. sub1() 을 트랜잭션에서 제외      

@Transactional(propagation = Propagation.NOT_SUPPORTED) 
public void sub1() {
       try {
         // ...
       } catch (DataIntegrityViolationException ignore) {
       
       } 


2. 트랜잭션이 필요하다면 main()과 sub1()에 예외 처리      

@Transactional 
public void main() {
    try{
         sub1();
    } catch (TransactionSystemException ignore) {
    
    }

    sub2();
 }   

@Transactional 
public void sub1() {
       try {
           // ...
       } catch (DataIntegrityViolationException ignore) {
       
       } 





p.s. noRollbackFor는 뭐예요?

@Transactional의 속성 중에 하나인 noRollbackFor는 명시를 하면 해당 Exception이 발생해도 Rollback이 되지 않습니다.


단, 주의할 점은 callee 에게 Exception은 그대로 throws 되므로, callee에서 예외처리를 반드시 해줘야 한다는 것입니다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari