벌크성 수정 쿼리
- 벌크성 쿼리는 대량의 엔티티 속성을 수정해야 할 때, 사용하는 기능이다.
- ex) 월급이 300 이상인 사원들의 연봉 10% 인상을 DB에 반영 등
일반 JPA 벌크성 수정 쿼리
public int buikAgePlus(int age) {
return em.createQuery("update Member m set m.age = m.age + 1 where m.age >= :age")
.setParameter("age", age)
.executeUpdate();
}
- 파라미터의 age 값보다 큰 Member의 age를 + 1 시켜주는 쿼리이다.
테스트 코드
@Test
public void bulkUpdate() {
//given
memberJpaRepository.save(new Member("member1", 10));
memberJpaRepository.save(new Member("member2", 19));
memberJpaRepository.save(new Member("member3", 20));
memberJpaRepository.save(new Member("member4", 21));
memberJpaRepository.save(new Member("member5", 40));
//when
int resultCount = memberJpaRepository.buikAgePlus(20);
//then
assertThat(resultCount).isEqualTo(3);
}
스프링 데이터 JPA 벌크성 수정 쿼리
@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
테스트 코드
public void bulkUpdate() {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 19));
memberRepository.save(new Member("member3", 20));
memberRepository.save(new Member("member4", 21));
memberRepository.save(new Member("member5", 40));
//when
int resultCount = memberRepository.bulkAgePlus(20);
//then
assertThat(resultCount).isEqualTo(3);
}
@Modifying
어노테이션 사용- 미사용시
org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations
예외 발생
- 미사용시
- 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화 :
@Modifying(clearAutomatically = true)
- 해당 옵션 없이 회원을
findById
로 다시 조회하면, 영속성 컨텍스트에 과거 값이 남아서 문제가 될 수 있으므로, 다시 조회해야 한다면, 영속성 컨텍스트 초기화를 해야한다.
- 해당 옵션 없이 회원을
권장하는 방안
- 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행
- 부득이하게 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트 초기화
@EntityGraph (fetch 조인)
- 연관된 엔티티들을 SQL 한번에 조회한다.
- 현재 member → team은 지연로딩 관계이므로, team을 조회할 때 마다 쿼리가 실행된다.
@Test
public void findMemberLazy() {
//given
//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);
em.flush();
em.clear();
//when
//select Member 실행
List<Member> members = memberRepository.findAll();
for (Member member : members) {
System.out.println("member = " + member.getUsername());
//select Team 실행 -> Lazy 로딩시 N + 1 문제 발생
System.out.println("member = " + member.getTeam().getName());
}
}
- 해당 테스트를 실행하면, N + 1 문제가 발생하여 쿼리가 총 3번 실행된다. (member는 실제 객체로 쿼리 한번에 member1과 2를 조회해오지만, 각각의 member에 연관된 team은 가짜인 프록시 객체로 조회해 오기때문에, 각각 연관되어 있는 team에대해 쿼리가 나간다.)
JPQL 페치 조인
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();
@Query
를 이용해서 페치조인 쿼리를 작성해서 조회할 수 있다.
EntityGraph
// 공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
//username 파라미터를 받는 메서드 이름 쿼리
@EntityGraph(attributePaths = {"team"})
List<Member> findEntityGraphByUsername(@Param("username") String username);
@EntityGraph
를 이요하면, 페치조인을 간편하게 사용 할 수 있다.
NamedEntityGraph 사용
// Member엔티티에 NamedEntityGraph 선언
@NamedEntityGraph(name = "Member.all", attributeNodes =
@NamedAttributeNode("team"))
@Entity
public class Member {}
// 리포지토리에서 선언한 NamedEntityGraph 이용
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
JPA Hint & Lock
Hint
- 조회해서 가져온 엔티티가 영속성 컨텍스트에 캐싱 되어 있는 상태일 때, 해당 엔티티의 내용을 수정하면, JPA의 변경감지 기능에 의해서 update쿼리가 수행되는데, 이를 방지하기 위해 조회기능만 적용하기 위해서 사용한다.
@QueryHints(value = {@QueryHint(name = "org.hibernate.readOnly", value = "true")}, forCounting = true)
Member findReadOnlyByUsername(String username);
org.springframework.data.jpa.repository.QueryHints
어노테이션 사용forCounting
: 반환 타입으로 Page 인터페이스를 적용하면 추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용(기본값true
)
Lock
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);
org.springframework.data.jpa.repository.Lock
- JPA가 제공하는 락은 JPA 책 16.1 트랜잭션과 락 절을 참고
정리
벌크성 수정 쿼리
- 대량의 엔티티 속성을 수정해야할 때 사용가능
@Modyfiying
사용- 벌크 연산 직후 영속성 컨텍스트 초기화
@EntityGraph 페치조인
- 지연 로딩 관계인 엔티티들을 조회할 때, N + 1 문제를 방지하기 위해 페치조인을 간편하게 사용하는 기능
@EntityGraph
사용
Hint & Lock
- Hint : 영속성 컨텍스트에 캐싱된 엔티티의 변경 감지기능을 사용하지 않고 조회기능만 사용 할 수 있게 적용하는 기능
반응형