개요
예전에 개발 중 트랜잭션 처리가 자동으로 되지 않는 문제를 경험한 적이 있었다. 처음에 @Transactional 어노테이션을 사용하면 모든 트랜잭션이 알아서 처리될 것이라고 생각했었다. 하지만 예상대로 트랜잭션이 작동하지 않고, 데이터가 반영되지 않는 문제가 발생해 한참을 헤맸다. 결국, 트랜잭션이 동작하지 않는 다양한 이유를 알게 되었고, 그 해결 방법을 찾게 되었다. 이 글에서는 @Transactional이 왜 동작하지 않는지, 그리고 그 해결법을 예시와 함께 설명해보려고 한다.
@Transactional 기본 개념
@Transactional은 스프링 프레임워크에서 제공하는 어노테이션으로, 데이터베이스 작업에서 트랜잭션을 관리하는 데 사용된다. 트랜잭션이란 작업의 일련의 과정을 하나로 묶어주는 개념으로, 작업이 모두 성공해야만 데이터베이스에 반영된다. 반대로 중간에 실패하면 모든 작업이 원복(rollback)된다. @Transactional을 사용하면 이 과정을 자동으로 처리할 수 있다. 하지만 이 어노테이션이 항상 동작하지 않는 경우가 있어, 그 이유를 잘 이해하는 것이 중요하다.
자주 발생하는 문제 상황
많은 개발자들이 @Transactional을 사용하면서 자주 겪는 문제는 트랜잭션이 기대한 대로 동작하지 않는 것이다. 예를 들어, 데이터가 제대로 커밋되지 않거나, 오류가 발생해도 롤백되지 않는 경우가 있다. 이러한 문제는 종종 @Transactional의 기본적인 동작 원리를 오해하거나 잘못된 설정 때문일 수 있다.
@Transactional이 동작하지 않는 이유와 해결법
1. 프록시 기반 트랜잭션 관리
스프링에서 @Transactional은 기본적으로 프록시를 사용해 동작한다. 이는 클래스나 메서드를 호출할 때 프록시가 트랜잭션을 관리하는 방식이다. 하지만 트랜잭션을 관리하는 프록시가 예상대로 동작하지 않는 경우가 종종 있다. 그 이유 중 하나는 자기 자신을 호출하는 메서드 때문이다. @Transactional이 적용된 메서드가 같은 클래스 내에서 다른 메서드를 호출하면 프록시가 동작하지 않는다.
해결법: 자기 자신을 호출하지 않고, 별도의 서비스로 분리하거나, AOP 구조로 설계하는 것이 필요하다.
@Service
public class MyService {
@Transactional
public void outerMethod() {
// 내부 메서드 호출
innerMethod(); // 트랜잭션이 적용되지 않음
}
@Transactional
public void innerMethod() {
// DB 작업
}
}
위 예시에서 outerMethod()가 내부적으로 innerMethod()를 호출할 때, 트랜잭션이 예상대로 적용되지 않는다. 이 경우, innerMethod()는 프록시를 통해 호출되지 않기 때문이다.
2. 메서드 접근 제어자 문제
@Transactional은 기본적으로 public 메서드에만 동작한다. 따라서 메서드가 private 또는 protected로 선언되어 있으면 트랜잭션이 적용되지 않는다.
해결법: 트랜잭션을 적용할 메서드를 반드시 public으로 선언해야 한다.
@Service
public class MyService {
@Transactional
public void validTransactionMethod() {
// 정상적으로 트랜잭션이 적용됨
}
@Transactional
private void invalidTransactionMethod() {
// 트랜잭션이 적용되지 않음
}
}
3. 읽기 전용 트랜잭션 설정
@Transactional에는 readOnly 속성을 설정할 수 있다. 이를 true로 설정하면 데이터베이스 읽기 전용 작업에 최적화되지만, 쓰기 작업을 시도하면 오류가 발생할 수 있다.
해결법: 데이터 변경이 발생하는 작업에서는 readOnly를 false로 설정하거나, 생략해야 한다.
@Transactional(readOnly = true)
public void readData() {
// 읽기 전용 작업
}
@Transactional
public void writeData() {
// 쓰기 작업
}
4. 예외 처리 문제
기본적으로 @Transactional은 Unchecked Exception(런타임 예외)만 롤백 처리를 한다. 만약 Checked Exception(체크 예외)을 던질 경우, 트랜잭션이 롤백되지 않는다.
해결법: 특정 예외에 대해 롤백이 필요하다면 rollbackFor 속성을 추가해야 한다.
@Transactional(rollbackFor = Exception.class)
public void saveData() throws Exception {
// 체크 예외가 발생해도 롤백 처리됨
}
예시 코드
다음은 @Transactional이 적용된 서비스 클래스의 예시이다.
@Service
public class TransactionalService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
if (user.getName() == null) {
throw new RuntimeException("이름이 null일 수 없습니다."); // 롤백 처리됨
}
}
}
위 코드에서는 @Transactional이 적용되어 있어, 만약 사용자 이름이 null이면 예외가 발생하고 데이터가 롤백된다. 반대로 예외가 발생하지 않으면 데이터가 정상적으로 저장된다.
정리
@Transactional은 스프링 애플리케이션에서 중요한 역할을 하는 어노테이션이다. 하지만 트랜잭션이 동작하지 않는 상황이 발생할 수 있으므로, 그 원인과 해결 방법을 미리 알고 있어야 한다. 자기 자신을 호출하는 문제, 메서드 접근 제어자, 예외 처리 방식 등 다양한 원인이 존재하기 때문에 이를 잘 이해하고 사용하는 것이 중요하다.
'Spring' 카테고리의 다른 글
Spring @RequestBody로 데이터 바인딩 오류 (0) | 2024.10.01 |
---|---|
Spring Boot 애플리케이션 로그 설정하기 (0) | 2024.09.29 |
Spring Boot Actuator 엔드포인트가 작동하지 않는 문제 해결 (0) | 2024.09.28 |
Spring Boot Auto Configuration 작동 원리 (0) | 2024.09.28 |
[스프링 핵심원리 기본]스프링 컨테이너와 스프링 빈 (0) | 2022.01.15 |
댓글