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 >
'Spring > SpringDataJPA' 카테고리의 다른 글
Web 확장 - 도메인 클래스 컨버터 & 페이징과 정렬 (0) | 2022.04.18 |
---|---|
사용자 정의 리포지토리 구현 (0) | 2022.04.18 |
@EntityGraph (0) | 2022.04.17 |
벌크성 수정 쿼리(error 해결중) (0) | 2022.04.17 |
스프링 데이터 JPA 페이징과 정렬 (0) | 2022.04.17 |