22.07.01
벌크성 수정 쿼리
JPA는 보통 엔티티를 하나 조회해서 데이터를 수정하면 트랜젝션 커밋 시점에 update 쿼리 -> JPA 변경 감지
여러 개의 엔티티를 쿼리 한 번으로 수정 또는 삭제 -> 벌크 연산 (executeUpdate())
(수정: update, 삭제: delete)
JPA를 사용한 벌크성 수정 쿼리
직접 jpql 작성: update 엔티티 set 변경사항 (where 조건)
int result = em.createQuery("update Member m set m.age = m.age + 1" + " where m.age >= :age")
.setParameter("age", age)
.executeUpdate(); //영향을 받은 엔티티 건수 반환
스프링 데이터 JPA를 사용한 벌크성 수정 쿼리
@Modifying *JPA executeUpdate()를 실행. 없으면 예외 발생
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
벌크 연산의 주의점
벌크 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에, 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있다..
하나의 트랜잭션 안에서 데이터 조회 -> 벌크 연산
권장하는 방안
1. 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행한다. (가장 심플)
2. 부득이하게 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트를 초기화 한다.]
자동 @Modifying(clearAutomatically = true): 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화
수동 (벌크 연산) -> em.clear()
*JPQL 쿼리 실행 과정: 내부적으로 영속성 컨텍스트 flush 한 후 JPQL 쿼리가 실행된다. 벌크 연산도 이것에 해당하기 때문에 영속성 컨텍스트의 flush 없이 clear 만 실행해도 된다.
@EntityGraph
연관된 엔티티들을 SQL 한번에 조회하는 방법
member -> team은 지연로딩 관계이다. team의 데이터를 조회할 때 마다 쿼리가 실행된다. (N+1 문제 발생)
연관된 엔티티를 한번에 조회하려면 페치 조인이 필요하다. (select 절에 team 데이터까지 포함)
(JPQL: select m from Member m (left) join fetch m.team)
스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다.
이 기능을 사용하면 JPQL 없이 페치 조인을 사용할 수 있다. (JPQL + 엔티티 그래프도 가능)
기본적으로 LEFT OUTER JOIN 사용
//공통 메서드 오버라이드
@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);
메서드에 어노테이션만 추가하면 내부적으로 페치 조인 적용
1. 스프링 데이터 JPA의 공통 메서드 <- 페치 조인
2. @Query(JPQL) <- 페치 조인
3. 스프링 데이터 JPA의 메서드 이름 기반 쿼리 생성 <- 페치 조인
"select m from Member m left join fetch m.team where m.username = :username"
NamedEntityGraph
/* 엔티티 */
@NamedEntityGraph(name = "Member.all", attributeNodes =
@NamedAttributeNode("team"))
@Entity
public class Member {}
/* 리포지토리 */
@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
JPA Hint & Lock
JPA Hint
JPA 쿼리 힌트(DB에 제공되는 SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)
ex) JPA(인터페이스) -> Hibernate(JPA 구현체)
org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);
변경 감지: 영속성 컨텍스트가 관리하고 있는 엔티티가 변경되었는지 확인하기 위해 변경 전 객체(스냅샷)이 필요하다.
단순 조회가 목적인 경우, 스냅샷은 없어도 됨 (최적화) -> readOnly (JPA 표준이 아닌 Hibernate가 제공하는 기능)
해당 메서드로 조회한 엔티티는 변경 감지가 작동하지 않는다.
Lock
org.springframework.data.jpa.repository.Lock 어노테이션을 사용
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);
JPA가 제공하는 Lock(javax.persistence) 기능을 편리하게 사용하게 도와준다.
*참고: 비관적 잠금(Pessimistic Lock)
트랜잭션끼리의 충돌이 발생(동일한 데이터를 동시에 수정)한다고 가정하고 우선 락을 거는 방법
DB에서 제공하는 락기능을 사용
실시간 트래픽이 많은 애플리케이션에서는 사용 XXX -> Optimisstic Lock으로 대체