스프링 트랜잭션이란?
스프링 트랜잭션은 데이터의 정합성을 보장하기 위해 사용하는 기능으로, 여러 데이터베이스 작업을 하나의 단위로 묶어서 모든 작업이 성공적으로 이루어져야만 최종적으로 데이터베이스에 반영(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 메서드는 기본 트랜잭션과 독립적으로 수행되도록 구성되어 있습니다. 이렇게 분리함으로써 두 작업의 성공/실패가 서로 영향을 주지 않으며, 필요 시 재사용성과 가독성도 높아집니다.
'웹개발 > spring && springboot' 카테고리의 다른 글
egov 전자정부프레임워크 (1) | 2024.03.17 |
---|---|
Mybatis db 언더바 사용된 컬럼과 자바 카멜케이스 변수 자동 매핑 (0) | 2024.03.12 |
[스프링]base64 암호화 (0) | 2024.03.01 |
spring AOP에서 예외 처리를 했다면... (0) | 2024.03.01 |
Spring framework 한글깨짐 해결방안 (0) | 2024.03.01 |