kinggora 2022. 7. 1. 15:57

벌크성 수정 쿼리

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으로 대체