Back-End/JPA

JPA 트랜잭션 관리와 @Transactional의 동작 방식

봉의일상 2025. 3. 9. 22:44

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 옵션을 통해 롤백 가능하게 설정할 수 있습니다.