Search
Duplicate

Spring Transaction Propagation

배경

포스팅 일련번호 생성 편에서 Spring Transaction Propagation를 이용하려다가 착각 했던 부분이
있었는데 당시에 개발 과정에서 새로운 트랜잭션 생성이 필요하다 판단하고
Spring에서 제공하는 Transaction의 기능을 살펴보던 중에 Propagation 옵션을 사용하다가
기존 알고 있던 부분과 다른 부분에 대해서 작성하겠습니다.

내용

기존 Transaction이 설정되어있는 서비스 코드 내에 새로운 Transaction을 만들어주면 동작할 코드가
필요했습니다.
Spring IO 에서 Transaction Propagation를 정리한 글이 있고 아래 링크가 있습니다.
그리고 Spring Transaction propagation 구글링을 해보면 참고 자료가 많이 있는데요.
옵션은 기본 옵셥인 REQUIRED 부터 여러가지가 있는데 그중에 실무에서 자주 사용하는 REQUIRED
와 REQUIRES_NEW 에 대해 알아보겠습니다.
NESTED, SUPPORT 같은 다른 옵션들도 많이 있는데 아직 실무에서 사용한 적은 없고 사용하게 된다
면 다음 글에 작성해보겠습니다.
아래는 구글 번역기로 돌리거나 구글링하면 쉽게 찾아볼 수 있는 정의입니다.
REQUIRED
부모 트랜잭션 내에서 실행하며, 부모 트랜잭션이 없을 경우 새로운 트랜잭션 생성.
부모 트랜잭션이 있다면 부모 트랜잭션에 합류합니다.
REQUIRES_NEW
항상 새 트랜잭성을 생성.
각 트랜잭션 범위에 대해 항상 독립적인 물리적 트랜잭션을 사용. 
예제 코드를 보면서 확인해보겠습니다.
간단하게 유저 정보와 로그를 저장하는 서비스 코드입니다.
유저를 저장하는 서비스의 saveUser와 SaveLog에 모두 @Transactional 어노테이션을 붙여줬습니다.
트랜잭션 처리를 할 메서드에 붙여주면 스프링에서 메서드가 호출 되기전 메서드가 종료되고 나서
proxy 패턴을 사용한 커밋, 롤백 처리를 지원합니다.
@Slf4j @RequiredArgsConstructor @Service public class UserService { private final LogDao logDao; private final LogService logservice; @Transactional public void saveUser(User user, Log userLog) { userDao.saveUser(user); try { logservice.saveLog(userLog); } catch(Exception e) { log.info("log 저장 실패"); } } }
Java
복사
@Slf4j @RequiredArgsConstructor @Service public class LogService { private final LogDao logDao; @Transactional public void saveLog(Log userLog) { logDao.saveLog(userLog); } }
Java
복사
이렇게 작성된 코드에서 만약 saveLog에서 예외가 발생한다면 saveUser의 트랜잭션까지
모두 롤백이됩니다.
부모 트랜잭션에 합류해서 하나의 트랜잭션으로 처리되기 때문입니다.
SaveLog를 아래와 같이 REQUIRES_NEW 옵션을 주면 어떻게 될까요?
@Slf4j @RequiredArgsConstructor @Service public class LogService { private final LogDao logDao; @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveLog(Log userLog) { logDao.saveLog(userLog); } }
Java
복사
그러면 saveLog에서 예외가 발생하여 롤백 되더라도 부모 트랜잭션에는 영향을 끼치지 않습니다.
새로운 트랜잭션에서 처리되고 있기 때문입니다.
SaveUser에 try~catch를 제거하면 어떻게 될까요?
@Slf4j @RequiredArgsConstructor @Service public class UserService { private final LogDao logDao; private final LogService logservice; @Transactional public void saveUser(User user, Log userLog) { userDao.saveUser(user); logservice.saveLog(userLog); log.info("log 저장 실패"); } }
Java
복사
saveLog에서 예외가 발생하면 REQUIRES_NEW 옵션이 설정되어 있음에도 불구하고 SaveUser도
롤백이 됩니다.
이유는 try~catch를 제거하면서 예외 처리를 하지 않게 되면 콜스택을 검색하여 오류가 발생한
메서드부터 메서드를 호출한 곳으로 역순으로 적절한 예외 처리를 할 수 있는 코드가 있는지
확인하다가 찾지 못하게되면 프로그램을 종료되게 됩니다.
그런데 트랜잭션이 물리적으로 분리 되었다고 해도 결국엔 같은 Thread를 사용하기 때문에
이런 예외 처리로 인해 종료되면서 부모의 트랜잭션도 영향을 미치게 됩니다.

마무리

스프링에서 지원하는 트랜잭션 자주 사용하는 전파 옵션에 대해 간략하게 알아봤습니다.
정리하면 트랜잭션이 매번 새로 생성되서 독립적인 트랜잭션 이라고 해도 예외 처리까지는
독립적이지는 않기 때문에 @Transactional 처리 시 예외 처리에 따라 어떻게 동작할지 분석하고
테스트 해보고 신경 써서 처리해줘야 합니다

참고

스프링 IO 참조 문서