Transaction
트랜잭션은 일련의 작업들을 하나로 묶어서 처리하는 단위이다.
트랜잭션이 성공적으로 완료되면 모든 작업이 영구적으로 반영되고, 하나라도 실패하면 모든 작업이 취소된다.
데이터의 일관성과 무결성을 유지하는 중요한 개념이다.
트랜잭션의 특징 (ACID)
-
Atomicity 원자성
한 트랜잭션 내의 모든 연산들이 완전히 수행되거나 전혀 수행되지 않는다. ( All or Nothing ) -
Consistency 일관성
어떤 트랜잭션이 수행되기 전에 데이터베이스가 일관된 상태를 가졌다면 트랜잭션이 수행된 후에 일관된 상태를 갖는다. -
Isolation 격리성 or 고립
여러 트랜잭션이 동시에 실행될 때 각 트랜잭션은 다른 트랜잭션의 영향을 받지않고 독립적으로 실행되어야 하며, 중간 상태가 다른 트랜잭션에 노출되지 않아야 한다. -
Durability 영속성
트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 저장되어야 하며, 시스템 오류가 발생하더라도 데이터가 손실되지 않아야 한다.
트랜잭션의 개념은 많이 들어서 알고 있었지만 내가 완전하게 이해하기는 어려웠다.
콘서트 티켓 예매를 예로 들어보자
상품을 구매하는 과정을 살펴보자.
- 상품의 재고를 확인한 후 장바구니에 추가합니다.
- 상품을 결제합니다.
- 결제가 성공하면 주문을 생성하고 재고를 업데이트합니다.
이 과정에서 트랜잭션을 사용하지 않으면 어떤 문제가 발생할까?
재고가 부족한 상황에서 동시에 여러 사용자가 같은 상품을 구매하려고 하면 재고가 음수가 될 수 있다.
또한 결제가 성공했지만 주문 생성이 실패하거나 재고 업데이트가 실패하면 나는 결제만 하고 물건은 받지 못할 수 있다.
트랜잭션을 사용하면 여러 단계의 구매 과정을 하나의 단위로 묶어서 처리할 수 있다.
모든 단계가 성공적으로 완료되면 변경 사항이 데이터베이스에 반영되지만, 도중에 실패하는 경우 모든 변경 사항이 롤백되어 데이터의 무결성과 일관성을 보장해야 한다.
스프링에서는 @Transactional 어노테이션을 사용하여 트랜잭션을 관리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
public class OrderService {
// 코드 생략
@Transactional
public void orderProcess(Long productId, int quantity, PaymentInfo paymentInfo) {
Product product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
// 재고 확인
if (product.getStock() < quantity) {
throw new RuntimeException("Not enough stock");
}
// 결제 처리
paymentService.processPayment(paymentInfo);
// 재고 감소
product.setStock(product.getStock() - quantity);
productRepository.save(product);
// 주문 생성
Order order = new Order();
order.setProduct(product);
order.setQuantity(quantity);
order.setTotalPrice(product.getPrice() * quantity);
orderRepository.save(order);
}
}
이 어노테이션은 트랜잭션의 시작과 종료를 자동으로 처리하며, 메서드 내의 모든 데이터베이스 연산을 하나의 트랜잭션으로 묶어준다. 클래스나 메서드에 붙여 해당 범위 내 메서드가 트랜잭션이 되도록 보장한다.
영속성 컨텍스트(Persistence Context)
영속성 컨텍스트는 엔티티(Entity) 객체를 관리하는 환경
주로 ORM 프레임워크에서 사용되며, 데이터베이스로부터 엔티티를 읽어올 때 영속성 컨텍스트에 저장하고, 엔티티의 상태 변경을 추적하여 데이터베이스에 동기화하는 기능을 제공한다.
-
엔티티의 생명주기
영속성 컨텍스트는 엔티티의 생명주기를 관리한다.
엔티티 객체가 생성되어 영속성 컨텍스트에 저장되면 ‘영속 상태’가 되며, 데이터베이스와 동기화된다.
수정된 엔티티는 트랜잭션이 커밋될 때 자동으로 데이터베이스에 반영된다. -
지연 로딩(Lazy Loading)
영속성 컨텍스트는 지연 로딩을 지원하여, 연관된 엔티티 객체를 실제로 사용할 때까지 데이터베이스 조회를 지연시킬 수 있다. -
동일성 보장
영속성 컨텍스트는 동일한 엔티티 식별자를 가진 객체에 대해 같은 인스턴스를 반환하여 동일성을 보장한다.
트랜잭션과 영속성 컨텍스트의 상호작용
트랜잭션과 영속성 컨텍스트는 서로 긴밀하게 연결되어 있다.
- 트랜잭션 범위:
- 트랜잭션은 데이터베이스 작업을 논리적 단위로 묶어주며, 트랜잭션 내에서 영속성 컨텍스트는 엔티티의 상태 변경을 추적한다.
- 트랜잭션 커밋 시 영속성 컨텍스트는 데이터베이스에 변경 사항을 반영하고, 롤백 시 변경 사항을 취소한다.
- 트랜잭션의 격리 수준:
- 트랜잭션 격리 수준(Transaction Isolation Level)은 여러 트랜잭션이 동시에 실행될 때의 동작을 제어한다.
- 높은 격리 수준일수록 동시성 문제를 줄일 수 있지만, 성능 저하가 발생할 수 있다.
- 데이터 일관성:
- 영속성 컨텍스트 내의 엔티티는 트랜잭션 동안 데이터베이스와의 일관성을 유지하고, 트랜잭션이 커밋될 때 영속성 컨텍스트의 변경 사항이 데이터베이스에 반영된다.
- 플러시(Flushing):
- 영속성 컨텍스트의 변경 사항을 데이터베이스에 동기화하는 작업
- 일반적으로 트랜잭션 커밋 시 자동으로 플러시가 발생한다.
즉, 영속성 컨텍스트는 엔티티의 생명주기를 관리하고, 트랜잭션은 데이터베이스 작업을 단위로 묶어 원자적인 실행을 보장한다.
트랜잭션의 활용
데이터베이스 상태 변경
예를 들어, 사용자가 새로운 주문을 생성할 때, 주문 내역을 데이터베이스에 반영하는 작업은 하나의 트랜잭션으로 묶인다.
이 경우 주문 생성, 결제 정보 저장, 재고 조정 등 여러 개의 데이터베이스 작업이 하나의 원자적인 작업 단위로 묶이게 된다.
예외 처리와 롤백
트랜잭션은 예외 상황이 발생했을 때 데이터베이스 상태를 롤백하여 이전 상태로 복원한다.
예를 들어, 주문 생성 중 결제 과정에서 오류가 발생하면, 주문과 관련된 모든 변경 사항을 취소하고 이전 상태로 돌아가야 한다.
동시성 제어
트랜잭션은 동시에 여러 사용자가 동일한 데이터에 접근할 때 발생할 수 있는 문제를 방지한다.
여러 사용자가 동시에 주문을 생성 할 때, 트랜잭션을 이용하여 충돌을 방지하고 일관성 있는 데이터 처리를 보장해야 한다.
복잡한 비즈니스 로직 처리
여러 개의 서비스 호출이나 데이터 조작 작업을 하나의 트랜잭션으로 묶어서 실행하면, 데이터 일관성을 유지하면서 원자적으로 처리해야 한다.
권한 관리 및 보안
트랜잭션을 시작할 때 권한을 확인하고, 트랜잭션이 종료될 때 권한을 제거하는 등의 작업을 통해 보안을 강화한다.