Spring Boot

[Spring Boot] 트랜잭션(Transaction)

Tech박조롱 2024. 7. 19. 14:11

 

트랜잭션이란

Spring Boot에서 트랜잭션은 데이터베이스 작업을 하나의 작업 단위로 묶어 처리하는 것을 의미한다. 트랜잭션을 사용하면 일련의 데이터베이스 작입이 모두 성공적으로 완료되거나, 어느 하나라도 실패하면 모두 취소되는 것을 보장할 수 있다. 이는 데이터 무결성과 일관성을 유지하는데 매우 중요하다.

Spring Boot에서 트랜잭션을 사용하려면 @transactional 어노테이션을 활용한다. @Transactional 어노테이션은 클래스나 메소드에 적용하여 해당 범위 내의 데이터베이스 작업이 트랜잭션으로 처리되도록 한다.

 

트랜잭션 특징

  1. 원자성 : 트랜잭션의 모든 연산이 모두 반영되거나, 모두 반영되지 않아야 한다.
  2. 일관성 : 트랜잭션이 성공적으로 완료되면, DB는 일관성 있는 상태를 유지한다.
  3. 격리성 : 여러 트랜잭션이 동시에 수행되더라도 각 트랜잭션은 독립적으로 실행되어야 한다.
  4. 지속성 : 트랜잭션이 성공적으로 커밋되면 그 결과는 영구적으로 DB에 저장된다.

트랜잭션 설정

기본 트랜잭션

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 기본적인 트랜잭션 설정. 이 메서드가 호출되면 트랜잭션이 시작되고,
     * 메서드가 정상적으로 완료되면 트랜잭션이 커밋.
     * 예외가 발생하면 트랜잭션이 롤백.
     */
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }
}

 

트랜잭션 전파

import org.springframework.transaction.annotation.Propagation;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 트랜잭션 전파 속성을 REQUIRED로 설정.
     * 현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고,
     * 없으면 새로운 트랜잭션을 생성 (기본값).
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        userRepository.save(user);
    }

    /**
     * 트랜잭션 전파 속성을 REQUIRES_NEW로 설정.
     * 항상 새로운 트랜잭션을 생성.
     * 기존 트랜잭션이 있으면 일시 중지.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createNewUser(User user) {
        userRepository.save(user);
    }
}

 

트랜잭션 전파 옵션

  1. REQUIRED : 현재 트랜잭션이 존재하면 사용, 없으면 새 트랜잭션을 생성 (기본값)
  2. REQUIRES_NEW : 항상 새 트랜잭션을 생성. 기존 트랜잭션이 있으면 일시 중지
  3. NESTED : 중첩 트랜잭션을 생성. 기존 트랜잭션이 없으면 새 트랜잭션을 생성

 

트랜잭션 격리 수준

import org.springframework.transaction.annotation.Isolation;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 트랜잭션 격리 수준을 READ_COMMITTED로 설정.
     * 다른 트랜잭션이 커밋한 변경 사항만 읽을 수 있다.
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void processUser(User user) {
        userRepository.save(user);
    }

    /**
     * 트랜잭션 격리 수준을 SERIALIZABLE로 설정.
     * 가장 높은 격리 수준으로, 완전한 트랜잭션 직렬화를 보장.
     */
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void processCriticalUser(User user) {
        userRepository.save(user);
    }
}

 

트랜잭션 격리 수준 옵션

  1. READ_UNCOMMITTED : 다른 트랜잭션이 커밋되지 않은 변경 사항을 읽을 수 있다.
  2. READ_COMITTED : 다른 트랜잭션이 커밋한 변경 사항만 읽을 수 있다.
  3. REPEATABLE_READ : 트랜잭션이 시작된 시점부터 커밋된 변경 사항만 읽을 수 있다.
  4. SERIALIZABLE : 가장 높은 격리 수준으로, 완전한 트랜잭션 직렬화를 보장한다.

트랜잭션 시간 제한 및 읽기 전용 설정

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 트랜잭션 시간 제한을 5초로 설정.
     * 5초 안에 트랜잭션이 완료되지 않으면 롤백.
     */
    @Transactional(timeout = 5)
    public void performTimeSensitiveOperation(User user) {
        userRepository.save(user);
        // 추가적인 데이터베이스 작업 수행
    }

    /**
     * 트랜잭션을 읽기 전용으로 설정.
     * 데이터베이스에 변경을 하지 않을 시 사용하여 성능을 최적화.
     */
    @Transactional(readOnly = true)
    public User getUser(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
}

 

트랜잭션 롤백 조건 설정

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 특정 예외가 발생하면 롤백되도록 설정.
     * IllegalArgumentException이 발생하면 트랜잭션을 롤백.
     */
    @Transactional(rollbackFor = IllegalArgumentException.class)
    public void createUserWithRollback(User user) {
        userRepository.save(user);
        // 강제로 예외를 발생시켜 롤백 테스트
        if (user.getName() == null) {
            throw new IllegalArgumentException("User name cannot be null");
        }
    }

    /**
     * 특정 예외를 무시하고 커밋되도록 설정.
     * IllegalArgumentException이 발생해도 롤백되지 않습니다.
     */
    @Transactional(notRollbackFor = IllegalArgumentException.class)
    public void createUserWithoutRollback(User user) {
        userRepository.save(user);
        // 강제로 예외를 발생시키지만 롤백되지 않음
        if (user.getName() == null) {
            throw new IllegalArgumentException("User name cannot be null");
        }
    }
}

 

커스텀 트랜잭션 관리

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PlatformTransactionManager transactionManager;

    /**
     * Programmatic 트랜잭션 관리. 직접 트랜잭션을 제어하는 방법.
     */
    public void programmaticTransactionManagement(User user) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            userRepository.save(user);
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}