본문 바로가기
웹개발/spring && springboot

[Spring]트랜잭션(transaction)의 이해와 트랜잭션 분리방법

by 지구별 여행자 임탱 2024. 3. 3.
728x90

스프링 트랜잭션이란?
스프링 트랜잭션은 데이터의 정합성을 보장하기 위해 사용하는 기능으로, 여러 데이터베이스 작업을 하나의 단위로 묶어서 모든 작업이 성공적으로 이루어져야만 최종적으로 데이터베이스에 반영(commit)하게 됩니다. 만약 이 과정 중 오류가 발생하면, 모든 작업을 이전 상태로 되돌리는(rollback) 방식으로 데이터의 안전성을 유지합니다. 

트랜잭션의 특성 (ACID)
 - 원자성(Atomicity): 트랜잭션 내부의 작업들은 모두 하나로 간주되며, 전부 성공하거나 전부 실패해야 합니다.
 -  일관성(Consistency): 트랜잭션이 성공적으로 완료된 후에는, 데이터베이스가 일관된 상태를 유지해야 합니다.
 -  독립성(Isolation): 다른 트랜잭션의 영향을 받지 않고 독립적으로 실행되어야 합니다.
 -  지속성(Durability): 트랜잭션이 완료되면, 그 결과는 영구적으로 반영되어야 합니다. [3]

 

스프링의 트랜잭션 관리 방법
 -  선언적 트랜잭션 관리: @Transactional 어노테이션을 사용하여 간편하게 트랜잭션을 관리합니다. 이 방법을 사용하면 별도의 코드 없이 트랜잭션의 시작과 종료, 예외 처리 등을 스프링 프레임워크가 알아서 처리해줍니다.
 -  프로그래매틱 트랜잭션 관리: 스프링의 트랜잭션 API를 직접 사용하여 프로그램 코드 내에서 트랜잭션을 제어합니다. 이 방법은 세밀한 제어가 필요할 때 유용합니다.
 -  트랜잭션 추상화: 트랜잭션 관리는 데이터베이스 기술과 분리되어 추상화되어 있어, 따라서 한 가지 방법으로 다양한 데이터베이스 기술에 동일하게 트랜잭션을 적용할 수 있습니다.

 

트랜잭션의 예제

@Service
public class BookService {
    
    private final BookRepository bookRepository;
    
    // 생성자를 통한 의존성 주입
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
    
    // @Transactional을 사용하여 해당 메서드를 트랜잭션으로 관리
    @Transactional
    public void updateBookStock(Long bookId, int stock) {
        Book book = bookRepository.findById(bookId)
                     .orElseThrow(() -> new IllegalArgumentException("해당 책이 존재하지 않습니다. id=" + bookId));
        book.setStock(stock); // 책의 재고를 업데이트
        
        // 예외 발생 시, 트랜잭션이 롤백
        if (stock < 0) {
            throw new RuntimeException("재고가 부족합니다.");
        }
    }
}

updateBookStock 메서드를 @Transactional 어노테이션을 이용해 트랜잭션으로 처리하고 있습니다. 재고(stock)가 부족할 경우 RuntimeException을 발생시켜 트랜잭션이 자동으로 롤백되도록 합니다.

 

Spring 트랜잭션 분리
스프링 프레임워크에서 트랜잭션을 분리하는 것은 복잡한 비즈니스 로직을 관리하고, 데이터의 일관성을 유지하는 데 매우 중요합니다. 

트랜잭션 분리 방법
@Transactional 사용: 트랜잭션을 분리하기 위해 @Transactional(propagation = Propagation.REQUIRES_NEW)를 사용하여 새로운 트랜잭션을 시작할 수 있습니다.
클래스 분리 주의: @Transactional이 스프링의 CGLIB 프록시 기반으로 동작하기 때문에, 동일 클래스 내에서는 분리된 트랜잭션으로 인식되지 않을 수 있습니다. 따라서, 분리된 트랜잭션이 필요한 경우에는 메서드를 다른 클래스에 위치시켜야 합니다. 
트랜잭션 전파 옵션: @Transactional 어노테이션 설정 시, Propagation 값에 따라 다른 트랜잭션과 어떻게 상호 작용할지를 결정합니다. 예를 들면, REQUIRES_NEW는 항상 새로운 트랜잭션을 시작하고, MANDATORY는 이미 존재하는 트랜잭션 내에서만 실행되게 합니다.
트랜잭션 범위 최소화: 트랜잭션을 가능한 한 최소한의 범위에 적용하여 더 섬세한 제어와 성능 향상을 도모하세요.


추가 사항
트랜잭션 격리 수준 설정: 데이터베이스의 일관성과 동시성을 관리하기 위해 트랜잭션 격리 수준을 적절히 설정하세요.
영속성 컨텍스트와 트랜잭션: JPA를 사용할 경우, 영속성 컨텍스트가 트랜잭션과 밀접하게 관련되어 있으므로 이를 고려한 설계가 필요합니다.
정확한 트랜잭션 분리는 안정적인 서비스 운영을 위한 기본이므로, 구현 시 꼼꼼하게 고민하고 테스트를 충분히 거친 후 적용하는 것이 좋습니다.

@Service
public class BookService {
    
    private final BookRepository bookRepository;
    private final NotificationService notificationService;

    @Transactional
    public void addBookAndNotify(Book book) {
        addBook(book);
        notifyAddition(book);
    }

    // 별도의 트랜잭션에서 책 추가 기능을 수행
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addBook(Book book) {
        bookRepository.save(book);
    }
    
    // 트랜잭션 분리로 알림 기능은 기본 트랜잭션과는 독립적으로 수행
    public void notifyAddition(Book book) {
        notificationService.notifyAddition(book);
    }
}

위의 예제에서 addBookAndNotify 메서드는 책을 추가하고, 그 사실을 알리는 두 가지 작업을 수행합니다. addBook 메서드는 @Transactional(propagation = Propagation.REQUIRES_NEW) 어노테이션을 사용하여 무조건 새로운 트랜잭션을 생성하고, notifyAddition 메서드는 기본 트랜잭션과 독립적으로 수행되도록 구성되어 있습니다. 이렇게 분리함으로써 두 작업의 성공/실패가 서로 영향을 주지 않으며, 필요 시 재사용성과 가독성도 높아집니다.