1. 트랜잭션의 기본 동작 방식
Spring의 @Transactional은 기본적으로 트랜잭션을 시작하고, 종료 시점에 변경 사항을 감지하여 자동으로 커밋 또는 롤백한다. 하지만 단순 조회(read) 작업에서도 트랜잭션이 적용될 경우 불필요한 오버헤드가 발생할 수 있다.
2. 읽기 전용(readOnly = true)의 장점
JPA의 변경 감지(Dirty Checking) 비활성화
JPA에서는 영속성 컨텍스트(Persistence Context)가 엔티티를 관리하면서 변경 감지(Dirty Checking)를 수행한다. 하지만 단순 조회에서는 변경 감지가 필요하지 않으므로 readOnly = true를 설정하면 JPA가 스냅샷을 저장하지 않도록 최적화된다. 즉, 메모리 사용량과 불필요한 연산이 줄어든다.
@Transactional
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
Java
복사
•
userRepository.findById(id)로 가져온 User 엔티티는 스냅샷으로 저장된다.
•
만약 조회 후 user.setName(”New Name”)을 실행하면 변경 감지가 작동하여 업데이트 쿼리가 발생할 수 있다.
@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) |