본문 바로가기
Spring|Spring-boot/Spring-Data-JPA

JPA Hint & Lock

by oncerun 2022. 11. 27.
반응형

 

처음에 데이터베이스 엔진에게 알려주는 SQL 힌트인지 알았지만 그게 아니었다.

이는 JPA 구현체에게 제공하는 힌트이다. 

 

무슨 기능이 있을까?

 

대표적으로 readOnly 사용할 때 사용할 수 있다.

 

@Test
public void readOnly() throws Exception {
    //given
    Member member = memberRepository.save(Member.builder().age(10).username("member1").build());
    em.flush();
    em.clear();
    //when

    Member findMember = memberRepository.findById(member.getId()).get();
    findMember.setAge(30);

    em.flush();
    //then

}

(get()은 사용하지 마세요..)

 

다음 코드는 변경 감지를 사용하는 경우이다. 

 

member를 가져와서 나이를 변경했다. JPA 특성상 변경 감지를 통해 원본 데이터가 변경된 경우 트랜잭션 커밋 시점에 update 쿼리를 질의한다. 

 

이 상황에서는 flush() 시 발생할 것이다. 

 

JPA의 영속성 콘텍스트 내부에는 1차 캐시를 저장하는 저장소가 존재한다. 변경 감지 입장에서 생각해보면

영속성 대상인 엔티티를 저장하고 있다가 해당 엔티티가 변경되었을 때 해당 변경을 추적하기 위해서이다.

 

그렇다면 원본 객체도 가지고 있어야 하고, 변경된 엔티티도 저장해야 변경된 부분을 확인해서 update를 하지 않을까? 

 

맞다.

 

메모리를 더 사용할 수밖에 없다. 내부적으로 원본 객체와 변경에 대해 대응하기 위한 객체 둘 다 가지고 있다.

 

이를 우리는 보통 스냅숏과 엔티티를 비교한다고 한다. 

 

스냅숏을 저장할 필요가 없는 경우가 있다. 만약에 변경하지 않고 단순 조회만 하고 싶으면 어떻게 될까? 

 

영속성 콘텍스트에 저장할 것인데 스냅숏이 필요 없는 경우 우리는 별도의 힌트를 제공할 수 있다. 

(아 물론 @Transactional(readOnly=true) 쓰면 더 편해요~)

 

@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findHintById(Long id);

 

Member member = memberRepository.save(Member.builder().age(10).username("member1").build());
em.flush();
em.clear();
//when

Member findMember = memberRepository.findHintById(member.getId());
findMember.setAge(30);

em.flush();

실제 사용하게 되면 Update 쿼리는 발생하지 않는다.

 

이는 스냅숏을 만들지 않는다는 것을 알 수 있다.

 

근데 사실 이로 인해 얻는 성능이 크다고는 할 수 없다. 정말 트래픽이 미쳐서 조회 성능이 딸리게 된다면 결국 레디스로 캐시를 하던지 해야 한다. 

 

 

 

 

Lock

 

JPA에서 제공하는 기능인데 스프링 데이터 JPA에서 사용하기 쉽게 기능을 제공해 준다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findHintById(Long id);

이 경우 나가는 쿼리를 확인해보면 for update가 붙는다.

    select
        member0_.id as id1_0_,
        member0_.age as age2_0_,
        member0_.team_id as team_id4_0_,
        member0_.username as username3_0_ 
    from
        member member0_ 
    where
        member0_.id=? for update

 

 

 

추가로 왜 @Transaction(readOnly = true)로 대체해도 되는지 설명하자면 다음과 같다.

 

실제 이는 Spring 5.1에서 적용된 내용이다. 

이전에는 @Transaction(readOnly = true)를 사용해도 스냅샷 생성이 막히지 않았다. 

 

이후 버전에는 @Transaction(readOnly = true)을 사용하면 다음과 같이 진행된다.

 

1. @QueryHint와 동일하게 스냅샷을 만들지 않는다. 

 

2. 불필요한 em.flush()를 호출하지 않는다. 

 

따라서 이는 메모리와 cpu를 절약할 수 있는 효과를 내고 @QueryHint 대신 @Transaction(readOnly = true)를 사용함으로써 대체될 수 있다.

 

다만 엔티티를 조회용으로 사용하는 것은 복잡하지 않은 애플리케이션에서만 적용되는 내용이긴 하다. 

 

DTO로 조회하는 경우 어짜피 스냅샷은 만들어지지 않기 때문에 별도의 성능 이점을 얻을 수 없다.

반응형

'Spring|Spring-boot > Spring-Data-JPA' 카테고리의 다른 글

Auditing  (0) 2022.11.30
Custom Repository  (0) 2022.11.30
@EntityGraph  (0) 2022.11.27
벌크성 수정 쿼리  (0) 2022.11.27
스프링 데이터 JPA  (0) 2022.11.26

댓글