트랜잭션 처리가 필요한 메서드의 코드에서 Runtime 예외가 발생하고, 그 메서드를 호출하는 메서드에서 해당 부분을 예외 처리를 무시하는 방식으로 넘겼더니 실행 과정에서 "Transaction silently rolled back because it has been marked as rollback-only" 오류가 발생했다. 이 오류는 트랜잭션이 롤백 상태로 표시되어 커밋되지 않고 자동으로 롤백되었음을 의미한다. 이 문제는 주로 트랜잭션 관리와 관련된 설정이나 코드에서 발생할 수 있다. 이 포스트에서는 이 문제의 주요 원인과 해결 방법을 상세히 설명한다.
문제 원인
1. 예외 발생
트랜잭션 내에서 RuntimeException이나 Error가 발생하면 Spring은 기본적으로 트랜잭션을 롤백한다. 이는 Spring의 트랜잭션 관리 기본 설정 때문이다. 예를 들어, 데이터베이스 작업 중에 DataIntegrityViolationException이 발생하면 트랜잭션이 롤백된다.
2. 명시적 롤백
코드에서 TransactionStatus.setRollbackOnly() 메서드를 호출하여 트랜잭션을 롤백 상태로 설정한 경우. 이 메서드는 현재 트랜잭션을 롤백 상태로 표시하며, 이후에 트랜잭션을 커밋하려고 하면 롤백된다.
3. 트랜잭션 속성 설정
@Transactional 어노테이션의 속성에서 특정 예외가 발생하면 롤백하도록 설정한 경우. 예를 들어, @Transactional(rollbackFor = Exception.class)와 같이 설정하면 Exception이 발생할 때마다 트랜잭션이 롤백된다.
4. 중첩된 트랜잭션
중첩된 트랜잭션에서 내부 트랜잭션이 롤백되면 외부 트랜잭션도 롤백될 수 있다. 이는 트랜잭션 전파(Propagation) 설정에 따라 다르다. 기본 전파 설정인 REQUIRED는 내부 트랜잭션이 롤백되면 외부 트랜잭션도 롤백되도록 한다.
해결 방법
1. 예외 처리
트랜잭션 내에서 발생할 수 있는 예외를 적절히 처리한다. 필요에 따라 예외를 잡아서 롤백되지 않도록 할 수 있다. 예를 들어, 특정 예외를 잡아서 로그를 남기고 트랜잭션을 계속 진행할 수 있다.
@Transactional
public void shibaHolic() {
try {
// 비즈니스 로직
} catch (RuntimeException e) {
// 예외 처리
// 필요에 따라 롤백 방지
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
2. 트랜잭션 속성 조정
@Transactional 어노테이션의 속성을 조정하여 롤백 규칙을 설정할 수 있다. 예를 들어, 특정 예외가 발생해도 롤백되지 않도록 설정할 수 있다.
@Transactional(noRollbackFor = RuntimeException.class)
public void shibaHolic() {
// 비즈니스 로직
}
3. 명시적 롤백 확인
명시적 롤백은 코드에서 TransactionStatus.setRollbackOnly() 메서드를 호출하여 트랜잭션을 롤백 상태로 설정하는 경우를 말한다. 이 메서드는 트랜잭션을 롤백 상태로 표시하며, 이후에 트랜잭션을 커밋하려고 하면 자동으로 롤백된다.
@Service
public class ShibaHolicService {
@Autowired
private ShibaRepository shibaRepository;
@Transactional
public void shibaHolic() {
try {
// 비즈니스 로직
shibaRepository.save(new ShibaHolic());
// 특정 조건에서 명시적 롤백 설정
if (someCondition) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
} catch (RuntimeException e) {
// 예외 처리
// 필요에 따라 롤백 방지
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
위 예제에서 someCondition이 참인 경우, setRollbackOnly() 메서드를 호출하여 트랜잭션을 롤백 상태로 설정한다. 이 경우 트랜잭션은 커밋되지 않고 롤백된다. 명시적 롤백 설정이 불필요한 경우 해당 코드를 제거하거나 조건을 수정해야 한다.
4. 로그 확인
로그를 통해 트랜잭션이 롤백된 원인을 파악할 수 있다. 예외 메시지나 스택 트레이스를 통해 어떤 예외가 발생했는지, 어떤 코드에서 문제가 발생했는지 확인할 수 있다.
@Service
public class ShibaHolicService {
@Autowired
private ShibaRepository shibaRepository;
private static final Logger logger = LoggerFactory.getLogger(ShibaHolicService.class);
@Transactional
public void shibaHolic() {
try {
// 비즈니스 로직
shibaRepository.save(new ShibaHolic());
} catch (RuntimeException e) {
// 예외 발생 시 로그 기록
logger.error("Exception ", e);
// 트랜잭션 롤백 설정
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
위 예제에서 RuntimeException이 발생하면 로그를 기록하고 트랜잭션을 롤백 상태로 설정한다. 로그를 통해 예외 메시지와 스택 트레이스를 확인할 수 있다. 이를 통해 어떤 예외가 발생했는지, 어떤 코드에서 문제가 발생했는지 파악할 수 있다.
최종적으로 문제가 해결된 코드 예시
@Service
public class ShibaHolicService {
@Autowired
private ShibaRepository shibaRepository;
@Transactional
public void shibaHolic() {
try {
// 비즈니스 로직
shibaRepository.save(new ShibaHolic());
} catch (RuntimeException e) {
// 예외 처리
// 필요에 따라 롤백 방지
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
위 예제에서 shibaHolic() 메서드는 트랜잭션 내에서 실행되며, RuntimeException이 발생하면 기본적으로 트랜잭션이 롤백된다. 예외를 잡아서 처리하면 롤백을 방지할 수 있다.
댓글