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이 적용되지 않는 경우
-
같은 클래스 내에서
@Transactional메서드를 호출하는 경우@Transactional은 프록시 기반으로 동작하므로, 같은 클래스 내에서 다른 메서드를 호출하면 적용되지 않음.
@Service public class OrderService { @Transactional public void outerMethod() { innerMethod(); // @Transactional 적용되지 않음 } @Transactional public void innerMethod() { // 트랜잭션이 적용되지 않음 } }해결 방법:
self-invocation을 막기 위해TransactionalAspect를 사용하거나, 클래스를 분리. -
프록시 객체를 직접 사용하지 않는 경우
@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을 사용하는 것이 더 간편하고 유지보수하기 좋음.
✅ 정리
@Transactional을 사용하면 자동으로 트랜잭션을 관리하고, 예외 발생 시 롤백됨.- **조회 성능 최적화를 위해
@Transactional(readOnly = true)**를 사용할 수 있음. - 체크 예외(Checked Exception)도 롤백하려면
rollbackFor = Exception.class추가. - Propagation 옵션을 활용하여 트랜잭션 전파 방식 조절 가능.
- 같은 클래스 내에서 호출하면
@Transactional이 적용되지 않음.