Pink Spider/Spring Boot JPA에서 `@Transactional` 사용법

Created Wed, 26 Mar 2025 11:21:45 +0900 Modified Mon, 08 Dec 2025 08:41:47 +0900
1651 Words 8 min

Spring Boot JPA에서 @Transactional 사용법

Spring Boot JPA에서 @Transactional 어노테이션은 트랜잭션 범위 내에서 데이터베이스 작업을 실행하고, 실패 시 롤백하는 기능을 제공하는 중요한 요소입니다.


1. @Transactional 기본 사용법

Spring의 @Transactional을 사용하면 하나의 메서드에서 여러 개의 JPA 작업(조회, 저장, 수정, 삭제 등)을 수행할 때 트랜잭션을 자동으로 관리할 수 있습니다.

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Transactional
    public void updateMember(Long id, String name) {
        Member member = memberRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Member not found"));

        member.setName(name);  // JPA 영속성 컨텍스트에 의해 자동으로 업데이트됨
    }
}
  • @Transactional을 사용하면, 메서드 실행 중 예외가 발생하면 롤백되며, 성공하면 자동으로 커밋됩니다.
  • member.setName(name);을 호출하면 JPA의 변경 감지(dirty checking) 덕분에 save()를 명시적으로 호출하지 않아도 업데이트됩니다.

2. 읽기 전용 트랜잭션 (@Transactional(readOnly = true))

readOnly = true를 설정하면 성능 최적화를 할 수 있습니다.

  • JPA가 변경 감지를 비활성화하므로, 조회 성능이 개선됩니다.
  • 쓰기 작업이 발생하면 예외가 발생할 수 있습니다.
@Transactional(readOnly = true)
public Member getMember(Long id) {
    return memberRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("Member not found"));
}

readOnly = true를 사용하면 SELECT 쿼리만 수행하는 메서드에서 성능이 최적화됩니다.


3. 예외 발생 시 롤백 (rollbackFor 옵션)

기본적으로 @Transactional은 **RuntimeException 또는 Error**가 발생했을 때만 롤백됩니다.

  • Checked Exception (Exception)이 발생하면 롤백되지 않습니다.

Checked Exception까지 롤백하고 싶다면 rollbackFor 속성을 추가하면 됩니다.

@Transactional(rollbackFor = Exception.class)
public void processTransaction() throws Exception {
    // DB 작업 수행
    throw new Exception("Checked Exception 발생!"); // 기본적으로 롤백되지 않음 (rollbackFor 설정 필요)
}

4. Propagation 옵션 (트랜잭션 전파 방식)

트랜잭션이 존재할 때 새로운 트랜잭션을 만들 것인지, 기존 트랜잭션을 활용할 것인지 설정할 수 있습니다.

Propagation 값 설명
REQUIRED (기본값) 기존 트랜잭션이 있으면 참여, 없으면 새 트랜잭션 생성
REQUIRES_NEW 기존 트랜잭션을 무시하고 항상 새로운 트랜잭션 생성
NESTED 기존 트랜잭션 내에서 중첩된 트랜잭션 실행 (savepoint 생성)
MANDATORY 반드시 기존 트랜잭션이 있어야 실행 (없으면 예외)
SUPPORTS 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행
NOT_SUPPORTED 트랜잭션 없이 실행
NEVER 트랜잭션이 있으면 예외 발생

예제: REQUIRES_NEW 사용

기존 트랜잭션이 있더라도 항상 새로운 트랜잭션을 생성하여 실행합니다.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
    Log log = new Log(message);
    logRepository.save(log);
}

REQUIRES_NEW를 사용하면 부모 트랜잭션이 롤백되더라도, 새로운 트랜잭션이 커밋될 수 있음.


5. @Transactional이 적용되지 않는 경우

  1. 같은 클래스 내에서 @Transactional 메서드를 호출하는 경우

    • @Transactional은 프록시 기반으로 동작하므로, 같은 클래스 내에서 다른 메서드를 호출하면 적용되지 않음.
    @Service
    public class OrderService {
        @Transactional
        public void outerMethod() {
            innerMethod(); // @Transactional 적용되지 않음
        }
    
        @Transactional
        public void innerMethod() {
            // 트랜잭션이 적용되지 않음
        }
    }
    

    해결 방법: self-invocation을 막기 위해 TransactionalAspect를 사용하거나, 클래스를 분리.

  2. 프록시 객체를 직접 사용하지 않는 경우

    • @Transactional은 AOP 기반이므로, 같은 클래스 내에서 메서드를 호출하면 트랜잭션이 적용되지 않음.
    • Spring Bean을 통해 호출해야 적용됨.

6. 트랜잭션과 Lazy Loading

@Transactional을 사용하면 영속성 컨텍스트가 유지되므로 Lazy Fetch가 동작할 수 있습니다.

@Transactional
public void processOrder(Long orderId) {
    Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("Order not found"));

    // Lazy 로딩된 연관 엔티티 접근 (트랜잭션이 유지되므로 가능)
    List<OrderItem> items = order.getOrderItems();
    System.out.println(items.size());
}

트랜잭션이 없으면 LazyInitializationException 발생 가능.


7. @Transactional vs EntityManager

  • @Transactional을 사용하면 Spring이 자동으로 트랜잭션을 관리.
  • EntityManager를 직접 사용하여 트랜잭션을 관리할 수도 있음.
@PersistenceContext
private EntityManager entityManager;

public void manualTransaction() {
    EntityTransaction tx = entityManager.getTransaction();
    try {
        tx.begin();
        // 데이터 처리
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
    }
}

일반적으로 Spring 환경에서는 @Transactional을 사용하는 것이 더 간편하고 유지보수하기 좋음.


✅ 정리

  1. @Transactional을 사용하면 자동으로 트랜잭션을 관리하고, 예외 발생 시 롤백됨.
  2. **조회 성능 최적화를 위해 @Transactional(readOnly = true)**를 사용할 수 있음.
  3. 체크 예외(Checked Exception)도 롤백하려면 rollbackFor = Exception.class 추가.
  4. Propagation 옵션을 활용하여 트랜잭션 전파 방식 조절 가능.
  5. 같은 클래스 내에서 호출하면 @Transactional이 적용되지 않음.