-
@Transactional에 대해서Jpa 2025. 7. 30. 16:03
서론
JPA와 Spring을 함께 사용하다 보면 @Transactional을 붙이는 경우가 많습니다.
“왜 조회는 @Transactional 없이도 잘 되는데, 저장이나 삭제는 안 되는 걸까?”
“readOnly 옵션을 붙이면 성능이 좋아진다고 하는데, 진짜 체감할 정도일까?”이런 궁금증이 생기면서 트랜잭션의 개념과 동작 원리를 하나하나 정리해 봤습니다.
이 글은 제가 공부하며 기록한 내용을 바탕으로 @Transactional의 사용 목적, flush와 준영속 상태까지 이어지는 핵심 내용을 정리한 글입니다.
@Transactional이란?
@Transactional은 Spring에서 트랜잭션 범위를 지정하기 위한 애노테이션입니다.
이 애노테이션이 붙은 메서드나 클래스는 트랜잭션 안에서 실행되며,
정상적으로 종료되면 commit, 예외가 발생하면 rollback이 됩니다.@Transactional public void saveUser(User user) { userRepository.save(user); }위 메서드는 트랜잭션 안에서 실행되며, 예외가 없으면 commit, 예외가 발생하면 rollback 됩니다.
내부 동작 방식
- @Transactional이 붙은 메서드가 호출되면 Spring이 프록시 객체를 통해 트랜잭션 시작
- 실제 로직 실행
- 성공 시 → commit
- 예외 발생 시 → rollback
- 트랜잭션 종료
Spring은 기본적으로 런타임 예외가 발생할 때만 rollback합니다.
체크 예외는 rollback 대상이 아닙니다.트랜잭션이 왜 필요한가?
JPA에서는 엔티티를 저장하거나 수정할 때 실제로는 즉시 DB에 반영되지 않습니다.
영속성 컨텍스트라는 1차 캐시에만 반영되고, DB와의 동기화는 나중에 일어납니다.이 DB와의 동기화 시점을 결정하는 것이 flush입니다.
그리고 그 flush를 언제 발생시킬지 결정하는 트랜잭션(commit)입니다.flush와 commit의 관계
flush란?
flush = 영속성 컨텍스트의 변경 내용을 SQL로 변환하여 DB에 반영하는 작업
em.persist(member); // INSERT 쿼리 아직 실행 안 됨 em.flush(); // 여기서 쿼리 실행됨 (DB 반영)하지만 flush는 커밋이 아닙니다.
DB에 반영되더라도 트랜잭션이 커밋되지 않으면, 나중에 롤백될 수 있습니다.flush 발생 시점
- em.flush() 직접 호출
- 트랜잭션 commit 직전
- JPQL, Criteria 쿼리 실행 직전
flush vs commit
flush SQL을 DB에 전송하여 반영 commit 트랜잭션을 영구 반영 rollback flush된 SQL도 모두 무효 처리 즉, @Transactional은 flush 시점을 제어하고, 커밋 여부를 결정하는 중요한 역할을 합니다.
@Transactional를 언제 사용해야 할까?
-> 쓰기 작업(INSERT, UPDATE, DELETE)
JPA에서는 모든 쓰기 작업은 반드시 트랜잭션 안에서 이루어져야 합니다.
트랜잭션이 없으면 변경사항이 DB에 반영되지 않습니다.@Transactional public void deleteUser(Long id) { User user = userRepository.findById(id).orElseThrow(); userRepository.delete(user); // 트랜잭션 없으면 실제 DB에서 삭제되지 않음 }조회에는 왜 @Transactional 없이도 작동할까?
단순한 조회(SELECT)는 트랜잭션 없이도 작동합니다.
이유는 다음과 같습니다.- SELECT는 DB에 변경사항을 반영하지 않음
- JPA는 필요할 때 커넥션을 열고 SELECT를 날림
- 커밋이나 flush 없이도 결과를 가져올 수 있음
public User getUser(Long id) { return userRepository.findById(id).orElseThrow(); // 트랜잭션 없이도 조회됨 }즉, 단순 조회는 트랜잭션 없이도 Connection만 있으면 실행할 수 있기 때문에 @Transactional이 없어도 문제가 되지 않습니다.
그런데 왜 조회에도 @Transactional(readOnly = true)를 붙일까?
실무에서는 조회에도 아래와 같이 @Transactional(readOnly = true)를 자주 붙입니다.
@Transactional(readOnly = true) public User getUser(Long id) { return userRepository.findById(id).orElseThrow(); }이유 1. 성능 최적화
- readOnly = true 설정 시 Hibernate는 더티 체킹(dirty checking)을 하지 않음
- 불필요한 flush를 막고, 트랜잭션 중 insert/update 등을 방지
- → 성능 향상 및 예기치 않은 쓰기 방지
이유 2. 지연 로딩(Lazy Loading) 오류 방지
- 지연 로딩된 연관 엔티티를 사용하는 경우, 트랜잭션이 없으면 LazyInitializationException 발생 가능
- 트랜잭션이 있으면 영속성 컨텍스트가 유지되기 때문에 안정적으로 Lazy 로딩 가능
이유 3. 트랜잭션 전파 일관성
- 서비스 계층의 모든 메서드가 트랜잭션 내에서 동작하면 일관된 트랜잭션 경계를 유지할 수 있음
정리
@Transactional 트랜잭션 범위를 설정, 커밋/롤백 관리 쓰기 작업에 필수 INSERT, UPDATE, DELETE는 트랜잭션 없으면 DB에 반영 안 됨 조회에 생략 가능 SELECT는 flush/commit 없이도 가능 readOnly = true의 장점 더티 체킹 생략, 성능 최적화, Lazy 로딩 지원 'Jpa' 카테고리의 다른 글
JPA 양방향 연관관계, 왜 편의 메서드를 써야 할까? (3) 2025.07.30 Spring Data JPA에서 동적 검색 해결: 사용자 정의 리포지토리와 Querydsl의 도입기 (3) 2025.07.21 연관관계 엔티티 직렬화 오류와 해결 (DTO 패턴) (6) 2025.07.07