@Transactional(readOnly = true) 란?
@Transactional(readOnly = true)는 JPA(Spring Data JPA 포함) 환경에서 읽기 전용 트랜잭션을 선언할 때 사용하는 어노테이션 속성입니다. 이 설정은 단순한 옵션처럼 보일 수 있지만, 실제로는 JPA 내부 동작, JDBC 연결, 성능에 중요한 영향을 미칩니다.
✅ 1. 기본 개념
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userRepository.findAll();
}
위와 같이 선언하면 해당 메서드에서 실행되는 트랜잭션은 쓰기 작업 없이 읽기 작업만 수행하겠다는 의미입니다. Spring은 이를 통해 내부적으로 몇 가지 최적화를 적용합니다.
✅ 2. 내부 동작
JPA 관점
JPA는 트랜잭션 내에서 엔티티를 조회한 후, 변경 사항이 있는지 검사합니다 (Dirty Checking).
readOnly = true를 설정하면, 변경 감지(Dirty Checking)를 비활성화시킵니다.
따라서 EntityManager.flush()도 수행되지 않으며, 쓰기 연산이 방지됩니다.
JDBC 관점
Spring은 JDBC 드라이버에게 Connection.setReadOnly(true)를 요청합니다.
일부 DB에서는 이를 기반으로 쿼리 플랜 최적화가 발생할 수 있습니다.
예: MySQL은 읽기 전용 트랜잭션에 대해 레코드 잠금을 건너뛸 수 있음
단, 실제로 DB 수준에서 무조건 쓰기를 막는 것은 아니며, 개발자의 코드가 쓰기를 시도할 경우 예외가 발생하지는 않습니다.
따라서 이 설정은 개발자의 명시적 선언 및 최적화를 유도하는 역할이 큽니다.
✅ 3. 사용 예시 정리
UserService (읽기)
@Service
public class UserService {
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow();
}
}
UserService (쓰기)
@Transactional
public void updateUser(UserDto dto) {
User user = userRepository.findById(dto.getId()).orElseThrow();
user.setName(dto.getName());
// 변경 감지로 인해 자동 update됨 (flush 수행됨)
}
✅ 4. 결론
@Transactional(readOnly = true)는 단순한 선언처럼 보이지만, JPA 환경에서 다음과 같은 이점을 제공합니다:
불필요한 flush 제거 → 성능 개선
트랜잭션의 의도 명확화 → 코드 가독성 향상
일부 DB에서 쿼리 실행 최적화 가능
즉, 읽기 전용 서비스 로직에는 반드시 사용하는 것이 권장되며, 쓰기 작업이 동반되는 메서드에는 사용하지 말아야 합니다.
✅ 5. DB별 쿼리 최적화
1. MySQL에서는 read only로 선언된 트랜잭션에 대해:
InnoDB 스토리지 엔진이 레코드 잠금을 생략할 수 있습니다.
undo log 생성을 줄일 수 있습니다.
MVCC(다중 버전 동시성 제어) 관련 작업을 줄여 속도를 향상시킬 수 있습니다.
📌 즉, 트랜잭션 격리 수준이 READ COMMITTED 이상인 경우에도 락 오버헤드가 감소합니다.
✅ 참고: MySQL에서는 Connection.setReadOnly(true)를 트랜잭션 시작 시 적용하면, 일부 select 쿼리에서 더 가볍게 동작합니다.
2. Oracle DB는 읽기 전용 트랜잭션에 대해 쿼리 최적화 플랜의 선택에 영향을 줄 수 있습니다.
실행 계획에서 쓰기 관련 준비를 생략하거나, 트랜잭션 격리 레벨이 엄격하더라도 undo log나 redo log 사용을 줄일 수 있습니다.
단, Oracle에서는 **명시적 힌트나 트랜잭션 레벨 read-only 선언(SQL 레벨)**이 필요할 수도 있어 JDBC 수준의 readOnly만으로는 제한적인 경우도 있습니다.
3. PostgreSQL에서는 BEGIN TRANSACTION READ ONLY; 또는 JDBC에서 readOnly 설정 시:
쓰기 시도를 명시적으로 차단할 수 있습니다.
실행 계획에서 “이 트랜잭션은 절대 쓰지 않는다”는 전제가 가능하므로, DB 내부에서는 실행 계획(Planner)이 그에 맞춰 더 공격적인 캐시나 병렬 처리 전략을 선택할 수 있습니다.
'IT 정보 > JPA' 카테고리의 다른 글
Spring Data JPA 쿼리를 작성 방식 (0) | 2025.04.07 |
---|