벌크 연산

밀크야살빼자·2024년 5월 29일
0

벌크 연산

하나의 데이터가 아닌 여러 데이터를 한번에 수정하거나 삭제하는 연산입니다. (Hibernate는 삽입 연산도 지원합니다.)

순수 JPA

  • executeUpdate()를 통해 벌크 연산을 수행할 수 있습니다.
int resultCount = 
	em.createQuery("update employee m set m.salary = m.salary * 1.2 where age > :age")
	.executeUpdate();

Spring Data JPA

  • Modifying을 사용하여 벌크 연산을 합니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
    @Modifying
    @Query("update employee m set m.salary = m.salary * 1.2 where age > :age")
    int bulkSalaryUpdate(int age);
}

만약, @Modifying을 작성해주지 않으면 org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations 예외가 발생합니다.

QueryDsl

public long updateSalary(int salary) {
	query.update(employee)
		.set(employee.salary, employee.salary.multiply(1.2))
		.where(employee.age.gt(age))
		.execute();
}

주의 사항

  • 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 수행합니다. (즉, DB에 반영된 변경이 영속성 컨텍스트에는 반영이 되지 않습니다.)
    1. 트랜잭션 시작
     2. 영속성 컨텍스트에 나이가 20인 직원1, 직원 2 등록
     3. 나이가 20인 직원 연봉 인상 - 벌크 연산(DB만 바꾸고 영속성 컨텍스트는 그대로 - 불일치 발생)
     4. findByAge(int age)로 나이가 20인 직원들을 가져옴(실질적인 문제 발생)
     5. 해당 직원들의 데이터를 클라이언트에게 보내줌
     6. 트랜잭션 커밋
    
    3번에서 현재 영속성 컨텍스트는 그대로 두고 나이가 20인 직원들의 연봉을 인상 시킵니다. 4번에서 findByAge(20)를 통해서 DB에 연봉이 인상된 데이터를 가져옵니다. 하지만, JPA는 기본적으로 DB에서 데이터를 가져와도 영속성 컨텍스트에 해당 엔티티가 존재하면 DB데이터를 버리고 영속성 컨텍스트의 데이터를 사용합니다. 그렇기 때문에 DB에 업데이트된 데이터가 아닌, 2번째 과정에서 영속성 컨텍스트에 등록한 직원1, 직원2의 데이터를 그대로 사용하게 됩니다. 이는 DB와 영속성 컨텍스트간의 데이터 불일치가 발생한 것입니다.

해결 방법

  • em.refresh()를 사용하여 벌큰 연산 수행 직후 정확한 엔티티를 사용해야 할때, 해다 메소드를 통해 DB에서 다시 조회합니다.

  • 벌크 연산을 먼저 실행하고 조회하면 됩니다.(가장 실용적인 해결책이며, JPA와 JDBC를 함께 사용할 때도 유용합니다.)

    • 트랜잭션 시작 → 나이가 20인 직원 연봉 인상(벌크연산) → 트랜잭션 커밋
  • 벌크 연산시에는 영속성 컨텍스트와의 데이터를 맞춰주기 위해서 영속성 컨텍스트를 초기화해주어야 합니다.

    		1. 트랜잭션 시작
    		2. 영속성 컨텍스트에 나이가 20인 직원 1, 직원 2 등록
    		3. 나이가 20인 직원 연봉 인상 - 벌크 연산(DB만 바꾸고 영속성 컨텍스트는 그대로(불일치 발생))
    		4. 영속성 컨텍스트와 DB를 맞춰주고, 영속성 컨텍스트를 Clear(불일치 해결)
    		5. findByAge(int age)로 나이가 20인 직원들을 가져옴(문제 없음)
    		6. 해당 직원들의 데이터를 클라이언트에게 보내줌
    		7. 트랜잭션 커밋
    		int resultCount = memberRepository.bulkSalaryUpdate(20);
    		em.flush();
    		em.clear();

    벌크 연산은 결국 JPQL을 사용하는 것이기 때문에, bulkSalaryUpdate 실행시키는 순간 지금까지 영속성 컨텍스트에 담긴 변경사항들을 DB에 flush한 후 수행합니다.(FlushModeTypedl Auto인 경우)
    따라서 em.flush()가 없어도 괜찮습니다.

    		int resultCount = memberRepository.bulkSalaryUpdate(20);
    		em.clear();

    Modifying의 옵션 - clearAutomatically

    영속성 컨텍스트를 초기화 해주기 위해 clear()를 해주었는데, 이는 @Modifying의 옵션을 바꾸는 것만으로도 해결이 가능합니다.

    @Modifying(clearAutomatically = true)

    참고자료

  • https://velog.io/@sdsd0908/JPA-%EB%B2%8C%ED%81%AC-%EC%BF%BC%EB%A6%AC%EC%97%B0%EC%82%B0-%EC%82%AC%EC%9A%A9%EB%B2%95-em.flush

  • https://ttl-blog.tistory.com/842

profile
기록기록기록기록기록

0개의 댓글