• 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
• JPA 변경 감지 기능으로 처리할 경우, 다음과 같은 방식으로 동작함
1. 재고가 10개 미만인 상품을 리스트로 조회한다.
2. 조회된 상품 각각의 가격을 10% 증가시킨다.
3. 트랜잭션 커밋 시점에 JPA의 변경 감지(Dirty Checking)가 동작하여
변경된 각 상품에 대해 UPDATE SQL을 실행한다.
→ 만약 변경된 상품이 100건이라면, UPDATE SQL이 100번 실행됨
(비효율적이며 성능 저하 우려 있음)
벌크 연산 예제
• 쿼리 한 번으로 여러 테이블 로우를 변경할 수 있다 (→ 엔티티를 일일이 변경하지 않음)
• JPA에서는 JPQL의 벌크 연산(Bulk Operation)을 통해 실행
• 예: UPDATE, DELETE 쿼리
- UPDATE: 조건에 맞는 여러 엔티티의 값을 한 번에 변경
- DELETE: 조건에 맞는 여러 엔티티를 한 번에 삭제
- INSERT: JPQL 자체는 지원하지 않지만, Hibernate는 아래처럼 지원함 (insert into ... select ... 구문)
• executeUpdate() 메서드 사용 시, 영향 받은 row 수(int)를 반환
→ 예: 20개의 row가 수정되었다면 executeUpdate()는 20 반환
• 단, 벌크 연산은 영속성 컨텍스트를 무시하므로 1차 캐시와 동기화 되지 않음 → 이후 em.clear() 등으로 캐시 초기화 권장
// 벌크 연산
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("resultCount = " + resultCount);
벌크연산주의
• 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리 실행
→ 즉, 1차 캐시나 스냅샷을 거치지 않고 DB에 바로 반영됨
• 벌크 연산은 가능한 한 트랜잭션 내에서 가장 먼저 실행하는 것이 안전함
• 벌크 연산 수행 후에는 반드시 em.clear() 또는 flush + clear로 영속성 컨텍스트를 초기화해야 데이터 불일치 문제를 방지할 수 있음
package org.hellojpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import org.hellojpa.jpql.Member;
import org.hellojpa.jpql.Team;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamB);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
// 벌크 연산
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("resultCount = " + resultCount);
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getAge());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
✅ 결과가 0으로 나오는 이유 요약 - 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 실행합니다. - 즉, em.find()로 가져오는 member1은 이미 영속성 컨텍스트에 존재하는 캐시된 엔티티입니다.