spring / / 2023. 10. 22. 20:24

@Transactional의 readonly 옵션의 중첩 사용

@Transactional(readOnly = true)는 트랜잭션을 사용할 때 읽기 전용으로 사용하겠다는 의미이다.

readOnly로 사용 시 아래와 같은 장점이 있다고 한다.

장점

  • JPA를 사용할 경우 변경감지를 수행하지 않아 성능상 이점을 가져올 수 있다.
  • mysql 이중화 구성(master/slave)를 사용할 경우 readOnly를 사용할 경우 slave를 자동으로 호출해줘서 DB 부하를 줄여줄 수 있다.
  • 읽기 가독성 증가 (의도적으로 읽기 전용으로 사용하겠다고 명시)
  • transactionId를 부여되지 않아 오버헤드를 줄일 수 있다.

여기서 readOnly 사용을 할 때 몇 가지 궁금점이 생겼다.

1) @Transactional(readOnly = true)일 때 수정작업(update)을 하면 어떻게 될까?

2) @Transactional(readOnly=true/false) 이 중첩되어 사용하는 경우는 어떻게 적용될까?

1. @Transactional(readOnly = true)일 때 수정작업(update)을 하면 어떻게 될까?

테스트를 위해 샘플 소스를 작성해보았다.

@Transactional(readOnly = true)
public void changeNameReadOnly(int id, String name) {
    UserJpo userJpo = userRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    userJpo.setName(name); // 수정
}

아래는 테스트 코드이다.

@BeforeEach
void setUp() {
    userService.register(new User(1, "홍길동", "서울"));
}

@Test
void changeNameReadOnly() {
    userService.changeNameReadOnly(1, "홍길동2");
    User user = userService.find(1);
    log.info(user.getName()); // 홍길동
}

@Transactional(readOnly = true)일 때 홍길동 => 홍길동2로 변경하는 코드이다.

실행결과

실행해보면 오류가 발생하지 않고 잘 실행된다.
하지만 결과는 홍길동으로 출력이 된다.

좀 더 상세한 로그확인을 위해 로직을 좀 더 추가해보았다.

@Transactional(readOnly = true)
public void changeNameReadOnly(int id, String name) {
    UserJpo userJpo = userRepository.findUserJpoById(id);
    FlushModeType flushMode = em.getFlushMode();
    log.info("flushMode: {}", flushMode);
    log.info("readOnly: {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly());
    printEntityState(userJpo);
    userJpo.setName(name);
}
실행결과
main] com.example.user.service.UserService     : flushMode: COMMIT
main] com.example.user.service.UserService     : readOnly: true

위에서 flushMode: COMMIT으로 출력된다.

여기서 flushMode는 무엇일까?

flush는 영속성 컨텍스트와 데이터베이스를 동기화하는 것을 의미한다. 영속성 컨텍스트의 객체 상태가 변경이 되고 해당 객체의 변경이 발생했을 때 DB에 변경내용을 반영하기 위해서 DML(insert/update/delete)이 실행된다.

flushMode는 자동으로 반영되는 내용을 조절하기 위해서 사용되는 모드이다.

  • FlushModeType.AUTO
    • 커밋이나 쿼리를 실행할 때 플러시한다는 의미 (기본값)
  • FlushModeType.COMMIT
    • 커밋할 때만 플러시한다는 의미

즉, @Transactional(readOnly = true)로 설정하면 FlushModeCOMMIT으로 되어 자동으로 플러시되지 않는다는 의미이다. 그래서 readOnly=true로 되면 commit을 따로 해주지 않는 한 DB에 변경사항이 반영되지 않는다.

2. @Transactional(readOnly=true/false) 이 중첩되어 사용하는 경우는 어떻게 적용될까?

service -> repository를 사용하는 구조에서 아래와 같은 형태일 때 readOnly가 적용될까?

1번 케이스: UserService(readOnly=true), UserRepository(readOnly=false)

2번 케이스: UserService(readOnly=false), UserRepository(readOnly=true)

1번 케이스 : UserService(readOnly=true), UserRepository(readOnly=false)
@Transactional(readOnly = true) // service는 readOnly=true
public void changeNameTrueFalse(int id, String name) {
    UserJpo userJpo = userRepository.findById(id) // repository는 readOnly=false
            .orElseThrow(IllegalArgumentException::new);
    FlushModeType flushMode = em.getFlushMode();
    log.info("flushMode: {}", flushMode);
    log.info("readOnly: {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly());
    printEntityState(userJpo);
    userJpo.setName(name);
}

UserService는 readOnly=true이고, UserRepository는 readOnly=false인 경우는 readOnlytrue로 설정이 된다.

그래서 name은 홍길동으로 변경이 되지 않는다.

2번 케이스 : UserService(readOnly=false), UserRepository(readOnly=true)
@Transactional(readOnly = false)
public void changeNameFalseTrue(int id, String name) {
    UserJpo userJpo = userRepository.findUserJpoById(id);
    FlushModeType flushMode = em.getFlushMode();
    log.info("flushMode: {}", flushMode);
    log.info("readOnly: {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly());
    printEntityState(userJpo);
    userJpo.setName(name);
}
@Repository
public interface UserRepository extends JpaRepository<UserJpo, Integer> {
    @Transactional(readOnly = true)
    UserJpo findUserJpoById(int id);
}

UserService는 readOnly=false이고, UserRepository는 readOnly=true인 경우는 readOnlyfalse로 설정이 된다.

그래서 name은 홍길동2로 변경이 된다.

결론: readOnly가 중첩으로 사용될 때는 @Transactional이 시작된 트랜잭션의 readOnly의 값으로 하위 트랜잭션에 모두 적용이 된다.

참고

https://www.baeldung.com/spring-jpa-flush

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유