조회 메서드에 readOnly를 붙이면 무엇이 달라지는가
문제 발생
조회 메서드에 @Transactional(readOnly = true)를 붙였습니다. 팀에서 두 가지 오해가 있었습니다.
- "이제 이 메서드에서는 절대 쓰기가 안 된다"
- "붙이기만 하면 어디서든 성능이 좋아진다"
실제로는 다른 서비스에서 호출된 경우 아무 효과가 없었고, 어떤 코드는 readOnly 트랜잭션 안에서도 데이터를 수정했습니다.
원인 분석
readOnly는 금지가 아니라 힌트입니다. Spring 문서의 @Transactional 설정 표는 이 속성을 읽기-쓰기 대 읽기 전용 트랜잭션으로 설명하고, 결정적인 단서를 덧붙입니다 — REQUIRED 또는 REQUIRES_NEW 값에만 적용됩니다.
즉 새 트랜잭션이 실제로 시작될 때만 의미가 있습니다. 이미 읽기-쓰기 트랜잭션이 진행 중인데 REQUIRED로 참여하면, 그 안에서 readOnly = true를 선언해도 기존 트랜잭션의 성격을 바꾸지 못합니다.
또한 이 설정은 트랜잭션 인프라와 영속성 제공자에게 주는 힌트이지, 드라이버 수준에서 쓰기를 막아주는 장치가 아닙니다. JPA에서는 flush 모드를 낮춰 더티 체킹 비용을 줄이는 이득이 실제로 있지만, 그건 제공자가 그 힌트를 활용하기 때문입니다.
해결 방안
- 조회 전용 서비스 메서드에 붙입니다. JPA를 쓴다면 더티 체킹 생략만으로도 목록 조회에서 차이가 납니다.
@Transactional(readOnly = true)
public List<PostSummary> list(int page) { ... }- 클래스에 기본값을 두고 메서드에서 뒤집는 방식이 편합니다. 문서 예제와 같은 구조입니다.
@Transactional(readOnly = true)
public class PostService {
@Transactional
public void publish(long id) { ... } // 쓰기 메서드만 재선언
}-
전파 설정과 함께 이해합니다. 바깥 트랜잭션에 참여하는 경우에는 효과가 없다는 점을 알고, 정말 분리가 필요하면
REQUIRES_NEW를 의도적으로 씁니다. -
쓰기 금지 장치로 오해하지 않습니다. 안전장치가 필요하면 읽기 전용 커넥션이나 복제본(replica) 데이터소스로 라우팅하는 편이 실제 보장을 줍니다.
-
복제본 라우팅과 조합할 때 특히 유용합니다.
readOnly플래그를 데이터소스 선택의 신호로 쓰는 구성이 흔합니다 — 그때는 이 애노테이션이 성능 힌트를 넘어 라우팅 계약이 됩니다. -
효과를 측정합니다. "붙이면 빨라진다"가 아니라, 쿼리 수와 응답 시간이 실제로 달라지는지 확인한 뒤 규칙으로 삼습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.