필자는 처음에 @Transactional(readOnly = false)와 @Transactional(readOnly = true)를 기능에 맞게 메서드에 하나씩 붙여주면서 사용하거나 @Transactional(readOnly = false)만 클래스 단위에 붙여서 사용했습니다.

이와 같이 사용하다 보니 데이터 조회 기능과 삽입, 삭제, 수정 기능들이 비즈니스 로직에 복합적으로 구현되어 있어 가독성이 떨어지고 비효율적이라고 느꼈습니다.

 

비효율적으로 느낀 이유는 아래와 같습니다.

  • 조회 기능에 대해서 오류 또는 기능 추가를 하게 될 때 조회 기능에 대한 로직만 읽으면 되는데 삽입, 삭제, 수정에 대한 로직들도 한 번씩 읽어 시간적 비용이 발생한다고 생각했습니다.
  • 조회 기능과 삽입, 삭제, 수정 기능이 한 곳에 모여있으면 하나의 객체가 여러 역할을 한다고 생각하였습니다.

위와 같이 생각이 되어 아래와 같은 방법으로 수정하였습니다.

  • 읽기 전용과 삽입, 삭제, 수정 즉 쓰기 전용에 대해서 비즈니스 로직을 분리하였습니다.

 

비즈니스 로직을 읽기 전용과 쓰기 전용 분리 전 

@Service
@RequiredArgsConstructor
@Transactional
public class TestService {
    private final AccountRepository accountRepository;

	//조회 로직
     public Account getAccount(Long id) {
        return accountRepository.findById(id).get();
    }
    
     //삽입, 삭제, 수정 로직
    public void create(String name,int age) {
       Account account = new Account(name, age);
       accountRepository.save(account);
    }
}

 

 

비즈니스 로직을 읽기 전용과 쓰기 전용 분리 후

 

읽기 전용

@Service
@Transactional(readOnly = true)
public class TestReadService {
    private final AccountRepository accountRepository;
    //조회 로직
    public Account getAccount(Long id) {
        return accountRepository.findById(id).get();
    }
}

 

 

쓰기 전용

@Service
@Transactional
@RequiredArgsConstructor
public class TestReadService {
    private final AccountRepository accountRepository;
    //삽입, 삭제, 수정 로직
    public void create(String name,int age) {
       Account account = new Account(name, age);
       accountRepository.save(account);
    }

}

 

  • @Transactional readOnly의 디폴트값은 false라서 쓰기에 대한 @Transactional의 readOnly에 대한 속성 설정을 생략하였습니다.

 

필자는 단순히 가독성과 조회, 쓰기 역할을 분리하기 위해서 사용을 했었는데 조사를 해보니 @Transactional(readOnly=true)로 하였을 때 성능적으로 이점이 있다는 걸 알게 되었습니다.

 

 

성능상 이점

@Transactional(readOnly=true)로 하였을 때 성능상 이점이 있는 이유는 JPA의 특징인 변경 감지와 관련이 있습니다.

JPA에서 Entity를 조회를 하면 조회한 데이터를 스냅샷으로 저장을 합니다.

조회한 데이터의 정보가 변경을 하고 Commit 하는 시점에 스냅샷과 1차 캐시의 데이터를 확인 후에 동일하지 않을 경우 update 쿼리가 발생합니다.

이때 조회 기능에 대해서 @Transactional(readOnly=true)로 설정을 해놓으면 스프링 프레임워크는 JPA의 플러쉬 모드를 MANUAL로 설정을 합니다.

MANUAL 모드는 트랜잭션 내에서 사용자가 수동으로 flush를 호출하지 않으면 flush가 자동으로 수행되지 않는 모드입니다.

즉 수동으로 flush를 하지 않으면 데이터를 변경하더라도 데이터베이스에 적용되지 않아 예상치 못한 데이터 변경에 대해 방지할 수 있습니다.

그리고 JPA에서 해당 트랜잭션 내에서 조회하는 Entity를 조회용임을 인식하고 변경 감지를 위한 Snapshot을 따로 보관하지 않으므로 메모리가 절약되는 성능상 이점 역시 존재한다.