트랜잭션의 개념은 여기를 클릭하여 확인해보세요 !
@Transactional이 중첩이 되었을 때
업무 중 @Transactional이 중첩이 되었을 때(아래와 같은 상황) 어떻게 진행될지 궁금해졌다 !
@Transactional
public Info buy(String item){
payment newPayment = new payment(item);
return payment(newPayment)
}
@Transactional
public Info payment(Info info){
buyRepository.save(info);
return info;
}
buy 메서드에서 payment 메서드를 호출하고 있는데, 두 메서드 모두 @Transactional 어노테이션이 적용되어 있는 상태입니다.
트랙잭션의 전파 설정
스프링에서는 위 상황과 같이 트랜잭션이 중첩되어 실행되는 경우 어떻게 처리할지에 대한 설정을 제공하고 있습니다.
이를 트랜잭션 전파 설정이라고 합니다 !
REQUIRED (기본 설정)
- 별도로 설정하지 않으면 REQUIRED 설정이 사용됩니다. (Default 설정)
- 부모트랜잭션이 있는 경우, 새로 트랜잭션을 생성하지 않고 부모 트랜잭션에 합쳐집니다.
- 부모 트랜잭션이 없는 경우에만 자신의 트랜잭션을 새로 생성합니다.
REQUIRES_NEW
- 부모 트랜잭션의 존재 여부와 관계없이, 무조건 자신의 트랜잭션을 새로 생성합니다.
- 기존에 실행중이던 부모 트랜잭션은 자식 트랜잭션이 종료될 때까지 대기합니다.
- 자식 트랜잭션이 롤백되더라도 부모 트랜잭션이 롤백되지 않습니다.
- 자식 트랜잭션이 정상적으로 종료되고 부모 트랜잭션이 롤백되는 경우, 자식 트랜잭션은 롤백되지 않습니다.
- 즉, 부모 트랜잭션과 자식 트랜잭션 사이의 롤백 여부는 서로 독립적입니다.
NESTED
- 부모 트랜잭션의 존재 여부와 관계없이, 무조건 자신의 트랜잭션을 생성합니다.
- 기존에 실행중이던 부모 트랜잭션은 자식 트랜잭션이 종료될 때까지 대기합니다.
- 자식 트랜잭션은 부모 트랜잭션과 함께 커밋됩니다.
- 자식 트랜잭션이 롤백되는 경우, 부모 트랜잭션도 롤백됩니다.
- 자식 트랜잭션이 정상적으로 종료되고 부모 트랜잭션이 롤백되는 경우, 자식 트랜잭션도 같이 롤백됩니다.
- REQUIRES_NEW 설정과 비슷하지만, 부모와 자식 트랜잭션의 롤백 여부가 서로 종속적이라는 점이 다릅니다.
SUPPORTS
- 부모 트랜잭션이 있는 경우, 새로 트랜잭션을 생성하지 않고 부모 트랜잭션에 합쳐집니다.
- 부모 트랜잭션이 없는 경우 트랜잭션을 적용하지 않습니다.
NOT_SUPPORTED
- 부모 트랜잭션이 있는 경우, 부모 트랜잭션을 일시중지 시킵니다.
- 이후 트랜잭션을 적용하지 않은 채로 메서드를 수행하고, 메서드가 종료된 후 부모 트랜잭션을 재개합니다.
- 부모 트랜잭션이 없는 경우, 트랜잭션을 적용하지 않습니다.
MANDATORY
- 부모 트랜잭션이 있는 경우, 새로 트랜잭션을 생성하지 않고 부모 트랜잭션에 합쳐집니다.
- 부모 트랜잭션이 없는 경우, 예외가 발생합니다.
NEVER
- 부모 트랜잭션이 있는 경우, 예외가 발생합니다.
- 부모 트랜잭션이 없는 경우, 트랜잭션을 적용하지 않습니다.
- 즉, 트랜잭션을 절때 적용해서는 안되는 경우에 사용합니다.
사용법과 주의할 점
아래 코드는 위의 예시 코드의 메서드를 클래스로 묶어 놓은 것입니다.
@Service
public class Shop {
@Transactional
public Info buy(String item){
payment newPayment = new payment(item);
return payment(newPayment)
}
@Transactional(propagation = Propagation.NESTED)
public Info payment(Info info){
buyRepository.save(info);
return info;
}
}
트랜잭션 전파 설정은 @Transactional 어노테이션에 propagation 속성을 지정하여 사용할 수 있습니다.
위 코드는 payment 메서드에 NESTED 전파 설정을 사용하여 자식 트랜잭션을 생성하게 만드는 형태입니다.
하지만, 위 코드는 실제로 실행해 보면 의도한 것과는 다르게 자식 트랜잭션이 생성되지 않고 buy 메서드의 트랜잭션만 생성됩니다.
그 이유는 두 메서드가 하나의 클래스로 묶여있기 때문입니다.
스프링의 트랜잭션 기능은 스프링 AOP의 Proxy를 기반으로 작동합니다.
@Transactional 어노테이션이 붙은 메서드가 실행되는 경우, Spring AOP에서 Proxy 객체를 만들어 해당 메서드를 실행 전/후로 트랜잭션 시작/커밋을 하는 코드를 추가해 주는 방식으로 작동합니다.
이때 Spring에서 @Transactional 어노테이션이 붙은 메서드의 호출을 가로챌 수 있어야 하는데, Spring AOP에서는 기본적으로 같은 객체 내부에서의 메서드 호출을 가로챌 수 없다고합니다.
그래서 buy 메서드의 트랜잭션을 생성하고, payment를 호출했을 때 트랜잭션이 따로 생성되지 않는 것입니다.
위 문제를 해결하는 가장 단간한 방법은 두 메서드를 다른 두 클래스로 분리하는 것입니다.
@Service
public class Shop {
@Transactional
public Info buy(String item){
payment newPayment = new payment(item);
return payment(newPayment)
}
}
----------------------------------------------
@Service
public class Shop2 {
@Transactional(propagation = Propagation.NESTED)
public Info payment(Info info){
buyRepository.save(info);
return info;
}
}
'프로그래밍 > Java' 카테고리의 다른 글
[Java, 자바] 싱글톤 패턴(Singleton Pattern)이란 ? (0) | 2024.04.15 |
---|---|
[Java, 자바] instanceof란 ? (0) | 2024.04.13 |
[Spring, Spring Boot] @Configuration이란 ? (0) | 2024.04.12 |
[Spring, String Boot] StringUtils 사용 방법 (0) | 2024.04.08 |
[Spring, Spring Boot] @RequiredArgsConstructor란 ? (0) | 2024.04.01 |
개발의 모든 것 !
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!