배너 이미지

Spring Boot @Transactional 어노테이션

2025. 4. 22. 23:36·CS공부/Java & Spring

이번 글에서는 스프링 애플리케이션에서 자주 사용되는 어노테이션인
@Transactional의 동작 원리, 실무 이슈인 저장 직후 조회 실패 문제,
그리고 트랜잭션 격리 수준(Isolation Level)을 통한 정합성 제어 전략까지 정리하였다.


@Transactional의 기본 개념

  • 트랜잭션(Transaction): 데이터베이스 작업의 논리적인 단위. 전부 성공하거나, 전부 실패해야 함
  • @Transactional: 해당 메서드를 트랜잭션 범위 내에서 실행되도록 지정
  • 스프링에서는 AOP(프록시) 기반으로 트랜잭션을 적용

클래스 vs 메서드 단위 적용

위치 적용 대상 특징
클래스 모든 public 메서드 전역적인 트랜잭션 관리 가능
메서드 개별 메서드 세밀한 제어 가능, 우선순위 높음

실무 이슈: 저장 직후 조회가 안 되는 이유

상황 예시

@PostMapping("/create-and-get")
public ResponseEntity<?> createAndGet() {
    service.saveSomething();            // 저장
    Entity result = service.findSomething();  // 조회 → 조회 실패
    return ResponseEntity.ok(result);
}
  • @Transactional이 붙은 두 메서드를 같은 컨트롤러 내에서 호출
  • 같은 트랜잭션 안에서 실행되므로 저장이 아직 DB에 flush되지 않음
  • 따라서 조회는 이전 DB 상태를 기준으로 진행됨

해결 방법 ①: 명시적 flush() 호출

@Transactional
public void saveSomething() {
    repository.save(...);
    entityManager.flush(); // DB 반영 강제
}
  • 즉시 DB에 반영되므로 이후 find() 호출 시 정상 조회 가능
  • 단, 여전히 같은 트랜잭션 범위 내임에 유의

해결 방법 ②: 트랜잭션 분리

방법 1: 다른 서비스 빈으로 분리

@Service
public class SaveService {
    @Transactional
    public void saveSomething() { ... }
}

@Service
public class ReadService {
    @Transactional(readOnly = true)
    public Entity findSomething() { ... }
}
  • 컨트롤러에서 두 서비스를 분리 호출 → 다른 트랜잭션에서 실행됨

방법 2: 이벤트 발행 방식 활용

applicationEventPublisher.publishEvent(new SomethingCreatedEvent(...));
  • 저장과 조회를 분리된 흐름으로 구성 가능

해결 방법 ③: @Modifying + flushAutomatically

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update User u set u.status = :status where u.id = :id")
void updateStatus(@Param("id") Long id, @Param("status") String status);

트랜잭션 격리 수준 (Isolation Level)

정의:

트랜잭션이 다른 트랜잭션과 데이터를 얼마나 공유할 수 있는지 결정하는 설정

설정 방법:

@Transactional(isolation = Isolation.SERIALIZABLE)
수준 설명 허용 현상
DEFAULT DB 기본 설정 사용 (보통 READ_COMMITTED) DB에 따라 다름
READ_UNCOMMITTED 커밋되지 않은 데이터도 읽음 Dirty Read 허용
READ_COMMITTED 커밋된 데이터만 읽음 Non-repeatable Read 가능
REPEATABLE_READ 트랜잭션 동안 동일 데이터 보장 Phantom Read 가능
SERIALIZABLE 가장 강한 격리 → 완전한 정합성 없음 (성능 저하)

저장 직후 조회 이슈 vs 격리 수준

문제 상황 격리 수준 효과 실제 해결책
저장 후 즉시 조회 안 됨 영향 없음 (flush 문제) flush() or 트랜잭션 분리
동시에 같은 데이터 수정 효과 있음 (정합성 제어) SERIALIZABLE
Phantom Read 방지 효과 있음 REPEATABLE_READ

Spring Boot 환경 DB별 트랜잭션 격리 수준

DB 기본 트랜잭션 레벨 지원 트랜잭션 레벨 트랜잭션 지원 여부
MySQL REPEATABLE READ READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE ✅ (InnoDB 기준)
PostgreSQL READ COMMITTED READ COMMITTED, REPEATABLE READ, SERIALIZABLE

학습 정리

  • @Transactional은 매우 강력한 도구이지만, 동작 방식(AOP 프록시, flush 타이밍 등)을 명확히 이해해야 한다.
  • 같은 트랜잭션 내에서는 save 직후의 find가 예상대로 동작하지 않을 수 있다.
  • 트랜잭션 격리 수준은 동시성 문제나 정합성 제어에서 매우 유용하지만, flush 타이밍 이슈와는 별개이다.
  • 트랜잭션 설계는 데이터 흐름, 호출 구조, 예외 상황을 모두 고려한 전략적 접근이 필요하다.

'CS공부 > Java & Spring' 카테고리의 다른 글

Call by Reference와 Call by Value  (0) 2025.04.23
Spring 내부 구조 - DispatcherServlet, 요청 흐름, 에러 처리  (0) 2025.04.22
Spring Boot 프로젝트 horong 리팩토링  (1) 2025.04.22
힙 메모리 누수  (0) 2025.04.22
가비지 컬렉션(Garbage Collection)  (0) 2025.04.21
'CS공부/Java & Spring' 카테고리의 다른 글
  • Call by Reference와 Call by Value
  • Spring 내부 구조 - DispatcherServlet, 요청 흐름, 에러 처리
  • Spring Boot 프로젝트 horong 리팩토링
  • 힙 메모리 누수
quokkaST
quokkaST
  • quokkaST
    stquokka
    quokkaST
    • 개발자 (77)
      • n8n (2)
      • CS공부 (46)
        • Java & Spring (15)
        • 인프라 (7)
        • 운영체제 & 시스템 (9)
        • 기타 CS지식 (7)
        • 네트워크 (6)
        • 데이터베이스 (2)
      • 알고리즘 (16)
      • 프로젝트 (8)
        • 감정&금융챗봇 (8)
      • 리팩토링 (5)
        • horong (5)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
상단으로

티스토리툴바