본문 바로가기
카테고리 없음

[Spring] Transaction Propagation

by 잉ㅈI 2024. 12. 16.

Transaction

  • 시작과 종료가 존재
  • 종료
    • 커밋(commit): 모든 작업을 확정지음
    • 롤백(rollback): 모든 작업을 무효화함
  • 스프링에서는 내부적으로 커넥션 갖고있음→ transactionManager 이용
  • 하나의 트랜잭션 시작시, commit () 또는 rollback() 호출될 때 까지가 하나의 트랜잭션으로 묶임
  • 스프링에서는 선언적 트랜잭션(트랜잭션 어노테이션, @Transactional)
    • 여러 트랜잭션을 묶어서 하나의 트랜잭션 경계를 만들 수도 있음

물리 트랜잭션 vs 논리 트랜잭션

  • 기존의 트랜잭션 진행 중일 때 추가적인 트랜잭션 진행해야하는 경우엔?
    • 만약, 트랜잭션 전파 없이 1개의 트랜잭션만 사용되면 물리 트랜잭션만 존재
  • 트랜잭션 전파 사용될 때 논리 트랜잭션 개념 사용
  • 물리 트랜잭션
    • 실제 데이터베이스에 적용되는 트랜잭션, 커넥션을 통해 커밋/롤백하는 단위
  • 논리 트랜잭션
    • 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위
  • 모든 논리 트랜잭션이 커밋 → 물리 트랜잭션 커밋
  • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션도 롤백

Transaction Propagation

  • 전파 속성
    • 이미 트랜잭션이 진행 중일 때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것
    • 7가지 전파 속성 존재
    • REQUIRED, MANDATORY, SUPPORTS, REQUIRES_NEW , NOT_SUPPORTED, NESTED, NEVER

<테스트 할 상황>

- 부모에서 createUser1을 호출하고 save한다

- 이후 자식에서 createUser2를 호출하고 save한다

- 이후 다시 부모에서 createUser3를 호출하고 save한다

- 결론적으로 1 -> 2 -> 3순으로 호출이 되나, 자식과 부모에서 예외 여부를 포함하여 트랜잭션 전파범위에 따라 저장 결과가 달라진다

1) REQUIRED

  • default 속성

  • Transcation interface에 들어가보면 default가 REQUIRED인 것을 알수 있음 
  • 2개의 논리 트랜잭션을 묶어 1개의 물리 트랜잭션 사용
  • 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류, 그렇지 않다면 새로운 트랜잭션을 만듦
  • 중간에 자식/부모에서 rollback이 발생된다면 자식과 부모 모두 rollback

 

  • 자식에서 예외를 터트리고 부모에서 처리하면
org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
  • 자식 트랜잭션에서 예외가 발생하면 → 부모, 자식 모두 롤백되어 아무 User도 저장 안됨
  • 같은 상황에서 REQUIRED이 아닌, REQUIRES_NEW로 설정한다면?

2) REQUIRES_NEW

  • 무조건 새로운 트랜잭션을 만든다
  • nested한 방식으로 메소드 호출이 이루어지더라도 rollback은 각각 이루어 진다.

  • 결과적으로 createUser(1) 만 저장된다!
  • 그렇다면 부모에서 예외가 발생하면?
    • createUser(2)만 저장됨 (== 자식만 저장됨)
    • 트랜잭션 각각 만들어짐을 알수 있고, 부모에서 예외가 발생하더라도 자식에서 꼭 커밋이 되어야한다면 REQUIRES_NEW를 설정해야함

3) MANDATORY

  • 트랜잭션 의무(없으면 예외)
  • 부모에 트랜잭션 존재하면?
    • 무조건 부모 트랜잭션에 합류
  • 부모에 트랜잭션 존재하지 않는다면?
    • 예외발생(자식 메소드 들어가기 전까지 커밋 가능)

 

  • 부모에 트랜잭션 존재 안하면?
    • 예외 발생
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
  • createUser(1)만 저장됨을 알 수 있다

 

4) SUPPORTS

  • 메소드가 트랜잭션을 필요로 하지는 않지만, 진행 중인 트랜잭션이 존재하면 트랜잭션을 사용하겠다의 의미(== 트랜잭션 없어도 됨)
  • 기존 트랜잭션 없음
    • 트랜잭션 없이 진행
  • 기존 트랜잭션 있음 
    • 기존 트랜잭션에 참여

 

5) NESTED

  • 부모 트랜잭션이 존재하면
    • 부모 트랜잭션에 중첩시키기
  • 부모 트랜잭션이 존재하지 않는다면
    • 새로운 트랜잭션을 생성
  • 부모 트랜잭션에 예외가 발생하면 자식 트랜잭션도 rollback
  • 자식 트랜잭션에 예외가 발생하더라도 부모 트랜잭션은 rollback하지 않음
    • 이때 롤백은 부모 트랜잭션에서 자식 트랜잭션을 호출하는 지점까지만 롤백
    • 이후 부모 트랜잭션에서 문제가 없으면 부모 트랜잭션은 끝까지 commit
    • 현재 트랜잭션이 있으면 중첩 트랜잭션 내에서 실행하고, 그렇지 않으면 REQUIRED 처럼 동작
  • 중첩 트랜잭션은 JPA에서 사용 불가
  • 부모에 트랜잭션 존재하면
    • 부모에 예외 터지면, 아무것도 저장되지않음 → 이유는 부모트랜잭션 존재시 자식은 부모에 합류하기 때문

 

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

 

더보기

여기서 savepoints란?

  • 저장점(savepoint)을 정의하면 롤백시 트랜잭션에 포함된 전체 작업을 롤백하는 것이 아니라 현 시점에서 savepoint까지 트랜잭션 일부만 롤백 가능
  • NESTED 트랜잭션은 현재 트랜잭션의 내부에서 새로운 트랜잭션이 시작되며, 별도의 savepoint를 생성
    • JPA 또는 사용 중인 데이터베이스 드라이버가 savepoint 기능을 지원하지 않을 때 발생
  • 자식에서 예외 발생

  • 마찬가지로 모두 저장안된다
  • 부모에서 예외처리 했을 경우
    • 자식 트랜잭션 호출 전까지 commit 됨
  • 부모 트랜잭션 아예 없는 경우
    • 새로운 트랜잭션 생기기 때문에 자식에 예외가 발생하더라도 그전 유저는 저장된다

6) NEVER

  • 메소드가 트랜잭션을 필요로 하지 않음
  • 만약 진행 중인 트랜잭션이 존재하면 exception 발생

  • 트랜잭션존재하면 exception 발생 + 당연히 저장도 안된다
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'