ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @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 됩니다.

     

     

    내부 동작 방식

    1. @Transactional이 붙은 메서드가 호출되면 Spring이 프록시 객체를 통해 트랜잭션 시작
    2. 실제 로직 실행
    3. 성공 시 → commit
    4. 예외 발생 시 → rollback
    5. 트랜잭션 종료

    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 로딩 지원

     

     

     

     

     

     

     

Designed by Tistory.