Transaction과 @Transactional

leedong617·2024년 10월 4일
post-thumbnail

트랜잭션(Transaction)**과 @Transactional 어노테이션은 데이터베이스의 일관성을 보장하고, 원자성을 유지하기 위해 중요한 역할을 함. 트랜잭션은 데이터베이스 작업을 하나의 단위로 처리하며, 모든 작업이 성공적으로 완료되거나, 그렇지 않으면 모두 롤백되도록 함. Spring에서 @Transactional 어노테이션은 트랜잭션을 선언적으로 관리할 수 있게 해주는 도구임.

트랜잭션(Transaction)이란?

트랜잭션은 데이터베이스에서 일련의 작업이 성공 또는 실패로 끝나야 하는 단위 작업을 의미함. 즉, 트랜잭션 내의 모든 작업이 성공해야만 커밋되고, 하나라도 실패하면 롤백되어 데이터베이스가 원래 상태로 돌아감. 이로 인해 데이터의 일관성과 무결성을 보장할 수 있음.

트랜잭션의 ACID 특성

트랜잭션은 ACID(Atomicity, Consistency, Isolation, Durability)라는 4가지 특성을 가짐

  • Atomicity(원자성): 트랜잭션 내의 모든 작업이 완전히 성공하거나 아예 실패해야 함.
  • Consistency(일관성): 트랜잭션 전후의 데이터 상태가 일관된 상태로 유지되어야 함.
  • Isolation(고립성): 각각의 트랜잭션은 서로 독립적으로 실행되어야 하며, 동시에 실행되더라도 상호 간섭이 없어야 함.
  • Durability(지속성): 트랜잭션이 성공적으로 커밋된 후의 데이터 변경 사항은 영구적으로 저장되어야 함.

Spring의 @Transactional

@Transactional 어노테이션은 Spring에서 트랜잭션을 선언적으로 관리하기 위한 방법임. 이 어노테이션을 사용하면, 트랜잭션이 자동으로 시작되고 종료되며, 트랜잭션 내에서 발생하는 예외에 따라 커밋 또는 롤백이 자동으로 처리됨.

@Transactional의 주요 기능

트랜잭션을 자동으로 시작하고, 메소드가 성공적으로 종료되면 커밋함.
메소드 실행 중 런타임 예외(비체크 예외, unchecked exception)가 발생하면 자동으로 롤백함.
체크 예외(checked exception)가 발생할 경우에는 기본적으로 롤백되지 않음. 필요하다면 설정을 통해 롤백하도록 지정할 수 있음.

@Transactional 어노테이션 사용 예시

@Transactional 어노테이션은 클래스 레벨 또는 메소드 레벨에서 선언할 수 있음. 해당 어노테이션이 적용된 범위에서 트랜잭션이 처리됨.

메소드 레벨에서 사용

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        // 이 메소드 내의 모든 작업이 트랜잭션 내에서 처리됨
    }
}

@Transactional이 붙은 registerUser 메소드에서 발생하는 모든 데이터베이스 작업은 하나의 트랜잭션으로 처리됨.
런타임 예외가 발생하면 롤백되고, 성공적으로 완료되면 커밋됨.

클래스 레벨에서 사용

@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void registerUser(User user) {
        userRepository.save(user);
    }

    public void updateUser(User user) {
        userRepository.save(user);
    }
}

클래스 레벨에 @Transactional을 적용하면, 해당 클래스의 모든 메소드가 트랜잭션 관리하에 실행됨.

@Transactional의 주요 속성

@Transactional은 다양한 속성을 제공하여 트랜잭션의 동작을 제어할 수 있음.

propagation (전파 옵션)

트랜잭션의 전파 행동을 정의함. 즉, 트랜잭션이 이미 진행 중일 때 새롭게 호출된 메소드가 새 트랜잭션을 시작할지 또는 기존 트랜잭션을 사용할지 결정함.
주요 옵션:

  • REQUIRED (기본값): 기존 트랜잭션이 있으면 이를 사용하고, 없으면 새 트랜잭션을 시작함.
  • REQUIRES_NEW: 항상 새 트랜잭션을 시작함. 기존 트랜잭션이 있더라도 이를 일시 중지하고 새 트랜잭션에서 처리함.
  • MANDATORY: 반드시 기존 트랜잭션이 존재해야 함. 없으면 예외가 발생함.
  • SUPPORTS: 트랜잭션이 있으면 트랜잭션 내에서 실행하고, 없으면 트랜잭션 없이 실행함.
  • NOT_SUPPORTED: 트랜잭션을 무시하고 트랜잭션 없이 실행함.
  • NEVER: 트랜잭션이 있으면 예외가 발생함.
  • NESTED: 중첩된 트랜잭션을 시작함.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUser(User user) {
    userRepository.save(user);  // 항상 새 트랜잭션에서 처리
}

isolation (격리 수준)

트랜잭션에서 격리 수준을 설정함. 이는 동시성 문제를 처리하는 방법을 결정함.
주요 옵션:

  • DEFAULT: 데이터베이스의 기본 격리 수준을 따름.
  • READ_UNCOMMITTED: 다른 트랜잭션에서 커밋되지 않은 변경사항도 읽을 수 있음 (가장 낮은 격리 수준).
  • READ_COMMITTED: 다른 트랜잭션에서 커밋된 데이터만 읽을 수 있음.
  • REPEATABLE_READ: 트랜잭션 내에서 같은 데이터를 여러 번 조회해도 항상 동일한 결과를 보장함.
  • SERIALIZABLE: 가장 엄격한 격리 수준으로, 트랜잭션 간에 완전한 격리를 보장함. 성능에 영향이 큼.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateUser(User user) {
    userRepository.save(user);  // REPEATABLE_READ 격리 수준에서 실행
}

timeout (제한 시간)

트랜잭션이 지정된 시간 안에 완료되지 않으면 롤백되도록 설정함.
기본값은 -1로, 시간 제한이 없음을 의미함.

@Transactional(timeout = 5)  // 5초 안에 트랜잭션이 완료되지 않으면 롤백
public void saveUser(User user) {
    userRepository.save(user);
}

readOnly (읽기 전용)

트랜잭션을 읽기 전용 모드로 설정하여, 조회 작업에만 사용하고 데이터 변경 작업은 금지함.
성능 최적화를 위해, 데이터베이스에서 읽기 전용 트랜잭션일 때 쓰기 잠금 등의 불필요한 리소스를 줄임.

@Transactional(readOnly = true)
public List<User> getUsers() {
    return userRepository.findAll();  // 데이터 변경 불가, 조회만 가능
}

rollbackFor / noRollbackFor (롤백 조건)

특정 예외 발생 시 트랜잭션을 롤백하도록 설정할 수 있음.
기본적으로는 체크 예외가 발생하면 롤백되지 않고, 런타임 예외가 발생하면 롤백됨.

  • rollbackFor: 지정한 예외가 발생하면 트랜잭션을 롤백함.
  • noRollbackFor: 지정한 예외가 발생해도 롤백하지 않음.
@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) throws Exception {
    userRepository.save(user);
    if (user.getName() == null) {
        throw new Exception("Name is required");  // 이 경우 롤백됨
    }
}

트랜잭션 롤백 시 주의사항

롤백은 런타임 예외(unchecked exception)가 발생할 때만 자동으로 처리됨.
체크 예외는 기본적으로 롤백되지 않으므로, rollbackFor 속성을 사용하여 롤백 조건을 명시적으로 설정해야 함.
트랜잭션 경계 밖에서 예외가 발생하거나, 트랜잭션이 정상 종료되기 전에 강제로 flush가 호출되면, 트랜잭션이 올바르게 처리되지 않을 수 있음.

결론

  • 트랜잭션은 데이터 무결성을 유지하기 위한 중요한 개념이며, Spring의 @Transactional 어노테이션을 사용하면 선언적으로 트랜잭션 관리를 할 수 있어 코드가 간결해지고 유지보수성이 높아짐.
  • @Transactional은 트랜잭션의 전파, 격리 수준, 롤백 조건, 타임아웃 등을 설정할 수 있어 매우 유연한 트랜잭션 관리가 가능함.
  • 이를 올바르게 사용하면 데이터베이스의 일관성을 유지하고, 안전한 데이터 처리가 가능해짐.
profile
웹개발자 취업 준비생

0개의 댓글