객체그래프 탐색을 사용할때 SQL 한번에 조회하는 방법
member team은 지연로딩 관계이다. 따라서 다음과 같이 team의 데이터를 조회할 때 마다 쿼리가
실행된다. (N+1 문제 발생)
@Test
public void findMemberLazy() throws Exception {
//given
//member1 -> teamA
//member2 -> teamB
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
memberRepository.save(new Member("member1", 10, teamA));
memberRepository.save(new Member("member2", 20, teamB));
em.flush();
em.clear();
//when
List<Member> members = memberRepository.findAll();
//then
for (Member member : members) {
member.getTeam().getName();
}
}
team 을 가져오기 위한 쿼리
select
team0_.team_id as team_id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.team_id=?
member.getTeam().getName() = teamA
select
team0_.team_id as team_id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.team_id=?
member.getTeam().getName() = teamB
member.getTeam 을 하게되면 Team 지연로딩 이기 때문에 프록시 객체이다 따라서 team 의 정보를 DB에서 가져오는 쿼리를 N개만큼 날리게 된다.이러한 문제를
해결하기 위해 연관된 엔티티를 한번에 조회하기 위한 페치 조인이 있다.
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다. 이 기능을
사용하면 JPQL 없이 페치 조인을 사용할 수 있다. (JPQL + 엔티티 그래프도 가능)
//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
//메서드 이름으로 쿼리에서 특히 편리하다.
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
다음과 같이 @EntityGraph 를 사용하게 되면 JPQL을 직접 짜지 않고 페치조인을 사용할 수 있다.
@NamedEntityGraph(name = "Member.all", attributeNodes =
@NamedAttributeNode("team"))
@Entity
public class Member {}
--------------------------------------------------
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
JPA 쿼리 힌트(SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)
JPA에는 변경 감지라는 기능이 있다. 그래서 find로 가져온 객체와 원본 객체를 비교해서 flush 하는 시점에 둘이 서로 값이 다를 경우 update 쿼리가 발생한다.
이런 기능이 편리하고 장점도 있지만 단점이 존재한다.
영속성 컨텍스트에서 가지고 있는 원본 객체가 값이 수정된 걸 어떻게 알까?
바로 find() 같이 객체를 가져오면 스냅샷을 따로 찍어놓는다.
flush() 할 때 스냅샷과 원본이 다를 경우 update 쿼리를 날리는 것이다.
결국 객체를 두개 관리 하는 것이기 때문에 비용이 들게 된다.
따라서 단순히 조회만 하고 싶은 경우 과정을 생략하고 최적화 하기 위해서 JPA 쿼리 Hint 를 사용해야한다.
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
@Test
public void queryHint() throws Exception {
//given
memberRepository.save(new Member("member1", 10));
em.flush();
em.clear();
//when
Member member = memberRepository.findReadOnlyByUsername("member1");
member.setUsername("member2");
em.flush(); //Update Query 실행X
}
JPA HINT 를 쓴다고 해서 성능 최적화가 크게 되진 않는다.(조회 쿼리 전부에 사용할 필요는 없음)
@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly", value = "true")}, forCounting = true)
Page<Member> findByUsername(String name, Pageable pageable);
내가 접근하고 하는 Database 리소스에 다른사람이 접근조차 하지못하도록 락을 걸고 작업을 진행하는 것
여기서 접근이라는 것은 READ 작업과 WRITE 작업이 분할되어 있다.
PESSIMISTIC_READ
– 해당 리소스에 공유락을 겁니다.타 트랜잭션에서 읽기는 가능하지만 쓰기는 불가능해진다.PESSIMISTIC_WRITE
– 해당 리소스에 베타락을 겁니다.타 트랜잭션에서는 읽기와 쓰기 모두 불가능해진다. (DBMS 종류에 따라 상황이 달라질 수 있다)@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);
아직 해나가야 할 것들이 많다. 쉬지 말고 가자.