본문으로 건너뛰기
개발 머꼬
개발 노트Spring
hohyeon.dev35

조회 메서드에 readOnly를 붙이면 무엇이 달라지는가

  • #Engineering Note
  • #Spring

문제 발생

조회 메서드에 @Transactional(readOnly = true)를 붙였습니다. 팀에서 두 가지 오해가 있었습니다.

  • "이제 이 메서드에서는 절대 쓰기가 안 된다"
  • "붙이기만 하면 어디서든 성능이 좋아진다"

실제로는 다른 서비스에서 호출된 경우 아무 효과가 없었고, 어떤 코드는 readOnly 트랜잭션 안에서도 데이터를 수정했습니다.

원인 분석

readOnly는 금지가 아니라 힌트입니다. Spring 문서의 @Transactional 설정 표는 이 속성을 읽기-쓰기 대 읽기 전용 트랜잭션으로 설명하고, 결정적인 단서를 덧붙입니다 — REQUIRED 또는 REQUIRES_NEW 값에만 적용됩니다.

새 트랜잭션이 실제로 시작될 때만 의미가 있습니다. 이미 읽기-쓰기 트랜잭션이 진행 중인데 REQUIRED로 참여하면, 그 안에서 readOnly = true를 선언해도 기존 트랜잭션의 성격을 바꾸지 못합니다.

또한 이 설정은 트랜잭션 인프라와 영속성 제공자에게 주는 힌트이지, 드라이버 수준에서 쓰기를 막아주는 장치가 아닙니다. JPA에서는 flush 모드를 낮춰 더티 체킹 비용을 줄이는 이득이 실제로 있지만, 그건 제공자가 그 힌트를 활용하기 때문입니다.

해결 방안

  1. 조회 전용 서비스 메서드에 붙입니다. JPA를 쓴다면 더티 체킹 생략만으로도 목록 조회에서 차이가 납니다.
@Transactional(readOnly = true)
public List<PostSummary> list(int page) { ... }
  1. 클래스에 기본값을 두고 메서드에서 뒤집는 방식이 편합니다. 문서 예제와 같은 구조입니다.
@Transactional(readOnly = true)
public class PostService {
  @Transactional
  public void publish(long id) { ... }     // 쓰기 메서드만 재선언
}
  1. 전파 설정과 함께 이해합니다. 바깥 트랜잭션에 참여하는 경우에는 효과가 없다는 점을 알고, 정말 분리가 필요하면 REQUIRES_NEW를 의도적으로 씁니다.

  2. 쓰기 금지 장치로 오해하지 않습니다. 안전장치가 필요하면 읽기 전용 커넥션이나 복제본(replica) 데이터소스로 라우팅하는 편이 실제 보장을 줍니다.

  3. 복제본 라우팅과 조합할 때 특히 유용합니다. readOnly 플래그를 데이터소스 선택의 신호로 쓰는 구성이 흔합니다 — 그때는 이 애노테이션이 성능 힌트를 넘어 라우팅 계약이 됩니다.

  4. 효과를 측정합니다. "붙이면 빨라진다"가 아니라, 쿼리 수와 응답 시간이 실제로 달라지는지 확인한 뒤 규칙으로 삼습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.