레벨 2의 자동차 미션을 진행하던 중에 Service 레이어에서 @Transational 어노테이션을 사용했다.
어떤 메서드에 대해서 Atomic 하게 처리하기 위해서 사용했는데, 이때 영속 레이어의 읽기 작업만 호출할 때는 추가로 readOnly
옵션을 적용해줬다.
그랬더니…
클래스 스코프에 readOnly 설정을 해주고 쓰기 작업이 필요한 메서드에는 다시 @Transational을 붙여줘서 readOnly를 false로 지정해준 상태였다.
하지만 이것이 정확히 무엇을 하는 지 잘 몰라서 대답할 수가 없었다…
지금부터 알아보자.
DB를 공부할 때 Transaction 이란 개념이 나온다. 이것은 다음과 같은 네가지의 성질을 가진다.
동시적인 접근이 가능한 상황에서 의도치 않은 상황 발생을 막기 위해 사용한다.
스프링에서는 선언적 트랜잭션을 지원하는데, 이것이 바로 @Transactional 어노테이션이다.
클래스, 메서드 단위로 해당 어노테이션을 붙일 수 있다.
클래스 단위로 붙인다면, 해당 클래스의 모든 메서드에 디폴트 설정으로 적용이 된다.
메서드 단위로 붙이면 클래스 단위 적용을 덮어씌울 수 있다.
해당 클래스 또는 메서드에 트랜잭션 기능이 적용된 프록시 객체가 생성되고 사용하게 된다.
PlatformTransactionManager를 사용해서 Commit(성공) 또는 Rollback(실패) 한다.
사실 단순히 어노테이션만으로 완벽한 상황을 보장해주는 것은 아니다.
다수의 Transaction이 동시에 같은 리소스에 접근 하는 경우 Dirty Read, Non-Repeatable Read, Phantom Read 등의 문제가 발생할 수 있다.
이를 위해서 @Transactional 어노테이션에서는 여러가지 옵션을 사용할 수 있는데 이번 글의 주제인 readOnly에 대해 집중해보려고 한다.
우선 문자열 그대로 해당 트랜잭션은 읽기 작업만 하도록 적용하는 것이다.
이를 통해서 얻을 수 있는 이점은 다음과 같다.
만약 프로젝트에서 JPA를 쓴다면 변경 감지 기능에 의해서 flush가 일어나게 된다. 이 기능을 위해서 각 시점의 스냅샷 인스턴스를 메모리에 보관하고, flush 때 스냅샷 비교 등의 무거운 작업을 수행한다.
이때 readOnly = true
상태라면 강제로 플러시를 호출하지 않는 한 플러시가 일어나지 않는다.
또한 스프링 5.1부터는 스냅샷을 저장하지 않아서 메모리 또한 아낄 수 있다.
사실 이 readOnly
설정은 힌트 옵션이다. @Transactional.java를 들어가보면 다음과 같이 쓰여있다.
위의 JPA 내용이 주요 기능은 아니고 트랜잭션을 수행하는 하위 시스템(DB, API 등)에게 힌트를 제공하는 것이다. 만약 DB에서 읽기 Lock과 쓰기 Lock을 따로 관리하는 경우, 의도치 않은 쓰기 작업을 막아주는 등의 역할을 할 수 있다.
하지만…!! 하위 시스템에게 동작을 맡기기 때문에 해당 옵션을 설정했다고 해서 모든 환경에서 쓰기 작업이 실패한다고 보장할 수도 없다.
그럼에도 불구하고 읽기 작업일 경우 해당 옵션을 적용하는 것이 더 좋기 때문에 적용하는 것이다!!