JPA 트랜잭션 관리와 @Transactional의 동작 방식
1. 트랜잭션(Transaction)이란?
트랜잭션은 데이터베이스의 작업 단위로, 여러 개의 연산이 하나의 논리적인 작업으로 처리되는 것을 의미합니다. JPA는 트랜잭션을 통해 데이터의 일관성을 유지하고, 실패 시 롤백을 수행하는 기능을 제공합니다.
트랜잭션의 기본 원칙은 ACID(Atomicity, Consistency, Isolation, Durability)로 정리할 수 있습니다.
- Atomicity(원자성): 트랜잭션 내의 작업이 모두 완료되거나, 하나라도 실패하면 전체가 롤백됨
- Consistency(일관성): 트랜잭션이 실행되면 데이터는 항상 일관된 상태를 유지해야 함
- Isolation(고립성): 각 트랜잭션은 독립적으로 실행되어야 함
- Durability(지속성): 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 반영됨
2. JPA에서 트랜잭션 관리
JPA에서는 EntityManager를 통해 트랜잭션을 관리할 수 있습니다. 하지만 Spring Boot에서는 @Transactional을 사용하여 트랜잭션을 더욱 편리하게 관리할 수 있습니다.
(1) EntityManager를 이용한 트랜잭션 관리
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin(); // 트랜잭션 시작
Member member = new Member("John");
em.persist(member); // INSERT 실행 예정 (쓰기 지연)
tx.commit(); // 트랜잭션 커밋 (INSERT 실행)
} catch (Exception e) {
tx.rollback(); // 예외 발생 시 롤백
} finally {
em.close();
}
위와 같이 EntityTransaction을 사용하여 수동으로 트랜잭션을 관리할 수도 있지만, Spring에서는 @Transactional을 활용하여 더 간편하게 관리할 수 있습니다.
3. @Transactional의 동작 방식
(1) @Transactional 기본 사용법
Spring에서는 @Transactional을 사용하면 자동으로 트랜잭션을 시작하고, 예외 발생 시 롤백하도록 처리됩니다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Transactional
public void saveMember(String name) {
Member member = new Member(name);
memberRepository.save(member);
}
}
(2) @Transactional의 주요 기능
- @Transactional이 선언된 메서드가 실행되면 트랜잭션이 자동으로 시작됨
- 메서드가 정상 종료되면 트랜잭션이 커밋(commit) 됨
- 메서드에서 예외가 발생하면 자동으로 롤백(rollback) 됨
4. 트랜잭션 전파 옵션 (Propagation)
@Transactional은 트랜잭션이 어떻게 동작할지를 결정하는 전파(Propagation) 옵션을 제공하며, 기본값은 REQUIRED입니다.
전파 옵션설명
REQUIRED | 기존 트랜잭션이 있으면 참여, 없으면 새 트랜잭션 생성 (기본값) |
REQUIRES_NEW | 항상 새로운 트랜잭션을 생성하고 기존 트랜잭션을 일시 중단 |
MANDATORY | 반드시 기존 트랜잭션이 있어야 하며, 없으면 예외 발생 |
SUPPORTS | 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행 |
NOT_SUPPORTED | 기존 트랜잭션이 있어도 중단하고 트랜잭션 없이 실행 |
NEVER | 트랜잭션 없이 실행하며, 기존 트랜잭션이 있으면 예외 발생 |
전파 옵션 예제
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createOrder() {
Order order = new Order();
orderRepository.save(order);
}
위 코드는 REQUIRES_NEW 옵션을 사용하여 항상 새로운 트랜잭션을 생성합니다.
5. 트랜잭션 롤백 설정
Spring의 @Transactional은 기본적으로 체크 예외(Checked Exception)는 롤백하지 않고, 실행 예외(Runtime Exception)만 롤백합니다.
(1) 기본 롤백 동작
@Transactional
public void process() throws Exception {
throw new Exception("체크 예외"); // 롤백되지 않음
}
위 예제는 체크 예외(Exception)가 발생하지만 롤백되지 않습니다.
(2) 특정 예외 롤백 설정
@Transactional(rollbackFor = Exception.class)
public void process() throws Exception {
throw new Exception("체크 예외"); // 롤백됨
}
rollbackFor = Exception.class 옵션을 추가하면 체크 예외도 롤백 가능하게 설정할 수 있습니다.
6. @Transactional의 주의점
(1) 프록시 기반 동작으로 인한 내부 호출 문제
Spring의 @Transactional은 프록시 기반으로 동작하기 때문에 같은 클래스 내부에서 @Transactional이 적용된 메서드를 호출하면 트랜잭션이 제대로 적용되지 않을 수 있습니다.
@Service
public class MemberService {
@Transactional
public void outerMethod() {
innerMethod(); // 트랜잭션이 적용되지 않음
}
@Transactional
public void innerMethod() {
// 실제로 트랜잭션이 적용되지 않음
}
}
해결 방법으로 트랜잭션을 관리하는 별도의 클래스로 분리하거나, AOP 프록시를 활용하여 해결할 수 있습니다.
(2) 읽기 전용 트랜잭션 설정
읽기 전용 트랜잭션을 설정하면 Hibernate의 성능 최적화를 활용할 수 있습니다.
@Transactional(readOnly = true)
public List<Member> findAllMembers() {
return memberRepository.findAll();
}
readOnly = true를 설정하면 변경 감지가 비활성화되고, 불필요한 쓰기 지연 저장소 기능이 동작하지 않아서 성능이 최적화됩니다.
7. 결론
- JPA에서는 EntityManager를 통해 트랜잭션을 관리할 수 있지만, Spring에서는 @Transactional을 사용하여 더욱 편리하게 트랜잭션을 관리할 수 있습니다.
- 전파 옵션(Propagation)을 활용하여 트랜잭션의 흐름을 제어할 수 있으며, 기본적으로 REQUIRED 옵션이 사용됩니다.
- Spring의 @Transactional은 실행 예외(Runtime Exception) 발생 시 자동으로 롤백되며, 체크 예외는 rollbackFor 옵션을 통해 롤백 가능하게 설정할 수 있습니다.