Spring/SpringDataJPA

JPA Hint & Lock

느리지만 꾸준하게 2022. 4. 17. 23:41

JPA 쿼리 힌트 (SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)이고 코드로 살펴보면

 

Hibernate는 readOnly라는 쿼리기능을 제공한다.

 

JPA 영속성 컨텍스트에 딱 넣어놓고 (transaction commit 할때 자동으로 commit가 되는데 )

강제로 flush를 하면 실제 db에 insert 쿼리가 나가고(영속성 컨텍스트에 member가 남아있는 상태로

=> JPA 영속성 컨텍스트 안에 1차 캐시가 있는데 그걸 지우는 건 아니고 그것의 결과를 db에 동기화를 하는 것이고)

clear를 하면 영속성 컨텍스트가 다 날라간다.

 

그 이후로 JPA에서 조회를 하면 영속성 컨텍스트에 1차캐시가 없기 때문에 db를 무조건 조회한다.

// MemberRepositoryTest

@Test
    public void queryHint() {
        // given
        Member member1 = new Member("member1", 10);
        memberRepository.save(member1);
        em.flush();
        em.clear();

        // when
        Member findMember = memberRepository.findById(member1.getId()).get();
        // 객체상태를 바꾸고
//        findMember.setUsername("member2");

        // flush를 하면 상태가 바꼈다는걸 인지를 하고(Dirty Checking => 변경감지) member에서 member2로 바꼈구나

        em.flush();
    }

 

아래처럼 query를 돌리면

기존의 member1이였던 걸 member2로 바꿔버린다.

update member set age=10, tema_id=NULL, username='member2' where member_id=1;

 

 

 

MemberRepository를 아래와 같이 설정을 해주고

@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);

 

 

MemberRepositoryTest는 해서 돌리면 

@Test
public void queryHint() {
    // given
    Member member1 = new Member("member1", 10);
    memberRepository.save(member1);
    em.flush();
    em.clear();

    // when
    Member findMember = memberRepository.findReadOnlyByUsername("member1");
    findMember.setUsername("member2");

    // flush를 하면 상태가 바꼈다는걸 인지를 하고(Dirty Checking => 변경감지) member에서 member2로 바꼈구나
    em.flush();
}

이렇게 나오는데 readOnly는 변경감지 체크를 전혀 안한다.(스냅샷이 없고 내부적으로 읽기로만 최적화를 다하기 때문에)

=> 이러한 것들을 제공하기 위해서 queryHint가 제공이 된다.

select
        member0_.member_id as member_i1_0_,
        member0_.age as age2_0_,
        member0_.tema_id as tema_id4_0_,
        member0_.username as username3_0_ 
    from
        member member0_ 
    where
        member0_.username=?

 

Lock에 대해서 보면 DBMS가 select할 때 다른 애들은 손대지 마라하고 다 날라가게 걸어버릴 수가 있다.

// MemberRepository Interface

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);

 

LockModeType는 JPA 패키지로 나타나있다.

package javax.persistence;

 

 

MemberRepositoryTest

@Test
public void lock() {
    // given
    Member member1 = new Member("member1", 10);
    memberRepository.save(member1);
    em.flush();
    em.clear();

    // when
    List<Member> result = memberRepository.findLockByUsername("member1");
}

쿼리를 보면 select 에 update가 붙는것을 볼 수 있다.

select
        member0_.member_id as member_i1_0_,
        member0_.age as age2_0_,
        member0_.tema_id as tema_id4_0_,
        member0_.username as username3_0_ 
    from
        member member0_ 
    where
        member0_.username=? for update

 

그리고 application yml 에서 dialect: org.hibernate.dialect.Oracle10gDialect에 따라서 lock 동작방식이 달라진다.

// application yml

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/datajpa
    username: sa
    password:
    driver-class-name: org.h2.Driver


  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
      # 방언에 따라서 lock 동작방식이 달라진다.
#        dialect: org.hibernate.dialect.Oracle10gDialect
    #    show_sql: true
        format_sql: true


logging.level:
  org.hibernate.SQL: debug
#  org.hibernate.type: trace

 

 

 

실시간 트래픽이 많은 서비스에서 lock을 걸면 안된다. 특히 PESSIMISTIC LOCK을 걸면 손되는것이 다 lock이 걸려지기 때문이다.

걸려면 OPTIMISTIC lock을 걸어야 하는데 실제 lock을 거는것은 아니고 versioning 매커니즘으로 해결하는 방법이 있다.

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 실전! 스프링 데이터 JPA >

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/dashboard

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com