@Transactional은 크게 자카르타와 스프링 두 가지 방식이 있다.
1. 스프링에서 제공하는 Transactional (패키지: org.springframework.transaction.annotation.Transactional)
스프링에서 제공하는 Spring AOP를 사용하여 트랜잭션을 관리한다.
스프링의 트랜잭션 관리 방식은 매우 유연하며, Propagation, Isolation, Timeout, ReadOnly 등을 커스터마이징할 수 있는데
아래 코드는 스프링에서 제공하는 Transactional의 다양한 커스터마이징 사용 예제이다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransactionalService {
@Autowired
private ExampleRepository exampleRepository;
// Propagation: 기존 트랜잭션이 있어도 새로운 트랜잭션을 시작 (REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performTransactionWithPropagation() {
// 비즈니스 로직 - 새로운 트랜잭션이 시작됨
exampleRepository.save(new ExampleEntity("Propagation Test"));
}
// Isolation: 트랜잭션 격리 수준을 가장 높은 수준인 SERIALIZABLE로 설정
@Transactional(isolation = Isolation.SERIALIZABLE)
public void performTransactionWithIsolation() {
// 격리 수준이 적용된 트랜잭션 내에서 비즈니스 로직 실행
exampleRepository.save(new ExampleEntity("Isolation Test"));
}
// Timeout: 트랜잭션 제한 시간을 5초로 설정
@Transactional(timeout = 5)
public void performTransactionWithTimeout() throws InterruptedException {
// 6초간 대기하여 제한 시간 5초를 초과하도록 설정 (트랜잭션 실패 유도)
Thread.sleep(6000);
exampleRepository.save(new ExampleEntity("Timeout Test"));
}
// Read-only: 읽기 전용 트랜잭션을 지정하여 데이터 변경 방지
@Transactional(readOnly = true)
public ExampleEntity performTransactionWithReadOnly() {
// 데이터베이스에서 조회만 수행하고 수정 불가
return exampleRepository.findById(1L).orElse(null);
}
// Rollback for: 특정 예외(CustomException)가 발생하면 트랜잭션 롤백
@Transactional(rollbackFor = CustomException.class)
public void performTransactionWithRollbackFor() throws CustomException {
// 데이터 저장
exampleRepository.save(new ExampleEntity("RollbackFor Test"));
// 예외 발생으로 트랜잭션 롤백
if (true) {
throw new CustomException("롤백 트리거");
}
}
}
기본 설정은 아래와 같다.
- Propagation.REQUIRED: 현재 트랜잭션이 있으면 그 트랜잭션에 참여하고, 없으면 새 트랜잭션을 생성.
- Isolation.DEFAULT: 데이터베이스의 기본 격리 수준을 따름.
- Timeout.DEFAULT: 제한 시간이 설정되지 않음.
- Read-only = false: 트랜잭션이 읽기/쓰기를 모두 허용.
- Rollback: RuntimeException이나 Error가 발생하면 트랜잭션이 롤백됨.
2. 자카르타 EE에서 지원하는 Transactional (패키지 : jakarta.transaction.Transactional)
Spring AOP가 아닌, 자카르타 EE의 기본 트랜잭션 관리 메커니즘을 따르며 스프링과 달리 트랜잭션 전파한다.
컨테이너(자카르타 EE 서버)가 트랜잭션 관리의 주체라고 볼 수 있다.
단점은 격리 수준 등의 커스터마이징 기능이 제한적이라는 것. 하지만 자카르타 EE 표준이기 때문에 스프링 외의 다른 EE 환경에서도 사용이 가능하다.
즉, 프레임워크 간 호환성을 고려할 때 적합하다.
구현 예시
import jakarta.transaction.Transactional;
@Transactional(rollbackOn = Exception.class)
public void performJakartaTransaction() {
// 자카르타 트랜잭션 관리하에 실행
}
사실 궁금한건 이거였다.
혹시라도 한 프로젝트에서 두 방식을 섞어서 개발하면 문제가 생길 수도 있지않을까?
결론적으로
스프링의 @Transactional을 기본 설정으로 사용하는 경우, 대부분의 상황에서는 문제 없이 트랜잭션 관리가 잘 동작한다.
하지만 옵션을 커스터마이징하여 사용하는 경우나 특정 조건에서는 몇 가지 문제점이나 주의할 점이 있다.
- 트랜잭션 관리 중복: 스프링과 자카르타 트랜잭션 관리 방식이 동시에 적용되면 중복 관리로 인해 트랜잭션이 두 번 시작되거나 예기치 않게 종료될 수 있음.
- 전파 및 격리 수준 충돌: 두 트랜잭션의 Propagation 및 Isolation Level이 상충될 수 있어, 예상치 못한 트랜잭션 처리 결과가 발생할 수 있음.
- 예외 및 롤백 처리 차이: 스프링과 자카르타 트랜잭션에서 예외에 대한 롤백 처리 방식이 다를 수 있으며, 일관성이 없는 트랜잭션 롤백이 발생할 수 있음.
- 트랜잭션 관리자의 일관성 부족: 스프링 트랜잭션 관리자와 자카르타 트랜잭션 관리자가 동일한 트랜잭션을 처리할 때 동기화 문제가 발생할 수 있음.
요약하면 아래와 같다.
- 자카르타 트랜잭션은 표준 사양으로, 주로 자카르타 EE 또는 Java EE 환경에서 사용되며, 단순한 트랜잭션 관리를 제공하며
특정 프레임워크에 종속되지 않고 표준적인 트랜잭션 처리를 원하는 경우에 사용 - 스프링 트랜잭션이 더 세부적인 트랜잭션 제어가 가능해 복잡한 애플리케이션에서 유용하지만, 스프링 프레임워크에서만 동작
- 두개를 하나의 프로젝트에서 섞어써도 대부분의 상황에서는 문제가 되지 않지만 권장되지 않는다.
- 두 트랜잭션 관리 메커니즘이 충돌할 수 있으며, 트랜잭션의 일관성이 보장되지 않을 가능성이 높기 때문에
한 프로젝트에서는 하나의 트랜잭션 관리 방식만 사용하는 것이 권장된다.
'PROGRAMMING > SPRING' 카테고리의 다른 글
| [SPRING] AOP를 활용한 로그 중앙화 (feat.@Aspect, @Pointcut) (0) | 2024.08.17 |
|---|---|
| [SPRING] Lombok @Log4j2 log4j-slf4j-impl cannot be present with log4j-to-slf4j 에러 (gradle) (0) | 2024.06.28 |
| [SPRING] @EqualsAndHashCode(callSuper = true) (0) | 2024.06.02 |
| [SPRING] Spring boot 3.x에서 QueryDSL 설정 (0) | 2024.05.20 |
| [SPRING] static field에 @Value annotation 적용? (0) | 2024.05.11 |