728x90
반응형
SMALL

2024.11.01 업데이트


쿼리 한 번으로 여러 테이블 로우(레코드)를 변경하는 걸 말한다. UPDATE, DELETE, INSERT 같은 것들이 이제 벌크 연산이 가능하다.

뭐 별건 아닌데 조금 주의할 사항이 있다.

 

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다.

이게 뭐가 문제냐면 벌크 연산을 처리한 전과 후에 만약 영속성 컨텍스트에 관리하고 있는 레코드(객체)가 있다면 벌크 연산이 적용되지 않은 채 영속성 컨텍스트에 그대로 관리될 수 있다. 그래서 이걸 해결하는 방법은 벌크 연산을 처리하고 영속성 컨텍스트를 초기화하는 것이다. 또는 영속성 컨텍스트에 뭔가를 관리하기 전 벌크 연산을 먼저 수행하는 것이다. 

반응형
SMALL

 

예를 들어보자.

멤버 3명이 있고 최초에 age0으로 설정했다. 그리고 영속성 컨텍스트에 멤버 3명을 영속시켰는데 그 이후에 벌크 연산으로 멤버 모두의 age값을 20으로 설정했다. 설정한 후 3명의 멤버 중 한명을 아무나 잡고 age를 찍어보면 내가 기대하는 건 20인데 0으로 나온다. 왜냐하면, 영속성 컨텍스트에 관리되는 멤버의 age0에서 변경된 상태가 아니다. 위에서도 말했지만 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리하기 때문이다. 실제 결과 출력을 보면 다음과 같이 반영된 개수는 3개지만 그 중 하나를 임의로 찍었을 때 나이가 0으로 나온다. 이럴때가 문제가 될 수 있다. 

그래서, 벌크 연산은 그냥 무조건 벌크 연산을 처리하고 영속성 컨텍스트를 초기화하자.

플러시를 한다고 초기화하는게 아니다. 플러시는 영속 컨텍스트 데이터를 DB에 반영하는거고 초기화는 clear()를 호출해야 한다.

 

 

참고로, 이렇게 JPQL을 사용하면 무조건 사용하는 시점에 flush()를 호출한다. 벌크 연산도 마찬가지다. 그래서 JPQL 호출하기 전에 flush()를 먼저하고 JPQL을 실행한다. 근데 그거랑 clear()는 다른 얘기다. 이미 영속성 컨텍스트에서 관리하는 객체가 있으면 flush()를 하더라도 그 데이터는 그대로 남아있는거다. 그러니까 벌크 연산을 수행할 때 flush()가 자동 호출되지만, clear()를 하지 않은 상태에서 영속성 컨텍스트에 있는 데이터를 그대로 가져오면 DB를 거쳐서 가져오는게 아니고 영속성 컨텍스트에 있는 데이터를 가져오고 이 데이터는 벌크 연산에 적용되는 데이터라고 할 지라도 데이터가 반영되어 있는 상태가 아닌건 똑같다. 그래서 결론은 벌크 연산은 반드시 수행하고 clear()를 호출하자! 

그런데! 이 역시도 Spring Data JPA를 사용하면 굉장히 깔끔하고 편리하게 해결해준다. 아래 코드를 보자.

interface UserRepository extends Repository<User, Long> {

  @Modifying
  @Query("delete from User u where u.role.id = ?1")
  void deleteInBulkByRoleId(long roleId);
}
  • 지금 저기 NamedQuery가 있다. 그리고 저건 벌크 연산이다. Role이 특정 Role인 유저들을 모두 지우니까. 
  • 그런데, 그 위에 @Modifying 애노테이션이 있다. 저 애노테이션은 영속성 컨텍스트를 clear()하는 애노테이션이다. 즉, 이 쿼리가 수행된 후 clear()를 호출해주는 애노테이션이다. 그래서 이렇게 이쁘고 간단하게 벌크 연산이 가능하다.

 

728x90
반응형
LIST

'JPA(Java Persistence API)' 카테고리의 다른 글

[JPA] Dirty Checking, Merge  (8) 2024.11.10
[JPA] OSIV (Open Session In View)  (0) 2023.11.16
[JPA] Part 17. Named Query  (2) 2023.10.30
[JPA] Part 16. Fetch JOIN (JPQL)  (0) 2023.10.30
[JPA] Part 15. JPQL  (0) 2023.10.28

+ Recent posts