JPA

[Data JPA] 벌크, @EntityGraph, Hint

나는시화 2024. 1. 30. 14:31

벌크성 수정 쿼리 

쿼리

// MemberRepository, / 벌크 쿼리
@Modifying // 이게 있어야 업데이트 쿼리를 날려줌.
@Query("update Member m set m.age = m.age + 1 where m.age >=:age")
int bulkAgePlus(@Param("age")int age);

Test 코드 

  • 벌크 쿼리는 영속성 컨텍스트에 들리지 않고 DB에 바로 쿼리를 날림. 영속성 컨텍스트는 영향을 받지 않음.
  • 그래서 아래와 같이 사용X, 벌크 쿼리 사용 후에는 영속성 컨텍스트를 날려주어야함. 
@Test
public void paging(){
    Member m1 = new Member("AAA1",20);
    Member m2 = new Member("AAA2",30);
    Member m3 = new Member("AAA3",40);
    Member m4 = new Member("AAA4",10);
    Member m5 = new Member("AAA5",22);
    memberRepository.save(m1);
    memberRepository.save(m2);
    memberRepository.save(m3);
    memberRepository.save(m4);
    memberRepository.save(m5);

    int resultCount = memberRepository.bulkAgePlus(20);
    // 벌크 연산은 영속성 컨텍스트를 들리지 않고 DB에 쿼리를 바로 날리는 식
    // 영속성 컨텍스트에 있는 member들은 영향을 받지 않고, 
    // DB에 있는 member들은 영향을 받음. 
    Member aaa5 = memberRepository.findMemberByUsername("AAA5");
    System.out.println("aaa5 age = " + aaa5.getAge()); // 22,
    System.out.println(resultCount); // 4


}

수정 코드 

  • 방법1: flusth와 clear 사용 
@Test
public void paging(){
    Member m1 = new Member("AAA1",20);
    Member m2 = new Member("AAA2",30);
    Member m3 = new Member("AAA3",40);
    Member m4 = new Member("AAA4",10);
    Member m5 = new Member("AAA5",22);
    memberRepository.save(m1);
    memberRepository.save(m2);
    memberRepository.save(m3);
    memberRepository.save(m4);
    memberRepository.save(m5);
    
    entityManager.flush();
    entityManager.clear();
    
    int resultCount = memberRepository.bulkAgePlus(20);
    Member aaa5 = memberRepository.findMemberByUsername("AAA5");
    System.out.println("aaa5 age = " + aaa5.getAge()); // 23
    System.out.println(resultCount); // 4


}
  • 방법2: clearAutomatically = true 사용 
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >=:age")
int bulkAgePlus(@Param("age")int age);

@EntityGraph

 지연 로딩 전략 

  • team의 name을 호출할 때마다 쿼리 발생. -> 성능 이슈 
@Test
public void findMemberLazy(){
    // 멤버와 팀은 다대1 지연관계 전략
    // member1 -> teamA
    // member2 -> teamB
    Team teamA = new Team("teamA");
    Team teamB = new Team("teamB");
    teamRepository.save(teamA);
    teamRepository.save(teamB);
    Member member1 = new Member("member1",10,teamA);
    Member member2 = new Member("member2",10,teamB);
    memberRepository.save(member1);
    memberRepository.save(member2);

    entityManager.flush();
    entityManager.clear();

    List<Member> members = memberRepository.findAll();
    for(Member member : members){
       System.out.println("member = " + member.getUsername()); // 이때는 문제가 없음
       System.out.println("member.team = " + member.getTeam().getName()); // 쿼리 발생
    }
}

fetch join 

  • 쿼리가 한 번만 나가는 것을 볼 수 있다.
// member repository에 쿼리 작성 
@Query("select m from Member m left join fetch m.team ")
List<Member> findMemberFetchJoin();
@Test
public void findMemberLazy(){
    // 멤버와 팀은 다대1 지연관계 전략
    // member1 -> teamA
    // member2 -> teamB
    Team teamA = new Team("teamA");
    Team teamB = new Team("teamB");
    teamRepository.save(teamA);
    teamRepository.save(teamB);
    Member member1 = new Member("member1",10,teamA);
    Member member2 = new Member("member2",10,teamB);
    memberRepository.save(member1);
    memberRepository.save(member2);

    entityManager.flush();
    entityManager.clear();

    List<Member> members = memberRepository.findMemberFetchJoin(); // 이 때 데이터를 모두 조회 
    for(Member member : members){
       System.out.println("member = " + member.getUsername());
       System.out.println("member.team = " + member.getTeam().getName());
    }
}

EntityGraph

  • 위 코드에서 findAll()로 멤버 조회
  • 결과는 위와 같음.
  • EntityGraph를 사용하면 fetch 조인을 한 것과 같음.
// member repository에 EntityGraph 어노테이션 사용 
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
List<Member> members = memberRepository.findAll(); // 이 때 데이터를 모두 조회
for(Member member : members){
    System.out.println("member = " + member.getUsername());
    System.out.println("member.team = " + member.getTeam().getName());
}


Hint 

  • entityManager.flush(); // 변경 감지로 update쿼리가 나감
    변경 감지를 사용하려면 원본과 수정이 된 객체 , 즉 2개가 필요함 -> 메모리 사용
    근데 변경을 하지 않을 상황에서도 조회했다고 메모리를 먹을 수 있음.
@Test
public void queryHint(){
    Member member1 = memberRepository.save(new Member("member1", 10));
    entityManager.flush();
    entityManager.clear();

    Member findMember = memberRepository.findById(member1.getId()).get();
    findMember.setUsername("member2");

    entityManager.flush(); // 변경 감지로 update쿼리가 나감
    // 변경 감지를 사용하려면 원본과 수정 객체 2개가 필요함 -> 메모리 사용
    // 근데 변경을 하지 않을 상황에서도 조회했다고 메모리를 먹을 수 있음.

}

 

@QueryHints 사용 

  • 읽기 전용 객체 불러오기 
// member repository 
@QueryHints( value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String user);
@Test
public void queryHint(){
    Member member1 = memberRepository.save(new Member("member1", 10));
    entityManager.flush();
    entityManager.clear();

    // 읽기 전용
    Member findMember = memberRepository.findReadOnlyByUsername("member1");
    findMember.setUsername("member2"); // 변경 감지가 일어나지 않음.
    // 읽기 전용이라 메모리 낭비가 없다. 

    entityManager.flush();
}