JPA Hint
JPA 쿼리 힌트는 SQL이 아닌 JPA구현체에게 제공하는 힌트이다.
만약 JPA로 특정 엔티티를 조회한다면 영속성 컨텍스트에는 조회한 데이터의 스냅샷을 남겨두기 때문에 하나의 엔티티 대해 두개의 객체가 존재하게 된다. 예를 들어 username이 "userA"라는 멤버를 조회했을 경우에 영속성 컨텍스트에는 "userA"라는 멤버 객체와 그 스냅샷이 같이 저장되는 것이다. ( JPA 영속성 컨텍스트 이해하기 Dirty Checking참고 )
이러한 기능은 조회한 데이터를 수정할 때는 용이하겠지만 그냥 단순히 수정하지 않고, 조회만을 한다면 쓸데없는 메모리 낭비로 이어진다.
하여 이러한 경우 스냅샷을 찍어두지 않도록 하는 방법을 하이버네이트가 제공하는데, JPA인터페이스는 제공하지 않는다. 하여 하이버네이트에게 살짝 알려주는 것이다. "스냅샷 찍지마...."라고.
먼저 JPA Hint를 적용하지 않고, 멤버를 조회한 뒤 멤버를 수정한 경우 알아서 update문을 날려주는 상황을 테스트해보자.
@Test
void queryBeforeHint() {
//given
Member member1 = memberRepository.save(new Member("member1", 10, null));
em.flush();
em.clear();
//when
Member findMember = memberRepository.findById(member1.getId()).get();
findMember.setUsername("member2");//member1의 이름 수정
em.flush();//Dirty checking을 하여 바뀐것이 있다면 Update 쿼리를 날린다.
}
/* Dirty checking의 단점은 영속성 컨텍스트에 두 개의 객체를 가지고 있어야 한다는 것이다.*/
/* 즉 member1과 member1의 스냅샷을 가지고 있어야 한다. --> 메모리를 더 먹게된다. */
/* 하여 개발자가 member 변경은 원하지 않고, 단순히 조회만 원할 때에도 메모리에 두개의 객체가 생기기 때문에*/
/* 이는 커다란 단점이라 할 수 있다. */
날아간 쿼리는 아래처럼 하나의 select문과 하나의 update문이 나간다.
select
member0_.member_id as member_i1_0_0_,
member0_.age as age2_0_0_,
member0_.team_id as team_id4_0_0_,
member0_.username as username3_0_0_
from
member member0_
where
member0_.member_id=?
// update문 발동
update
member
set
age=?,
team_id=?,
username=?
where
member_id=?
하지만 이번에는 JPA Hint를 사용해서 "스냅샷 찍지마!" 라고 힌트를 줘보자.
이때 @QueryHints( value = @QueryHint(name = "org.hibernate.readOnly", value = "true")) 를 사용한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
//name = "org.hibernate.readOnly", value = "true" --> 스냅샷을 만들지 않는다.
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
}
이를 테스트 해보자. 이번에도 역시 조회한 멤버의 이름을 "member2"로 수정했다. update쿼리가 나갈까??
@Test
void queryHint() {
//given
Member member1 = memberRepository.save(new Member("member1", 10, null));
em.flush();
em.clear();
//when
Member findMember = memberRepository.findReadOnlyByUsername("member1");
findMember.setUsername("member2");//member1의 이름 수정
em.flush();
}
테스트 결과 아래와 같이 select문 한개만 날라가고 update문은 없었다.
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
JPA Hint는 아래와 같이 페이징에도 적용할 수 있다. 반환타입을 Page로 해주면 된다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly",
value = "true")},
forCounting = true)
Page<Member> findPagingByUsername(String name, Pageable pageable);
}
• org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용
• forCounting : 반환 타입으로 Page 인터페이스를 적용하면 추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용(기본값 true )
Lock
자주 사용되진 않지만 Lock은 아래와 같이 사용할 수 있다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
Member findLockByUsername(String username);
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'JPA > 스프링 DATA JPA' 카테고리의 다른 글
Data JPA 11 Auditing (0) | 2022.01.08 |
---|---|
Data JPA 10 사용자 정의 리포지토리 구현 (0) | 2022.01.08 |
Data JPA 08 @EntityGraph (0) | 2022.01.07 |
Data JPA 07 벌크성 수정 쿼리 (0) | 2022.01.07 |
Data JPA 06 쿼리 메서드 - 반환타입, 페이징 (0) | 2022.01.06 |