본문 바로가기

Back-End/Spring

트랜잭션 전파 속성 ( propagation ), 롤백 예외

트랜잭션을 시작하거나 기존 트랜잭션에 참여하는 방법을 결정하는 속성이다. 트랜잭션 경계의 시작 지점에서 트랜잭션 전파 속성을 참조해서 해당 범위의 트랜잭션을 어떤 식으로 진행시킬지 정할 수 있다.

 

이렇게 글로만 보면 트랜잭션 전파 속성은 왜 써야하는지 전혀 와닿지 않는다. 항상 필요에 의한 공부를 할 때 진정한 공부가 되는 것 같다.

e-커머스 회사를 다니고 있고, 고객 주문 + 주문 내역을 기록해주는 기능을 개발하라고 지시를 받았다! 어떻게 개발할 것인가?

트랜잭션을 어떻게 묶을까?

위의 상황을 분석해보면, 고객 주문은 성공을 했는데 주문 내역이 기록이 되지 않아 롤백된다면??? 이 얼마나 매출을 깍아먹는 개발자인가....

 

즉 지금 상황에서 트랜잭션을 어떻게 묶을지 결정할 수 있는 요인은

  • 중요한 일(주문 내역), 중요하지 않은 일(로그) 묶음을 별개로 가져가자

이다. 즉, 주문이 실패하면 로그도 기록되지 않아야 하지만, 주문이 성공했는데 로그 실패 때문에 전체 롤백이 되면 안된다.

 

트랜잭션 전파 속성 1 - NESTED

이미 진행 중인 트랜잭션이 있으면 중첩 트랜잭션을 시작한다. 즉, 별개의 트랜잭션을 만드는 것이 아니라 트랜잭션 안에 다시 트랜잭션을 만든다. ( 이게 되는구나...? ) 즉, 독립적인 트랜잭션을 새로 만드는 REQUIRES_NEW와는 다르다.

  • 부모 트랜잭션의 커밋,롤백 -> 중첩된 트랜잭션에 영향을 줌
  • 중첩된 트랜잭션의 커밋,롤백 -> 부모 트랜잭션에 영향을 주지 않음! 

즉, 위의 상황에 딱 맞는 구현이다. 주문은 부모 트랜잭션으로 설정하고, 로그는 중첩 트랜잭션으로 구현하면 된다.

 

트랜잭션 전파 속성 2 - REQUIRES_NEW

위에서 NESTED와 비교했던 속성이다. 이 전파 속성은 진행 중인 트랜잭션이 있으면 잠시 보류시키고 항상 새로운 트랜잭션을 시작한다.

class Service {
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void doSomething() {
        // access a database using a DAO
    }
}
  • 서로 커밋, 롤백 영향이 없음. 왜냐하면 데이터베이스 관점에서 보면 서로 다른 별개의 트랜잭션이기 때문

트랜잭션 전파 속성 3 - REQUIRED

기본 속성이다. 미리 시작한 트랜잭션이 있으면 참여하고 아니면 시작한다. 매우 강력하고 유용하다고 하는데 어떠한 부분인지 와닿지는 않는다....

 

트랜잭션 롤백 예외 : rollbackFor

선언적 트랜잭션에서는 런타임 예외가 발생했을 때만 롤백한다.

왜 런타임 예외만 롤백해??

일반적으로 개발할 때, 체크 예외는 리턴 값대신 비즈니스적인 의미를 담아 던지는 예외이다. ( Effective Java 3/e 보면서 checked exception, unchecked exception을 언제써야 하는지 공부를 했다... ) 즉, 기본적으로 비즈니스 예외는 개발자에게 복구를 맡기고 프로그램적으로 문제가 있는 런타임시에만 롤백한다.

 

하지만, Checked Exception도 롤백하고 싶은 경우라면 아래와 같이 사용하면 된다.

@Transactional(rollbackFor = DataNotFoundException.class)