Akashic Records

Spring Data JPA - @Transactional 본문

Spring.io

Spring Data JPA - @Transactional

Andrew's Akashic Records 2024. 11. 26. 15:09
728x90

Spring Data JPA(Spring Data Java Persistence API)

@Transactional

@Transactional은 Spring 프레임워크에서 제공하는 어노테이션으로, 트랜잭션 경계를 정의하고 관리하는 데 사용됩니다. 데이터베이스와의 상호작용을 포함한 메소드 실행 중 오류가 발생하면 자동으로 롤백(rollback)을 수행하여 데이터 일관성을 유지합니다. 이 어노테이션은 데이터 접근 로직을 단순화하고, 트랜잭션 관리에 필요한 반복적인 작업을 줄여주는 역할을 합니다.

아래에서는 @Transactional 어노테이션에 대해 자세히 설명하고, 주요 사용 방법 및 주의사항들을 다룹니다.

  • @Transactional은 Spring에서 트랜잭션 경계를 정의하고 관리하는 데 사용하는 어노테이션으로, 데이터 무결성을 유지하는 데 중요한 역할을 합니다.
  • 트랜잭션 전파 유형, 격리 수준, 타임아웃, 읽기 전용 등 다양한 속성을 통해 트랜잭션의 동작을 제어할 수 있습니다.
  • Spring의 AOP 기반으로 동작하므로 메소드 간 호출, 예외 관리, 접근 제어 등에 주의가 필요합니다.
  • 적절히 사용하면 트랜잭션 관리를 자동화하고 코드의 복잡성을 줄일 수 있지만, 설정이나 내부 동작 방식에 대한 깊은 이해가 필요합니다.

1. @Transactional의 주요 기능

@Transactional은 트랜잭션 관리를 위해 다음과 같은 기능을 제공합니다:

  1. 트랜잭션 경계 설정:
    • 해당 메소드나 클래스에 트랜잭션을 적용하여 데이터 작업이 모두 성공적으로 수행되면 커밋(commit)되고, 오류가 발생하면 롤백(rollback)됩니다.
  2. 롤백 자동화:
    • 메소드 실행 중 런타임 예외(RuntimeException) 또는 체크되지 않은 예외가 발생하면 트랜잭션을 자동으로 롤백합니다. 일반적으로 이러한 방식으로 데이터의 무결성을 유지합니다.
  3. 트랜잭션 전파(propagation):
    • 기존의 트랜잭션이 있는지 여부와 관련하여 새로운 트랜잭션을 시작할지, 기존 트랜잭션을 사용할지 등을 설정할 수 있습니다. 이를 통해 다른 메소드 간의 트랜잭션 동작을 제어할 수 있습니다.
  4. 트랜잭션 격리 수준(isolation level):
    • 동시성 제어를 위해 데이터베이스의 트랜잭션 격리 수준을 지정할 수 있습니다. 이를 통해 여러 사용자가 동일한 데이터를 동시에 액세스할 때 발생할 수 있는 문제를 방지합니다.

2. @Transactional 적용 범위

  • 메소드 레벨:
    • 특정 메소드에만 트랜잭션을 적용할 때 사용합니다. 이 경우 해당 메소드가 호출될 때 트랜잭션이 시작되고, 메소드가 종료될 때 커밋되거나 롤백됩니다.
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }
  • 클래스 레벨:
    • 클래스 전체에 대해 트랜잭션을 적용합니다. 이 경우 해당 클래스의 모든 public 메소드가 트랜잭션 내에서 실행됩니다.
    @Transactional
    public class UserService {
        public void updateUser(User user) {
            userRepository.save(user);
        }
    
        public void deleteUser(Long userId) {
            userRepository.deleteById(userId);
        }
    }

3. 주요 속성

@Transactional에는 트랜잭션 동작을 제어하기 위한 여러 속성들이 있습니다:

  1. propagation (전파 유형):
    • 트랜잭션 전파 유형을 설정하여 메소드가 호출되는 상황에서 트랜잭션을 어떻게 처리할지 결정합니다.
    • 주요 전파 유형:
      • REQUIRED (기본값): 기존 트랜잭션이 있으면 그 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작합니다.
      • REQUIRES_NEW: 항상 새로운 트랜잭션을 시작하며, 기존 트랜잭션이 있어도 일시 중지됩니다.
      • SUPPORTS: 트랜잭션이 있는 경우 참여하고, 없으면 트랜잭션 없이 실행합니다.
      • MANDATORY: 반드시 기존 트랜잭션이 있어야 합니다. 없으면 예외가 발생합니다.
      • NEVER: 트랜잭션 없이 실행하며, 기존 트랜잭션이 있는 경우 예외를 발생시킵니다.
      • NESTED: 기존 트랜잭션 내에서 중첩된 트랜잭션을 생성합니다.
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void performNewTransaction() {
        // 항상 새로운 트랜잭션 내에서 실행됩니다.
    }
  2. isolation (격리 수준):
    • 트랜잭션의 격리 수준을 정의하여, 동시에 여러 트랜잭션이 실행될 때 데이터 무결성을 유지하는 방법을 설정합니다.
    • 주요 격리 수준:
      • READ_UNCOMMITTED: 다른 트랜잭션에서 아직 커밋되지 않은 데이터를 읽을 수 있습니다.
      • READ_COMMITTED: 다른 트랜잭션에서 커밋된 데이터만 읽을 수 있습니다.
      • REPEATABLE_READ: 동일한 트랜잭션 내에서 동일한 쿼리를 여러 번 실행해도 항상 동일한 결과를 보장합니다.
      • SERIALIZABLE: 가장 높은 수준의 격리로, 트랜잭션 간에 완전한 격리를 보장합니다.
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void performWithHighIsolation() {
        // SERIALIZABLE 격리 수준으로 실행
    }
  3. timeout (타임아웃):
    • 트랜잭션의 최대 실행 시간을 초 단위로 설정합니다. 설정된 시간 안에 완료되지 않으면 트랜잭션은 자동으로 롤백됩니다.
    @Transactional(timeout = 5)
    public void performWithTimeout() {
        // 5초 안에 완료되지 않으면 롤백됨
    }
  4. readOnly (읽기 전용):
    • 트랜잭션을 읽기 전용으로 설정합니다. 이 설정은 데이터 읽기만 수행하고 변경하지 않을 때 사용됩니다. 이를 통해 성능 최적화를 할 수 있습니다.
    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
  5. rollbackFor  noRollbackFor:
    • 특정 예외에 대해 롤백 여부를 설정합니다.
    • rollbackFor: 지정된 예외가 발생하면 롤백합니다.
    • noRollbackFor: 지정된 예외가 발생하더라도 롤백하지 않고 커밋합니다.
    @Transactional(rollbackFor = CustomException.class)
    public void performWithCustomRollback() throws CustomException {
        // CustomException 발생 시 롤백됨
    }
728x90

4. @Transactional 동작 방식

  • @Transactional은 AOP(Aspect-Oriented Programming)를 사용하여 트랜잭션 경계를 설정합니다. 이로 인해 트랜잭션 경계 설정을 위한 부가기능이 런타임 시점에 프록시 객체에 의해 관리됩니다.
  • 이는 다음과 같은 방식으로 동작합니다:
    1. Spring은 @Transactional이 적용된 메소드를 호출할 때, 먼저 해당 메소드가 트랜잭션 내에서 실행되는지 확인합니다.
    2. 트랜잭션이 없다면 새로운 트랜잭션을 시작하고, 기존 트랜잭션이 있다면 설정된 전파 속성에 따라 기존 트랜잭션에 참여하거나 새로운 트랜잭션을 시작합니다.
    3. 메소드 실행 후 오류가 발생하지 않으면 커밋, 오류가 발생하면 롤백됩니다.

5. 주의사항 및 사용 시 유의점

  1. 프록시 기반 동작:
    • Spring의 @Transactional은 프록시 객체를 통해 동작합니다. 자기 자신 클래스 내에서 트랜잭션 메소드를 호출하면 트랜잭션이 적용되지 않을 수 있습니다. 이는 프록시가 자기 자신에 대한 호출을 가로채지 못하기 때문입니다.
    • 해결 방법으로는 별도의 서비스 클래스에 트랜잭션 메소드를 정의하거나, 자신 내 호출을 피하는 것이 있습니다.
  2. 프록시의 대상은 public 메소드:
    • Spring의 기본 트랜잭션 관리 기법은 public 메소드에 대해서만 트랜잭션을 적용합니다. @Transactional이 적용된 메소드가 public이 아니면 트랜잭션이 적용되지 않습니다.
  3. 체크드 예외와 언체크드 예외:
    • 기본적으로 런타임 예외(RuntimeException)가 발생할 때만 롤백이 자동으로 수행됩니다.
    • 체크드 예외(CheckedException)에 대해 롤백을 원한다면 rollbackFor 속성을 사용하여 롤백을 지정해야 합니다.
  4. 읽기 전용 트랜잭션 최적화:
    • readOnly = true를 설정하면 Hibernate는 SQL을 최적화하고, 플러시와 같은 동작을 피할 수 있어 성능이 개선됩니다. 그러나, 이 설정으로도 데이터베이스 엔진의 동작을 100% 제어할 수 있는 것은 아닙니다.

6. 실용적인 예제

@Service
public class BankService {

    @Autowired
    private AccountRepository accountRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
        Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
        Account toAccount = accountRepository.findById(toAccountId).orElseThrow();

        fromAccount.withdraw(amount);
        toAccount.deposit(amount);

        // 이 코드가 완료되기 전에 문제가 생기면 전체 트랜잭션은 롤백됩니다.
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

위 코드에서 transferMoney() 메소드는 트랜잭션 전파 유형 REQUIRED이므로, 호출될 때 이미 트랜잭션이 있다면 해당 트랜잭션에 참여하고, 없으면 새 트랜잭션을 시작합니다. 만약 중간에 오류가 발생하면 모든 작업은 롤백됩니다.

728x90
Comments