본문 바로가기
Spring Data JPA

Spring Data JPA - 쿼리 메서드(3)

by 왈레 2023. 4. 13.

7. 벌크성 수정 처리

벌크 업데이트란 단건 UPDATE, DELETE가 아닌 다건의 UPDATE, DELETE 연산을 하나의 쿼리로 하는 것을 말한다.

// Bulk Update
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);

 

 

벌크성 수정 쿼리 사용시 주의점

JPA에서 벌크 연산성 조심해야할 게 있다.

JPA라는것은 영속성 컨텍스트에서 Entity들이 flush()를 통해서 DB에 반영이 되는건데

벌크연산은 영속성 컨테이너를 안거치고 직접 DB에 update 연산을 해버린다.

그래서 영속성 컨텍스트와 DB 싱크가 안맞는다. Entity를 save하고 나서 이 Entity에 벌크성 update를 하고

Entity로 다시 조회해보면 update가 반영이 안된것을 확인할 수 있다.

사실은 반영이 안된게 아니라 DB에는 반영이 되있는데 영속성 컨텍스트와 싱크가 안맞아서 그런거다.

그래서 벌크연산 이후에 영속성 컨텍스트를 무저건 날려야한다.

em.flush(), em.clear() 아니면 @Modifying에 clearAutomatically = true을 주면 된다.

 

8. @EntityGraph

 

@EntityGraph는 Data JPA에서 fetch 조인을 어노테이션으로 사용할 수 있도록 만들어 준 기능이다.

쉽게 생각해서 Member 객체안에 Team 객체가 존재하고 User객체를 select할 때 Team 객체를 @EntityGraph로쉽게 fetch join할 수 있는 기능을 제공해준다.

 

  • 그렇다면 왜 fetch 조인을 할까?
  • fetch = FetchType.LAZY이랑 무슨상관일까?

아무리 (fetch = FetchType.LAZY) 을 사용한다해도 결국 프록시 객체를 사용해야하는 코드가 있다면 N + 1인것이다. Member 안에 team이 LAZY가 걸려있고, memberList를 메서드 쿼리로 가져오고(이때는 한방 쿼리)

각각의 member안 team에 접근해야하는 코드가 있다면 결국 N + 1이 된다.

따라서 이런 경우 member를 불러올 때 team까지 한방 쿼리로 불러오는 fetch join을 사용해야한다.

 

fetch join을 하려면 JPQL을 써야하고, 이건 너무 귀찮은 방식이다. 이것을 해결해주기 위해서 JPA는 EntityGraph 기능을 제공한다. (간단한건 EntityGraph, 복잡한건 그냥 JPQL)

 

참고로 Member의 Team이 프록시 객체인지 아닌지 확인하려면 Member.getTeam().getClass()를 통해서 확인할 수 있다. 객체이름에 Proxy가 써져잇으면 프록시 객체이다.

// fetch join : 이렇게하면 한방으로 member와 member안의 team을 한방 쿼리로 불러온다.
@Query("select m from Member m left join fetch m.team")
List<Member> findAllFetchJoin();

// EntityGraph
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();

// JPQL + EntityGraph 형태도 가능
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

// 메소드 쿼리 + EntityGraph
@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username); // 강의에서는 @Param을 넣어줫는데, 빼도 동작하는 듯  

// NamedEntityGraph (잘 안씀)
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
public class Member {...}

@EntityGraph("Member.all")
List<Member> findNamedEntityGraphByUsername(String username);

 

9. JPA Hint & Lock

  • JPA 쿼리 힌트 (SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)
  • 힌트는 하이버네이트가 제공하고 JPA 표준은 제공하지않는다.
  • 예를들어 JPA 더티체킹은 Entity의 변경이 이뤄지면 자동으로 update를 해주는데 이것의 단점은 결국 비교를 원본데이터와 바뀐데이터 두 개를 가지고 있어야한다. 그래서 메모리 낭비가 있을 수 있다.
    JPA 조회하는 순간 영속성 컨텍스트에 원본을 만들어둔다.
    하지만 ReadOnly hint를 준다면 JPA는 최적화를 내부적으로 진행하고 영속성 컨텍스트의 스냅샷같은 곳에 원장 데이터를 만들지않는다. → 이런 경우 물론 더티체킹도 안된다.
  • 사실 이런걸로 극적인 최적화는 안된다. 일일이 공수를 들여서 굳이 Hint를 다 줄필요없다. 어차피 조회성능이 부족하면 메모리 DB를 사용해야한다.
  • lock은 자주 사용하는 기능인 아니다. 실시간 트래픽이 많은 곳에서는 가급적 lock을 걸면안되고, 걸려면 옵티머스 락이라고 실제 락을 거는게 아니라 버저닝이라는 매커니즘으로 거는방법이 있는데 그걸 사용하거나, 다른 방법으로 풀어야한다, 실시간 트래픽이 많지않고 돈을 맞추거나 이런경우에 사용한다.
// Hint
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);

// Lock
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);

'Spring Data JPA' 카테고리의 다른 글

Spring Data JPA - 쿼리 메서드(2)  (0) 2023.04.13
Spring Data JPA - 쿼리 메서드(1)  (0) 2023.04.12

댓글