JPQL 벌크 연산을 주의하자
JPQL 벌크 연산
엔티티를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나, 삭제하려면 entityManger.remove() 메서드를 사용할 수 있다. 하지만 이 방법으로 수백 개 이상의 엔티티를 하나씩 처리하기에는 시간이 너무 오래 걸린다. 이럴 때 여러 건을 한번에 수정하거나 삭제하는 벌크 연산을 사용할 수 있다.
벌크 연산의 주의점
하지만 벌크 연산을 사용할 때는 벌크 연산이 영속성 컨텍스트의 변경감지나 이런 특징들을 이용하지 않고 데이터베이스에 직접 쿼리한다는 점에 주의를 해야한다. 한 상황을 예시로 들어보겠다.
한 사용자의 모든 질문들의 내용들을 변경하는 JPQL 메서드가 있다고 하자.
- User의 ID가 1인 질문을 조회하였다.
- 벌크 연산으로 질문의 내용을 모두 hot!! 으로 변경시켰다.
- 벌크 연산을 수행한 후에 질문의 내용을 확인해보았을 때 hot!! 으로 변경되지 않았다.
처음 조회를 했을 때 question 객체가 영속성 컨텍스트가 관리된다. 벌크 연산은 영속성 컨텍스트를 통하지 않고 데이터베이스에 직접 쿼리한다. 데이터베이스에서는 question의 내용은 hot!!으로 변경되었을 것이다. 이렇게 영속성 컨텍스트의 객체 상태와 데이터베이스에 데이터의 값이 다르게 되기 때문에 주의해서 사용해야 한다.
- entitymanager.refresh() 사용하기 데이터베이스에서 데이터를 다시 조회해온다.
- 벌크 연산 수행 후 영속성 컨텍스트 초기화 벌크 연산을 수행한 직후에 영속성 컨텍스트를 초기화해서 비어준다. 그 후에 새로 엔티티를 조회해서 데이터베이스에서 엔티티를 조회한 후에 영속성 컨텍스트에 보관하게 하자.
아래의 그림과 같이 JPQL이 영속성 컨텍스트를 거치지 않고 데이터베이스로 직접 변경한 데이터를 다시 조회해오게 하면 된다.
영속성 컨텍스트와 JPQL
JPQL 조회 대상으로 엔티티, 임베디드, 값 등 여러가지 종류가 있을텐데 JPQL로 엔티티를 조회하면 영속성 컨텍스트에서 관리되지만 엔티티가 아니면 영속성 컨텍스트에 관리되지 않는다.
JPQL로 조회한 엔티티와 영속성 컨텍스트
JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있으면 JPQL로 데이터베이스에서 조회한 결과를 버리고 대신에 영속성 컨텍스트에 있던 엔티티를 반환한다.
왜 영속성 컨텍스트에 있는 기존 엔티티를 반환하는 것일까? 일단 영속성 컨텍스트는 기본 키 값을 기준으로 엔티티를 관리하기때문에 같은 기본 키 값을 가진 엔티티는 등록할 수 없다. 그리고 영속성 컨텍스트에 있던 데이터가 수정중인데 JPQL로 새로 검색한 엔티티로 대체하게 되면 위험하다. 또한 영속성 컨텍스트는 엔티티의 동일성을 보장하기 때문에 새로 검색한 엔티티를 버리고 기존 엔티티를 그대로 두게 되는 것이다.
JPQL과 플러시 모드
기본적으로 entityManager의 플러시 모드는 Auto이다.
FlushModeType.AUTO : 커밋 또는 쿼리 실행시 플러시 (기본 값) FlushModeType.COMMIT : 커밋시에만 플러시
기존에 영속성 컨텍스트에 있던 User 객체가 닉네임이 kun에서 kun2로 변경되어 쓰기 지연 저장소에 update 쿼리를 보관중이다. JPQL로 kun2 닉네임인 User를 조회하려고 한다고 할 때, flush가 되지 않았다면 데이터베이스에서는 kun2로 닉네임이 변경되지 않았을 것이다. 플러시 모드를 기본값인 AUTO로 사용한다면 쿼리 실행 직전에 영속성 컨텍스트가 플러시 되고 kun2 닉네임으로 User를 조회할 수 있게된다.
참고
자바 ORM 표준 JPA 프로그래밍