JPA 를 사용하여 @Transactional 에노테이션을 메소드에 설정하면 트랜잭션이 시작하는 시점과 commit 되는 시점이 메소드의 처음과 끝이라는 것을 알 것이다. 우리가 Repository에 save 하는 과정에서도 트랜잭션 처리가 되어 있다. 만약 이 save() 를 한 줄, 한 줄 호출을 한다면 별도의 트랜잭션을 사용하여 동작이 되는 것이겠죠.
지금부턴 트랜잭션의 전파(Propagation)에 대해 간단히 포스팅 해보겠습니다.
우리는 Member라는 정보를 Repository에 저장하고 수정하는 로직에 더티 체킹이라는 것을 알고 있다.
더티 체킹은 트랜잭션 단위에서 영속성 컨텍스트 내부에서 1차 캐시의 스냅샷을 기준으로 변경된 값을 비교하는 동작을 더티 체킹이라 하는데 이러한 동작은 어떻게 일어날까요?
기존 작업장이 있으면 재사용을 할거야! 없으면 새로 만들고!
@Transactional 에노테이션의 기본 propagation 값은*REQUIRED*(TransactionDefinition.*PROPAGATION_REQUIRED*) 으로 되어 있다.
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)
- 기존 트랜잭션이 있으면 재활용
- 없으면 새로 생성
기존의 트랜잭션이 있으면 기존 것을 그대로 사용하고 만약 없으면 트랜잭션을 새로 생성하여 작업을 한다는 것이다.
member 정보를 수정하고 테스트하는 코드이다. 과연 이 테스트는 성공 할 것인가?
테스트는 당연히 실패다. 더티 체킹은 한 트랜잭션 내에서 작업이 되어야 한다. 메소드에 @Transactional을 선언하여 테스트를 진행하면 성공이다.
이처럼 save() 의 트랜잭션을 transaction_propagation_test() 에 재사용하는 것을 볼 수 있다.
나는 재사용 필요 없어 무조건 신상만!
먼저 아래와 같은 코드를 보자.
@Transactional
public void singleTransactionSave(Member member) {
memberRepository.save(member);
throw new IllegalArgumentException("RollBack!");
}
@Test
@DisplayName("트랜잭션 전파 테스트2")
@Transactional
void transaction_propagation_test2() {
Member member = new Member("이기영", "기영@naver.com", Gender.MALE);
try {
memberService.singleTransactionSave(member);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
System.out.println(memberRepository.findAll());
}
member 정보를 저장하고 저장소에서 정보를 가져오는 동작을 테스트 해보았다.
일부러 저장 로직에 예외를 던지고 테스트하는 트랜잭션 단위에 해당 예외를 잡는 과정을 구현했다.
결과는 예외를 잘 처리하여 member 정보가 저장이 되었다.
하지만 propagation속성을 *REQUIRES_NEW*(TransactionDefinition.*PROPAGATION_REQUIRES_NEW*) 으로 변경하면 어떻게 될 것인가?
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
- 재활용 필요 없음 무조건 신상만
- 호출하는 곳의 트랜잭션과는 상관없이 자체적으로 커밋과 롤백을 진행하겠다는 얘기
- 독립성 특징을 가진다.
@Transactional
public void singleTransactionSave(Member member) {
memberRepository.save(member);
throw new IllegalArgumentException("RollBack!");
}
@Test
@DisplayName("트랜잭션 전파 테스트2")
@Transactional
void transaction_propagation_test2() {
Member member = new Member("이기영", "기영@naver.com", Gender.MALE);
try {
memberService.singleTransactionSave(member);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
System.out.println(memberRepository.findAll());
}
*REQUIRES_NEW*(TransactionDefinition.*PROPAGATION_REQUIRES_NEW*) 를 이해하고 있으면 동작을 예상할 수 있겠다.
member 정보가 아무런 조회가 되지 않을 것이다.
호출하는 곳의 트랜잭션을 재사용하지 않고 자기의 트랜잭션 작업만 독립적으로 진행하는 동작을 하여 이러한 결과를 얻을 수 있었다.
새로운 작업장을 사용하는데 기존 작업장도 조금은 사용할거야
@Test
@DisplayName("트랜잭션 전파 테스트3")
@Transactional
void transaction_propagation_test3() {
Member member1 = new Member("이기영", "기영@naver.com", Gender.MALE);
memberRepository.save(member1);
Member member2 = new Member("이기철", "기철@naver.com", Gender.MALE);
try {
memberService.singleTransactionSave(member2);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
System.out.println(memberRepository.findAll());
}
위와 코드를 보면 singleTransactionSave() 는 예외가 발생하여 rollback이 될 것이고 save() 는 어떤 결과가 나올 것 인가?
nested 동작을 살펴보면 처음 save()는 성공한다. 이러한 동작을 보면 NESTED는 새로운 트랜잭션을 그대로 사용하고 독립적인 움직임이 보인다.
NESTED(TransactionDefinition.PROPAGATION_NESTED)
- 호출하는 트랜잭션을 그대로 사용하는데
- 약간의 분리되어 동작을 시킬 수 있음
- save point 까지는 commit이 성공한다.
이 외에도 4가지의 옵션들이 있다.
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
- 호출하는 쪽에서 트랜잭션이 있다면 해당 트랜잭션을 활용한다.
- 없다면 굳이 새로 생성하지 않는다.
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED)
- 호출 하는 쪽에서 트랜잭션을 가지고 있으면 잠시 멈춘다.
- 해당 영역은 트랜잭션 없이 별도로 동작하게 된다.
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY)
- 필수적으로 트랜잭션이 존재 해야함
- 없으면 에러를 발생
NEVER(TransactionDefinition.PROPAGATION_NEVER)
- 트랜잭션이 없어야 한다
- 트랜잭션이 존재하면 에러를 발생
'JPA' 카테고리의 다른 글
[Jpa] 테스트는 어느 부분까지 해야할까? (0) | 2022.08.26 |
---|---|
[Jpa] 단일 테이블 전략의 상속 관계 매핑 이슈 (0) | 2022.08.20 |
[Jpa] 대댓글 계층구조 연관관계 메소드 이슈 (0) | 2022.08.12 |
[Jpa] Cascade (0) | 2022.08.07 |
[Jpa] Transaction Scope and Isolation (0) | 2022.08.05 |
[Jpa] Deprecated 된 getById() 대안 getReferenceById() (0) | 2022.08.02 |
[Jpa] Dirty Checking (0) | 2022.07.19 |
[Jpa] Projections (0) | 2022.07.18 |