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'