Backend
home
🛋️

@Transactional(readOnly = true)

생성 일시
2025/02/23 10:45
태그
SpringBoot
게시일
2025/02/23
최종 편집 일시
2025/02/23 11:03

1. 트랜잭션의 기본 동작 방식

Spring의 @Transactional은 기본적으로 트랜잭션을 시작하고, 종료 시점에 변경 사항을 감지하여 자동으로 커밋 또는 롤백한다. 하지만 단순 조회(read) 작업에서도 트랜잭션이 적용될 경우 불필요한 오버헤드가 발생할 수 있다.

2. 읽기 전용(readOnly = true)의 장점

JPA의 변경 감지(Dirty Checking) 비활성화

JPA에서는 영속성 컨텍스트(Persistence Context)가 엔티티를 관리하면서 변경 감지(Dirty Checking)를 수행한다. 하지만 단순 조회에서는 변경 감지가 필요하지 않으므로 readOnly = true를 설정하면 JPA가 스냅샷을 저장하지 않도록 최적화된다. 즉, 메모리 사용량과 불필요한 연산이 줄어든다.
적용 전 (Dirty Checking 활성화)
@Transactional public User findById(Long id) { return userRepository.findById(id).orElseThrow(); }
Java
복사
userRepository.findById(id)로 가져온 User 엔티티는 스냅샷으로 저장된다.
만약 조회 후 user.setName(”New Name”)을 실행하면 변경 감지가 작동하여 업데이트 쿼리가 발생할 수 있다.
적용 후 (readOnly = true 설정)
@Transactional(readOnly = true) public User findById(Long id) { return userRepository.findById(id).orElseThrow(); }
Java
복사
JPA가 스냅샷을 저장하지 않으므로 Dirty Checking이 비활성화된다.
성능이 향상되고 불필요한 메모리 사용이 줄어든다.

데이터베이스에 읽기 전용 트랜잭션 적용 (DB 최적화)

일부 데이터베이스(MySQL, PostgreSQL 등) 에서는 readOnly = true 를 설정하면 트랜잭션을 읽기 전용 모드로 실행한다. 이 경우, DB 엔진이 쓰기 관련 리소스를 할당하지 않아 성능이 향상될 수 있다.
MySQL의 경우 SELECT 문을 실행할 때 내부적으로 락(LOCK) 을 걸 수 있는데, readOnly = true 를 설정하면 불필요한 락이 제거될 수 있다.

쓰기 연산 방지 (실수 방지)

readOnly = true 가 설정된 상태에서는 JPA를 통해 엔티티를 수정해도 변경 사항이 반영되지 않는다. 이는 실수로 인해 조회 메서드에서 데이터를 변경하는 문제를 예방할 수 있다.
@Transactional(readOnly = true) public void updateUser(Long id, String newName) { User user = userRepository.findById(id).orElseThrow(); user.setName(newName); // 변경되었지만 DB에 반영되지 않음 }
Java
복사
readOnly = true 를 설정하면 변경 감지가 비활성화되어 UPDATE 쿼리가 실행되지 않는다.

3. 언제 사용?

사용해야 하는 경우
단순 조회 메서드 (findById, findAll, findByUsername 등)
복잡한 조회 쿼리 (DTO 조회, QueryDSL 등)
사용하면 안 되는 경우
데이터를 변경하는 메서드 (save, update, delete 등)
Lazy Loading 후 데이터를 수정해야 하는 경우

4. 정리

설정
변경 감지
DB 최적화
실수 방지
@Transactional
활성화됨
일반 트랜잭션
수정 가능
@Transactional(readOnly = true)
비활성화됨
읽기 전용 최적화
수정 불가