
데이터베이스 트랜잭션을 처리하는 스프링 어노테이션 중 하나이다. 메서드나 클래스에 이 어노테이션을 붙이면, 해당 메서드/클래스 실행 과정에서 트랜잭션을 보장받을 수 있다.
트랜잭션이란 2개 이상의 작업이 작동할 때, "반드시 모든 작업이 성공하거나 모든 작업이 실패해야 하는" 것을 말하는 개념이다. 만약 그들 중 하나라도 실패하면, 롤백하는 것이 원칙이다. 예를 들어 트랜잭션이 적용된 계좌이체 클래스 내부에 출금 메서드와 입금 메서드가 있다고 할 때, 만일 출금은 됐는데 입금이 실패하면 롤백하는 것이 트랜잭션의 정상 작동이라고 할 수 있다.
트랜잭션에는 4가지 특성이 있는데,
이렇게 된다. 알기 쉽게 코드로 보면
@Transactional
public void signup() {
userRepository.save(user); // 회원가입
pointRepository.save(point); // 포인트 지급
couponRepository.save(coupon); // 쿠폰 지급
}
signup 메서드는 @Transactional이 붙어 있기 때문에, 내부의 세 로직 중 하나라도 실패하면 전부 취소된다.
1. 테스트 할 때
@Autowired
private QuestionService questionService;
@Test
@Transactional
void testJpa() {
for (int i = 1; i <= 300; i++) {
String subject = String.format("테스트 데이터입니다: [%03d]", i);
String content = "내용무";
this.questionService.create(subject, content);
}
}
필자가 이렇게 코드를 짜놓고, 더미 데이터가 생기지 않아서 당황했던 기억이 있다. 기존 코드를 재활용하는 과정에서 @Transactional을 지우지 않아서 생긴 문제로, 테스트 환경에서 트랜잭션을 적용하면 해당 메서드가 통과된 후 "자동으로 롤백" 하는 기능을 활성화한다.
2. 읽기 전용 메서드 만들 때
@Transactional(readOnly = true)
public List<Question> getList() {
return questionRepository.findAll(); // 읽기만 하는 작업
}
해당 메서드가 read 작업만 하는 경우, readOnly = true 옵션을 붙어서 어노테이션을 붙여두면 불필요한 변경 감지 기능이 비활성화되어 성능 최적화 및 실수로 데이터 변경하는 것을 방지할 수 있다.
3. 기존 트랜잭션과 독립적으로 동작할 필요가 있을 때
예시) 포인트 시스템
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
// 1. 결제 처리 (메인 트랜잭션)
paymentRepository.save(payment);
// 2. 포인트 적립 (독립 트랜잭션)
try {
pointService.addPoints(payment.getUser(), payment.getAmount());
} catch (Exception e) {
// 포인트 적립 실패해도 결제는 완료
log.error("포인트 적립 실패", e);
}
}
}
@Service
public class PointService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addPoints(User user, int amount) {
// 포인트 적립 로직
// 실패해도 결제 트랜잭션은 영향 없음
}
}
이 코드에서, addPoints 메서드가 실패하더라도, 즉 포인트 적립에 실패하더라도 결제 처리는 동작해야 한다. 하지만 processPayment에 @Transactional이 붙어있는 상태이기 때문에, 이 상태로는 addPoints 메서드가 실패하면 결제까지 취소되는 상황이 발생한다. 따라서 addPoints 메서드에 propagation = Propagation.REQUIRES_NEW 옵션을 붙여주므로써 독립적인 트랜잭션을 새로 만들어서 기존 트랜잭션에 영향을 주지 않게 구현할 수 있다.
어렵누... 지금 단계에서 드는 생각은 여러 가지 비즈니스 로직이 담긴 메서드에 @Transactional을 붙여 놓고, 예외적인 사항들만 따로 빼는 식으로 구현하는 건가 싶다. 처음부터 설계하고 들어가긴 힘들 것 같고 테스트하면서 중간중간 추가해야 할 듯...!