@Transactional 어노테이션 롤백이 동작하지 않는 경우
업무를 진행하는 중 @Transactional 어노테이션이 예상대로 롤백되지 않는 경우가 발생하였다.
스프링에서 트랜잭션 매니저 미설정으로 발생한 이슈로 금방 해결하긴 했지만 겸사겸사 그 외 발생할 수 있는 상황에 대해 찾아보고 정리해본다.
프록시 메커니즘으로 인한 문제 (자기 호출)
Spring은 기본적으로 프록시 패턴을 사용해 @Transactional을 적용한다. 즉, 트랜잭션 처리는 AOP 프록시 객체에서 관리된다.
따라서, 클래스 내부에서 자기 자신의 메서드를 호출할 경우 프록시 객체가 아닌 직접 호출되므로 트랜잭션이 적용되지 않는다.
해결책
- 메서드를 다른 빈에서 호출하도록 리팩토링한다
- 또는 AspectJ를 사용한 방식으로 트랜잭션을 설정한다
<aop:aspectj-autoproxy proxy-target-class="true" />
- 인터페이스가 없는 클래스에서 호출하는 경우, proxy-target-class=true 설정으로 CGLIB 프록시를 사용한다
<tx:annotation-driven proxy-target-class="true"/>
예외가 Catch되어 롤백되지 않는 경우
트랜잭션 롤백은 기본적으로 예외가 메서드 밖으로 던져져야 동작한다. 예외를 내부에서 처리해버리면 롤백이 발생하지 않는다
따라서, 아래처럼 try-catch 블록에서 예외를 처리하면 트랜잭션이 롤백되지 않는다
@Transactional
public void someMethod() {
try {
// 코드 로직
} catch (Exception e) {
// 예외를 잡아서 처리
System.out.println("예외 처리됨: " + e.getMessage());
}
}
해결책
예외를 다시 던지거나 명시적으로 트랜잭션을 롤백하도록 설정한다
@Transactional
public void someMethod() throws Exception {
try {
// 코드 로직
} catch (Exception e) {
// 예외 처리 후 다시 던짐
throw e;
}
}
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Transactional
public void someMethod() {
try {
// 코드 로직
} catch (Exception e) {
// 명시적으로 트랜잭션 롤백
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
예외 유형이 롤백되지 않는 경우
기본적으로 체크 예외(Checked Exception)는 롤백되지 않는다
해결책
- 특정 예외에 대해 롤백하려면 rollbackFor 속성을 사용한다
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
throw new IOException("예외 발생");
}
- 특정 예외에 대해 롤백을 방지하려면 noRollbackFor 속성을 사용한다
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void someMethod() {
throw new IllegalArgumentException("이 예외는 롤백되지 않습니다.");
}
트랜잭션 전파(Propagation) 설정 문제
트랜잭션 전파 설정에 따라 롤백 동작이 달라질 수 있다.
- REQUIRES_NEW : 새로운 트랜잭션이 시작되므로 상위 트랜잭션에 영향을 미치지 않는다
- NESTED : 중첩된 트랜잭션이 롤백되어도 상위 트랜잭션은 롤백되지 않는다
해결책
트랜잭션 전파 옵션을 프로젝트 상황에 맞게 설정한다
@Transactional(propagation = Propagation.REQUIRED)
public void someMethod() {
// 코드 로직
}
데이터베이스 제약 또는 설정 문제
트랜잭션 롤백이 데이터베이스 제약조건이나 데이터베이스 설정에 의해 제한될 수 있다
- 특정 데이터베이스는 DML 작업(예: INSERT, UPDATE) 후 트랜잭션 롤백을 완전히 지원하지 않을 수 있다
- 트랜잭션 격리 수준(Isolation Level) 설정에 따라 롤백 동작이 달라질 수 있다
해결책
- 트랜잭션 격리 수준을 명시적으로 설정한다
@Transactional(isolation = Isolation.SERIALIZABLE)
public void someMethod() {
// 코드 로직
}
- 사용하는 데이터베이스가 트랜잭션 롤백을 완벽하게 지원하는지 확인한다
메서드가 Public 접근 제어자가 아닌 경우
@Transactional 어노테이션은 public 메서드에만 적용된다. private이나 protected 메서드에는 트랜잭션이 적용되지 않는다.
해결책
- 해당 메서드를 public 접근 제어자로 변경한다
@Transactional
public void someMethod() {
// 코드 로직
}
플랫폼 트랜잭션 관리자 미설정
Spring 컨텍스트에서 트랜잭션 관리자(예: DataSourceTransactionManager)가 누락되면 트랜잭션이 제대로 작동하지 않는다.
해결책
트랜잭션 매니저를 설정한다
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
읽기 전용 트랜잭션 설정 문제
@Transactional(readOnly = true) 설정 시 데이터 수정이 포함된 작업이 오류를 발생시킬 수 있다
해결책
읽기 전용이 아닌 경우 readOnly = false로 설정한다
@Transactional(readOnly = false)
public void someMethod() {
// 데이터 수정 작업
}
정리
- 자기 호출 문제 : 프록시가 적용되지 않는 내부 호출
- 예외 처리 문제 : 예외를 내부에서 잡으면 롤백되지 않음
- 예외 유형 문제 : Checked Exception은 기본적으로 롤백되지 않음
- Propagation 문제 : 전파 설정에 따라 롤백 범위가 달라짐
- 데이터베이스 설정 문제 : 격리 수준 또는 제약조건 확인 필요
- 메서드 접근 제어자 문제 : public 메서드만 트랜잭션이 적용됨
- 트랜잭션 매니저 누락 : Spring 설정 확인 필요
- 읽기 전용 설정 문제 : 데이터 수정 시 readOnly = false로 설정