다음은 순수 jpa에서 사용한 벌크성 repository
//단순 jpa 사용 -> age이상인 회원의 나이를 +1 해주는 메소드
public int bulkAgePlus(int age) {
int result = em.createQuery("update Member m set m.age = m.age+1 where m.age >=:age")
.setParameter("age", age)
.executeUpdate();
return result;
}
기존에 Spring data jpa 를 사용하기전에는 .executeUpdate()라는 메소드를 사용해 주어야 했다.
@Modifying 어노테이션은 벌크성 쿼리 메소드에서 excuteUpdate()의 역할을 해준다.
excuteUpdate()의 메소드는 데이터 조회(select)를 제외한데이터를 추가(Insert), 삭제(Delete), 수정(Update)하는 SQL 문을 실행
기본적으로 jpaRepository에서 제공하는 메서드나 메서드 네이밍으로 만들어진 쿼리에는 적용되지 않는다.
clearAutoMatically, flushAutomatically 속성이 존재하며 주로 벌크연산과 같이 이용된다.
다음은 Spring data jpa를 사용한 Repository 메소드
@Query("update Member m set m.age = m.age+1 where m.age >=:age")
int bulkAgePlus(@Param("age") int age);
만약 이 Spring data jpa를 사용한 벌크성 쿼리 위에 @Modifying 어노테이션을 붙여주지 않는다면 다음과 같은 예외가 발생한다.
org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations
벌크성 쿼리를 실행한 다음에 영속성 컨텍스트에 과거 값이 남아서 문제가 될 수 있다. 만약 다시 조회해야한다면 반드시 영속성 컨텍스트를 초기화해야하는데 @Modifying에는 다음과 같은 속성이 존재한다.
JPA에서 1차 캐시는 DB의 접근 횟수를 줄여주고 다양한 성능 개선의 효과를 가져다주지만 @Modifying과 @Query를 이용한 벌크 연산에서는 이 기능때문에 예측 불가능한 결과가 나올 수 있다.
이제 할말이 굉장히 중요한 말이다.
JPA에서 조회를 실행할 때 1차 캐시를 확인해서 해당 엔티티가 1차 캐시에 존재한다면 DB에 접근하지 않고, 1차캐시의 엔티티를 반환한다. 하지만 벌크연산은 1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 QUERY를 실행하기 때문에 영속성 컨텍스트는 데이터 변경을 알 수 없다. 즉, 벌크연산을 실행할 떄 싱크가 맞지 않게된다.
이 문제를 해결하기위해 @Modigying어노테이션은 ClearAutomatically라는 속성이 존재하는 것이다. defalt가 False인 이 속성을 true로 변경해준다면 벌크연산 직후 자동으로 영속성 컨텍스트를 clear해준다 영속성 컨텍스트를 Clear해준다면 조회를 실행할 때 이미 update결과가 진행되어진 DB를 조회하기 때문에 데이터의 동기화문제를 해결할 수 있다.
@Modifying(clearAutomatically = true) //@Modifying 이것이 jpa에서 .excuteUpdate() 를 해준다.
@Query("update Member m set m.age = m.age+1 where m.age >=:age")
int bulkAgePlus(@Param("age") int age);
//clearAutomatically = true 속성은 벌크성 쿼리가 실행한다음에 em.clear()를 해준다.(영속성 컨텍스트 clear)