@Modifying 이란?
Spring Data JPA에서 사용되는 애노테이션으로, @Query 어노테이션을 통해 작성된 변경이 일어나는 쿼리(INSERT, DELETE, UPDATE )를 실행할 때 사용된다.
@Modifying을 변경이 일어나는 쿼리와 함께 사용해야 JPA에서 변경 감지와 관련된 처리를 생략하고 더 효율적인 실행이 가능하다.
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
📌 벌크 연산이란?
벌크 연산은 데이터베이스에서 UPDATE, DELETE 시 대량의 데이터를 한 번에 처리하기 위한 작업이다.
즉, JPA에서 벌크 연산은 단 건 데이터를 변경(더티 체킹)하는 것이 아닌, 여러 데이터에 변경 쿼리를 날리는 작업을 말한다.
@Modifying 애노테이션을 사용하여 벌크 연산을 수행할 수 있다.
예를 들어, 모든 사용자의 나이를 한 번에 늘리려면 다음처럼 사용할 수 있다.
@Modifying
@Query("UPDATE User u SET u.age = u.age + 1")
int incrementAllUserAges();
이렇게 하면, JPA가 데이터베이스에서 모든 사용자의 나이를 한 번에 늘리는 쿼리를 실행하고 변경된 로우의 개수를 반환한다.
이 방식을 통해 일괄 처리를 할 때 효율적인 성능 개선을 얻을 수 있다.
@Modifying 옵션 알아보기
flushAutomatically
기본 값은 false로, true면 해당 쿼리를 실행하기 전, 영속성 컨텍스트의 변경 사항을 DB에 flush한다.
clearAutomatically
기본 값은 false로, true면 해당 쿼리를 실행한 후, 영속성 컨텍스트를 clear한다.
이 옵션을 true로 설정하지 않을 경우 문제가 발생할 수 있는데 아래 주의할 점에서 살펴보자.
@Modifying 사용 시 주의할 점
변경 쿼리 동기화 문제
JPA에서는 1차 캐시라는 기능이 있다.
1차 캐시를 간단하게 설명하면 영속성 컨텍스트에 있는 1차 캐시를 통해 엔티티를 캐싱하고, DB의 접근 횟수를 줄임으로써 성능 개선 한다.
그런데 @Modifying과 @Query 를 사용한 벌크 연산에서 1차 캐시와 관련하여 문제가 발생한다.
JPA에서 조회를 실행할 시에 1차 캐시를 확인해서 해당 엔티티가 1차 캐시에 존재한다면 DB에 접근하지 않고, 1차 캐시에 있는 엔티티를 반환한다.
하지만 벌크 연산은 1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 Query를 실행하기 때문에 영속성 컨텍스트는 데이터 변경을 알 수가 없다.
즉, 벌크 연산 실행 시, 1차 캐시(영속성 컨텍스트)와 DB의 데이터 싱크가 맞지 않게 되는 것이다.
예시를 통해 한번 알아보자.
public interface TeamRepository extends JpaRepository<Team, Long> {
Team findByName(String name);
@Modifying
@Query("update Team t set t.name = :name where t.id = :id ")
void updateName(Long id, String name);
}
@Service
@RequiredArgsConstructor
public class TeamService {
private final TeamRepository teamRepository;
@Transactional
public void modifyTest() {
Team 오디 = new Team("오디");
Team save = teamRepository.save(오디);
System.out.println("======= befor : " + 오디.getName());
teamRepository.updateName(save.getId(), "뉴 오디");
System.out.println("======= after : " + teamRepository.findById(save.getId()).get().getName());
}
}
현재 오디라는 변수의 Team이 save되었을 때 영속성 컨텍스트에 관리되고 있고,
updateName이라는 벌크 쿼리를 실행했다 결과가 어떻게 나올까 ?
영속성 컨텍스트에 관리되고 있던 "오디"라는 이름을 가진 Team을 1차 캐시에서 꺼내와 조회했기 때문에 위 처럼 조회시에 변경 전인
"오디"가 출력되었다. (DB에는 데이터가 변경되어 있음)
벌크 쿼리를 실행하기 전에 영속성으로 관리되던 객체가 있었을 때 조회 시 영속성 컨텍스트에 과거 값이 남아 문제가 발생
이 경우 변경된 데이터를 사용하기 전에 영속성 컨텍스트를 비워주는 작업이 필요한데,
@Modifying의 clearAutomatically=true 속성을 사용해 변경 후 자동으로 영속성 컨텍스트를 초기화 할 수 있다.
해당 속성을 추가하게 되면, 조회를 실행할 때 1차캐시에 해당 엔티티가 존재하지 않기 때문에 DB 조회 쿼리를 실행하게 된다. ( 데이터 동기화 문제를 해결 )
@Modifying(clearAutomatically = true)
1차 캐시에 데이터가 없기 때문에 update 수행 후 추가적으로 select 쿼리를 날려 "뉴 오디"라는 변경된 이름을 가져왔다.
트랜잭션 관리
@Modifying 애노테이션은 기본적으로 @Transactional과 함께 사용된다.
변경 작업은 트랜잭션 내에서 실행되어야 하며, 완료되지 않은 변경 작업이 여러 작업에 영향을 줄 수 있기 때문이다.
이를 통해 데이터베이스에 대한 변경 작업을 수행할 때 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 지속성(Durability)을 보장할 수 있게 된다.
'◼ JPA' 카테고리의 다른 글
[JPA] 컬렉션 조회 시 주의할 점 (hibernate.default_batch_fetch_size) (0) | 2023.06.20 |
---|---|
JpaRepository를 상속한 인터페이스가 구현체가 없이 동작하는 이유 (0) | 2023.06.19 |
[JPA] JPQL의 fetch join(패치 조인)이란? (2) | 2023.06.12 |
[JPA] 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading) (0) | 2023.06.12 |
[JPA] 사용자 정의 쿼리 작성과 파라미터 바인딩(@Query, @Param) (0) | 2023.06.09 |