[JPA] @Transactional(readOnly = true)의 효과와 사용 시 주의할 점

기존 프로젝트에서 @Transactional으로 설정해 준 서비스에 readOnly 속성을 사용하지 않고 있었는데 데이터를 읽기만 하는 서비스에 readOnly = true를 적용해 읽기 전용 모드로 설정해 주는 것이 좋을 것 같다는 이야기를 듣고 읽기 전용으로 설정해 주면 어떤 효과를 얻을 수 있는지 알아봤다.

@Transactional(readOnly = true)


Spring에서 @Transactional 어노테이션을 사용할 수 있는데 트랜잭션을 readOnly = true로 설정해 주면 읽기 전용 모드로 변경할 수 있다. 그러면 @Transactional(readOnly = true) 속성을 적용함으로써 얻을 수 있는 이점에 대해 알아보자.

 

우선 @Transactional(readOnly = true)은 DB에서 데이터를 읽기만 하는 서비스 메서드에 적용을 해야 한다. readOnly에서도 알 수 있듯이 읽기 전용으로 변경하는 것이기 때문에 데이터를 수정하는 서비스에 적용하면 안된다.

 

성능 최적화

트랜잭션을 읽기 전용으로 설정하면 해당 메서드가 데이터를 읽기만 한다는 것을 DB에 알려줌으로써 쿼리 및 캐싱을 최적화할 수 있다.

그리고 읽기 전용으로 설정하면 데이터 변경이 일어나지 않기 때문에 변경감지를 위한 스냅샷을 저장하는 동작 또한 하지 않기 때문에 성능이 향상되는 것을 기대할 수 있다.

 

데이터 일관성

일반적으로 트랜잭션을 사용해서 DB에서 데이터의 일관성과 무결성을 보장하는 데 사용하는데 트랜잭션을 읽기 전용으로 설정하면 실수로 데이터를 수정해서 일관성을 위반할 가능성이 낮아진다.

 

가독성 향상

코드를 작성하는 개발자는 @Transactional(readOnly=true)이 설정된 메서드가 DB에서 데이터를 읽기만 한다는 것을 명확하게 확인할 수 있다. 이로 인해 코드의 가독성이 향상이 된다.

 

 

@Transactional(readOnly=true)을 사용할 때 고려해야 할 


읽기 작업만 하는 모든 메서드에 @Transactional(readOnly=true)을 무지성으로 사용해서는 안된다.  

 

readOnly = true를 적용할 때 Optimistic Lock의 동작에 영향을 미칠 수 있다는 것을 고려해야 한다.

JPA의 동시성 제어에 접근 방식이 두 가지 존재하는데 그중 하나가 Optimistic Lock이고 또 다른 하나가 Pessimistic Lock이다.

 

Optimistic Lock이란? 

낙관적 락(Optimistic Lock)은 두 개의 트랜잭션이 동시에 동일한 데이터를 수정하려고 시도할 때 발생하는 충돌을 방지하는 데 사용되는 메커니즘인데, 트랜잭션의 대부분은 충돌이 발생하지 않는다고 낙관적으로 가정하는 방법이다.

 

낙관적 락을 사용하려면 Entity에 @Version 어노테이션을 추가해서 사용할 수 있다. 

 

@Entity
public class Post {
    @GeneratedValue
    @Id
    private Long id;

    @Version
    private Long version;
    
    // Constructor, Getter ...
}

 

@Version 어노테이션을 추가하면 이제 트랜잭션이 엔티티를 수정할 때마다, 현재 버전 번호가 자동으로 업데이트되며 기록이 된다. 다른 트랜잭션이 동일한 엔티티를 수정하려고 시도하면 버전 번호를 확인하는데 이때 첫 번째 트랜잭션이 수정을 했다고 가정하면 두 번째 트랜잭션의 버전 번호는 조회한 시점의 버전과 수정한 시점의 버전이 다르다는 것을 확인한다. 이때 충돌이 발생했다는 것을 두 번째 트랜잭션에 알리게 된다.

 

즉, 데이터를 조회한 시점의 버전과 수정하려고 할 때 버전이 일치하지 않으면 충돌이 발생한 것으로 간주하고 예외가 발생한다.

 

@Transactional(readOnly=true)를 사용하면 이러한 낙관적 락 동작에 영향을 미치게 된다.

만일 @Transactional(readOnly=true)로 설정한 메서드에 엔티티를 수정하는 로직이 있을 경우, 해당 트랜잭션이 엔티티를 수정하는 것이 아니라 읽기 전용으로 설정했기 때문에 버전 번호를 확인하지 못할 수 있다. 이때 충돌을 감지하지 못하고 동시에 발생한 트랜잭션의 변경 사항을 덮어쓰게 되어 데이터 불일치 문제가 발생할 수 있다.

 

예를 들어 트랜잭션이 엔터티를 읽고 수정한 뒤, 다른 트랜잭션이 수정하려고 시도하는 경우 낙관적 락 충돌을 감지하지 않고, 수정된 엔터티는 DB에 저장되어 다른 동시 트랜잭션의 변경 사항을 덮어쓰게 된다.

 

그래서 낙관적 락이 활성화된 엔티티는 @Transactional(readOnly=true)로 설정된 메서드에서 엔티티를 읽기 작업만 하도록 하고, 수정하지 않도록 조심해야 한다.

 

정리

정리하면 @Transactional(readOnly=true)를 사용하면 해당 트랜잭션이 데이터를 읽기만 하도록 설정하여 애플리케이션의 데이터 일관성과 성능을 향상할 수 있다.