개발 | 프로젝트/Java | Spring

[Spring boot] @Transactional 이란?

seulll 2025. 4. 8. 17:16

1. 트랜잭션이란?

트랜잭션은 데이터베이스의 상태를 변환시키는 하나의 논리적 작업 단위를 말합니다. 쇼핑몰에서 주문을 처리할 때, 주문 정보 저장, 재고 감소, 결제 처리가 모두 성공해야만 전체 주문이 완료됩니다. 이 세 가지 작업을 하나의 트랜잭션으로 묶어서 처리하면, 중간에 문제가 생겼을 때 모든 작업을 원래대로 되돌릴 수 있습니다.

트랜잭션은 다음 네 가지 특성(ACID)을 가집니다.

  • 원자성(Atomicity): 트랜잭션 내의 모든 작업은 전부 성공하거나 전부 실패합니다.
  • 일관성(Consistency): 트랜잭션이 완료된 후에도 데이터베이스는 일관된 상태를 유지해야 합니다.
  • 격리성(Isolation): 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않습니다.
  • 지속성(Durability): 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 반영됩니다.

 

2. @Transactional 기본 개념

@Transactional은 말 그대로 트랜잭션(거래)의 경계를 설정해주는 어노테이션입니다.
하나의 작업 단위에서 모든 처리가 성공해야만 커밋되고, 하나라도 실패하면 롤백되어야 하는 경우에 사용합니다.

 

스프링에서는 트랜잭션을 관리하는 두 가지 방법이 있습니다:

  1. 프로그래밍 방식: 직접 코드로 트랜잭션을 시작, 커밋, 롤백하는 방식
  2. 선언적 방식: @Transactional 어노테이션을 사용하는 방식

대부분의 경우 @Transactional 어노테이션을 사용하는 선언적 방식이 더 간편하고 코드도 깔끔해집니다.

 

기본 사용법

 
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 이 메서드 내의 모든 데이터베이스 작업은 하나의 트랜잭션으로 처리됩니다.
        // 중간에 예외가 발생하면 모든 변경사항이 롤백됩니다.
    }
}
  • 클래스에 적용: 해당 클래스의 모든 public 메서드에 트랜잭션이 적용됩니다.
  • 메서드에 적용: 해당 메서드만 트랜잭션으로 처리됩니다.

 

3. 주요 속성 알아보기

@Transactional에는 다양한 속성이 있어 트랜잭션 동작을 세밀하게 제어할 수 있습니다.

대표적인 속성들

 
@Transactional(
    propagation = Propagation.REQUIRED,  // 트랜잭션 전파 방식
    isolation = Isolation.DEFAULT,       // 격리 수준
    timeout = 30,                        // 타임아웃(초)
    readOnly = false,                    // 읽기 전용 여부
    rollbackFor = Exception.class,       // 롤백을 발생시킬 예외
    noRollbackFor = NotFoundException.class // 롤백을 발생시키지 않을 예외
)

timeout

트랜잭션의 제한 시간을 초 단위로 설정합니다. 지정한 시간이 지나면 트랜잭션이 자동으로 롤백됩니다.

 

readOnly

트랜잭션을 읽기 전용으로 설정합니다. 이 설정은 다음과 같은 이점이 있습니다:

  • 데이터베이스에 읽기 전용이라는 힌트를 제공해 최적화 가능
  • JPA/Hibernate의 경우 더티 체킹(변경 감지)을 하지 않아 성능 향상
 

rollbackFor / noRollbackFor

기본적으로 스프링은 RuntimeException과 Error가 발생했을 때만 롤백합니다. 하지만 이 속성들을 사용하면 롤백 조건을 직접 지정할 수 있습니다.

 

 

4. @Transactional의 내부 동작 원리

1. 프록시 기반 AOP(관점 지향 프로그래밍)

스프링의 @Transactional은 프록시 기반 AOP를 통해 동작합니다.

  • 스프링이 @Transactional이 붙은 클래스에 대해 프록시 객체를 생성
  • 클라이언트가 메서드를 호출하면 실제 객체가 아닌 프록시 객체가 호출을 가로채서 트랜잭션을 처리함

AOP란?

AOP는 공통 관심 사항(cross-cutting concerns)을 핵심 로직과 분리해서 코드를 깔끔하게 유지할 수 있도록 도와주는 프로그래밍 패러다임입니다.

예: 로깅, 보안 체크, 트랜잭션 처리

 

2. 트랜잭션 처리 흐름

@Transactional이 적용된 메서드가 호출되면 다음과 같은 순서로 처리됩니다.

[호출] → [트랜잭션 시작] → [원본 메서드 실행] → [정상: 커밋 / 예외: 롤백] → [종료]

 

  • 트랜잭션 경계 설정: 프록시는 PlatformTransactionManager를 통해 트랜잭션을 시작합니다.
  • 트랜잭션 속성 적용: @Transactional에 지정된 전파 방식, 격리 수준 등의 속성을 적용합니다.
  • 원본 메서드 호출: 실제 비즈니스 로직이 담긴 원본 메서드를 실행합니다.
  • 예외 처리 및 커밋/롤백 결정:
    • 메서드가 정상적으로 완료되면 트랜잭션을 커밋합니다.
    • 런타임 예외(RuntimeException)가 발생하면 트랜잭션을 롤백합니다.
    • checked 예외(Exception)는 기본적으로 롤백하지 않습니다(rollbackFor 속성으로 변경 가능).

 

1) 트랜잭션 선언 (@Transactional)

개발자가 @Transactional 어노테이션을 메서드나 클래스에 붙이면
스프링은 프록시(Proxy) 객체를 생성하여 원래 객체를 감싸게 됩니다.

@Service
public class UserService {
  
  @Transactional
  public void registerUser(User user) {
    userRepository.save(user);
    logRepository.save(new Log("User created"));
  }
}

 

2) 프록시가 메서드 호출 감지

사용자가 registerUser()를 호출하면 실제 객체가 아니라 프록시 객체가 호출을 가로채 다음 작업을 합니다:

  1. 트랜잭션이 이미 존재하는지 확인
  2. 없다면 새 트랜잭션 시작
  3. 대상 메서드 실행
  4. 정상 종료 → 커밋 / 예외 발생 → 롤백

 

3) 트랜잭션 경계 설정

스프링은 PlatformTransactionManager를 통해 트랜잭션을 실제로 관리합니다.
JPA를 사용할 경우 → JpaTransactionManager가 자동으로 등록됨.

TransactionStatus status = transactionManager.getTransaction(...);
try {
    // 비즈니스 로직 수행
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
}