[JPA] @Modifying이란? 그리고 주의할점 (벌크 연산)

@Modifying 이란?

Spring Data JPA에서 사용되는 애노테이션으로,  @Query 어노테이션을 통해 작성된 변경이 일어나는 쿼리(INSERT, DELETE, UPDATE )를 실행할 때 사용된다.

@Modifying을 변경이 일어나는 쿼리와 함께 사용해야 JPA에서 변경 감지와 관련된 처리를 생략하고 더 효율적인 실행이 가능하다.

@Transactional
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);

▶  @Query, @Param 알아보기  (클릭)

 

📌 벌크 연산이란?

벌크 연산은 데이터베이스에서 UPDATE, DELETE 시 대량의 데이터를 한 번에 처리하기 위한 작업이다.
즉, JPA에서 벌크 연산은 단 건 데이터를 변경(더티 체킹)하는 것이 아닌, 여러 데이터에 변경 쿼리를 날리는 작업을 말한다.

 

@Modifying 애노테이션을 사용하여 벌크 연산을 수행할 수 있다.

예를 들어, 모든 사용자의 나이를 한 번에 늘리려면 다음처럼 사용할 수 있다.

@Transactional
@Modifying
@Query("UPDATE User u SET u.age = u.age + 1")
int incrementAllUserAges();

이렇게 하면, JPA가 데이터베이스에서 모든 사용자의 나이를 한 번에 늘리는 쿼리를 실행하고 변경된 로우의 개수를 반환한다.

이 방식을 통해 일괄 처리를 할 때 효율적인 성능 개선을 얻을 수 있다.


@Modifying  사용 시 주의할 점

변경 쿼리 동기화 문제

JPA에서는 1차 캐시라는 기능이 있다.

1차 캐시를 간단하게 설명하면 영속성 컨텍스트에 있는 1차 캐시를 통해 엔티티를 캐싱하고, DB의 접근 횟수를 줄임으로써 성능 개선 한다.

JPA 내부 동작 방식 알아보기 (클릭)

 

그런데 @Modifying과 @Query 를 사용한 벌크 연산에서 1차 캐시와 관련하여 문제가 발생한다.

JPA에서 조회를 실행할 시에 1차 캐시를 확인해서 해당 엔티티가 1차 캐시에 존재한다면 DB에 접근하지 않고, 1차 캐시에 있는 엔티티를 반환한다.

하지만 벌크 연산1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 Query를 실행하기 때문에 영속성 컨텍스트는 데이터 변경을 알 수가 없다.

즉, 벌크 연산 실행 시, 1차 캐시(영속성 컨텍스트)와 DB의 데이터 싱크가 맞지 않게 되는 것이다.

(만약 벌크 쿼리를 실행하고 다시 해당 데이터를 조회하면 영속성 컨텍스트에 과거 값이 남아 문제가 발생 )

 

이 경우 변경된 데이터를 사용하기 전에 영속성 컨텍스트를 비워주는 작업이 필요한데,

@Modifying의 clearAutomatically=true 속성을 사용해 변경 후 자동으로 영속성 컨텍스트를 초기화 할 수 있다. 

해당 속성을 추가하게 되면, 조회를 실행할 때 1차캐시에 해당 엔티티가 존재하지 않기 때문에 DB 조회 쿼리를 실행하게 된다. 데이터 동기화 문제를 해결 )

@Modifying(clearAutomatically = true)

 

트랜잭션 관리

@Modifying 애노테이션은 기본적으로 @Transactional과 함께 사용된다.

변경 작업은 트랜잭션 내에서 실행되어야 하며, 완료되지 않은 변경 작업이 여러 작업에 영향을 줄 수 있기 때문이다.

이를 통해 데이터베이스에 대한 변경 작업을 수행할 때 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 지속성(Durability)을 보장할 수 있게 된다.