ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] Transactional Propagation 정리하기
    Server/Spring 2021. 11. 28. 23:52
    728x90
    반응형

    @Transactional Propagation 알아보기

    이번 글에서는 Spring Transactional 어노테이션에서 propagation 특징에 대해서 정리해보려 합니다.

     

    Propagation

    옵션 설명
    REQUIRED 기본 옵션
    부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류, 그렇지 않다면 새로운 트랜잭션을 만든다.
    중간에 자식/부모에서 rollback이 발생된다면 자식과 부모 모두 rollback 한다.
    REQUIRES_NEW 무조건 새로운 트랜잭션을 만든다.
    nested한 방식으로 메소드 호출이 이루어지더라도 rollback은 각각 이루어 진다.
    MANDATORY 무조건 부모 트랜잭션에 합류시킨다.
    부모 트랜잭션이 존재하지 않는다면 예외를 발생시킨다.
    SUPPORTS 메소드가 트랜잭션을 필요로 하지는 않지만, 진행 중인 트랜잭션이 존재하면 트랜잭션을 사용한다는 것을 의미한다. 진행 중인 트랜잭션이 존재하지 않더라도 메소드는 정상적으로 동작한다.
    NESTED 부모 트랜잭션이 존재하면 부모 트랜잭션에 중첩시키고, 부모 트랜잭션이 존재하지 않는다면 새로운 트랜잭션을 생성한다.
    부모 트랜잭션에 예외가 발생하면 자식 트랜잭션도 rollback한다.
    자식 트랜잭션에 예외가 발생하더라도 부모 트랜잭션은 rollback하지 않는다. 이때 롤백은 부모 트랜잭션에서 자식 트랜잭션을 호출하는 지점까지만 롤백된다. 이후 부모 트랜잭션에서 문제가 없으면 부모 트랜잭션은 끝까지 commit 된다.
    현재 트랜잭션이 있으면 중첩 트랜잭션 내에서 실행하고, 그렇지 않으면 REQUIRED 처럼 동작합니다.
    NEVER 메소드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존재하면 익셉션이 발생한다.

     

    대부분의 글에서 Propagation에 대해서 위와 같이 설명합니다. 말로만 들어도 어떤 말인지 알 수 있지만 저는 완벽하게 이해가 되지 않아서 직접 예제 코드를 작성해보면서 테스트 해보았습니다. 하나씩 알아보겠습니다.



    REQUIRED

    스크린샷 2021-11-28 오후 7 31 18

    @Transactional에 아무 설정을 하지 않고 사용하면 Propagation Default인 REQUIRED가 설정됩니다.

     

    스크린샷 2021-11-28 오후 7 35 00

    REQUIREDDEFAULT 설정 값이다 보니 많은 사람들이 제일 많이 사용하고 있는 Propagation 속성 입니다. 즉, 위의 글에 적혀 있던 대로 자식/부모에서 rollback이 발생된다면 자식과 부모 모두 rollback 한다. 라는 말을 이해할 수 있습니다. 즉, 위의 예제코드 처럼 자식 트랜잭션에서 예외가 발생하면 부모, 자식 모두 롤백되어 어떤 유저도 저장되지 않습니다. (부모에서 예외가 발생해도 마찬가지 입니다.)



    자식에서 예외 발생한 것을 부모에서 예외 처리

    스크린샷 2021-11-28 오후 9 27 52

    그러면 위와 같이 자식에서 발생한 예외부모 트랜잭션에서 try-catch로 예외 처리를 하는 경우에는 어떻게 될까요?

     

    스크린샷 2021-11-28 오후 9 30 00

    try-catch를 하였더라도 예외가 발생하면서 전부 RollBack 되는 것을 볼 수 있습니다.



    REQUIRES_NEW

    자식에서 예외 발생

    스크린샷 2021-11-28 오후 7 40 25

    이번에는 자식에게 REQUIRES_NEW 옵션을 준 후에 위의 코드와 같이 테스트 해보았습니다. 위에서 적었던 설명으로 보면 부모, 자식 트랜잭션이 각각 열리기 때문에 자식에서 예외가 발생해도 부모에서 save 한 것은 저장이 되는 것을 예상했습니다.

    스크린샷 2021-11-28 오후 7 44 58

    그런데 부모에서 저장한 유저도 INSERT 되지 않은 것을 볼 수 있습니다. REQUIRES_NEW자식 트랜잭션에서 발생한 것이 부모 트랜잭션 까지 전파되지 않는다의 말은 틀린말이 아닌가 생각이 들었습니다.

     

    댓글로 첨언해주신 것을 기반으로 좀 더 설명을 보강하자면 "트랜잭션이 전파"되는 것과 "예외가 전파" 되는 것은 다르다는 특징이 있습니다. 자식 쪽에 예외가 발생할 경우 자식 쪽은 트랜잭션이 롤백이 되는 것이 맞습니다. 그런데 이 때 자식 쪽에서 발생한 예외가 부모 쪽으로 전파되기 때문에 부모 쪽에서도 예외가 발생하여 롤백이 일어난 것으로 이해해야 맞을 것 같습니다.

     



    자식에서 발생한 예외 부모에서 예외 처리

    스크린샷 2021-11-28 오후 9 33 47

    그래서 이번에는 부모에서 예외 처리를 했을 때는 어떻게 처리되는지 보기 위해서 예외 처리하고 실행해보겠습니다. 이번에는 자식에서 발생한 예외를 부모에서 try-catch로 묶어주었는데요. 그랬더니 자식 트랜잭션을 호출하기 전까지의 쿼리만 커밋된 것을 확인할 수 있습니다. 예외 처리를 해주어야 부모 트랜잭션도 자식 트랜잭션에 영향을 받지 않고 커밋을 하는 것 같습니다.

     

    스크린샷 2021-11-28 오후 9 35 55



    부모에서 예외 발생

    스크린샷 2021-11-28 오후 7 46 02

    이번에는 부모에서 예외가 발생한 경우에는 어떻게 되는지 테스트 해보겠습니다.

     

    스크린샷 2021-11-28 오후 7 47 58

    REQUIRES_NEW부모 트랜잭션에서 예외가 발생해도 자식 트랜잭션에서는 꼭 커밋되어야 하는 상황에서 사용하면 좋을 것 같습니다.



    MANDATORY

    스크린샷 2021-11-28 오후 7 53 56

    MANDATORY는 부모 트랜잭션이 존재하면 무조건 부모 트랜잭션에 합류시키고, 부모 트랜잭션에 트랜잭션이 시작된 것이 없다면 예외를 발생시킵니다. 즉, 혼자서는 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용합니다.

     

    스크린샷 2021-11-28 오후 7 56 24

    예측 했던 대로 부모에서 트랜잭션을 시작하지 않아서 위와 같은 에러가 발생한 것을 볼 수 있습니다.

     

    스크린샷 2021-11-28 오후 7 58 52

    그리고 데이터베이스에 어떻게 저장되었는지 확인해보면 부모에서 첫 번째로 저장한 User 1번만 저장되고 나머지는 저장되지 않은 것을 볼 수 있습니다.



    NESTED

    부모에서 예외 발생

    스크린샷 2021-11-28 오후 9 44 14

    NESTED 속성에서는 부모 트랜잭션에서 에러가 발생하면 자식 트랜잭션은 어떻게 되는지 알아보겠습니다. 위의 코드를 실행하면 자식 트랜잭션도 커밋이 되지 않습니다. 이유는 부모 트랜잭션이 존재하면 자식 트랜잭션도 부모 트랜잭션에 합류하기 때문입니다.



    자식에서 예외 발생

    스크린샷 2021-11-28 오후 9 41 50

    그리고 자식에서 예외가 발생하여도 마찬가지로 어떤 값도 User에 저장되지 않습니다.

     

    스크린샷 2021-11-28 오후 9 41 30

    그리고 예외를 발생했을 때 에러 로그를 보면 JpaDialect does not support savepoints - check your JPA provider's capabilities가 발생합니다. NESTEDJDBC 3.0 드라이버를 사용할 때에만 적용된다 라는 특징이 있습니다.



    자식에서 예외 발생한 예외 부모에서 예외 처리

    스크린샷 2021-11-28 오후 9 55 02

    이번에는 부모 트랜잭션에서 예외 처리를 했을 때의 경우를 해보겠습니다.

     

    스크린샷 2021-11-28 오후 10 02 35

    이번에도 자식 트랜잭션을 호출하기 전 부모 트랜잭션에서 호출한 INSERT 쿼리가 커밋된 것을 볼 수 있습니다.



    부모 트랜잭션이 없을 경우

    스크린샷 2021-11-28 오후 10 06 20

    NESTED 속성은 부모 트랜잭션이 존재하지 않는다면 새로운 트랜잭션을 생성한다고 했는데요.

     

    스크린샷 2021-11-28 오후 10 02 35

    그래서 위처럼 부모 트랜잭션이 없을 때는 자식 트랜잭션에서 새로 열리다 보니 자식 트랜잭션에서 예외가 발생해도 부모 트랜잭션에서는 1번 유저가 저장 커밋이 된 것을 볼 수 있습니다.



    NEVER

    스크린샷 2021-11-28 오후 10 32 35

    NEVER는 메소드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존재하면 익셉션이 발생합니다.

     

    스크린샷 2021-11-28 오후 10 36 01

    위처럼 부모에서 트랜잭션이 존재한다면 Existing transaction found for transaction marked with propagation 'never' 에러가 발생하는 것을 볼 수 있습니다.

     

    이번 글의 코드를 확인하고 싶다면 여기 에서 확인할 수 있습니다.

    반응형

    댓글

Designed by Tistory.