DEV ℧ Developer Diary

[JPA] JPQL - 벌크연산

해당 포스트는 인프런 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 듣고 정리한 글입니다.

벌크연산

실시간성으로 작동하는 것이아니라, 모았다가 한번에 연산하는 기능을 말한다.

  • 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
  • JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
    1. 재고가 10개 미만인 상품을 리스트로 조회한다.
    2. 상품 엔티티의 가격을 10% 증가한다.
    3. 트랜잭션 커밋 시점에 변경감지가 동작한다.
  • 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행

예시

  • 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
  • executeUpdate()의 결과는 영향받은 엔티티 수 반환
  • UPDATE, DELETE 지원
  • INSERT(insert into .. select, 하이버네이트 지원)
int resultCount = entityManager.createQuery("update Member m set m.age = 20")
                    .executeUpdate();

System.out.println("resultCount : " + resultCount);
Hibernate:
    /* insert com.dhaudgkr.jpa07.jpql04.domain.Team
        */ insert
        into
            Team
            (age, name, id)
        values
            (?, ?, ?)
Hibernate:
    /* insert com.dhaudgkr.jpa07.jpql04.domain.Team
        */ insert
        into
            Team
            (age, name, id)
        values
            (?, ?, ?)

Hibernate:
    /* insert com.dhaudgkr.jpa07.jpql04.domain.Member
        */ insert
        into
            Member
            (age, TEAM_ID, type, username, id)
        values
            (?, ?, ?, ?, ?)

Hibernate:
    /* insert com.dhaudgkr.jpa07.jpql04.domain.Member
        */ insert
        into
            Member
            (age, TEAM_ID, type, username, id)
        values
            (?, ?, ?, ?, ?)

Hibernate:
    /* insert com.dhaudgkr.jpa07.jpql04.domain.Member
        */ insert
        into
            Member
            (age, TEAM_ID, type, username, id)
        values
            (?, ?, ?, ?, ?)

/* update
        Member m
    set
        m.age = 20 */ update
            Member
        set
            age=20
resultCount : 3

벌크연산을 수행하기 전에 자동으로 FLUSH를 진행하고 연산을 수행한다.

DB1

Member 테이블의 나이가 전부 20으로 변경된것을 확인 할 수 있다.

주의 사항

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 실행
    • 벌크 연산을 먼저 실행
    • 벌크 연산 수행 후 영속성 컨텍스트 초기화 : 이와 같은 이유는 벌크연산으로 인해, 벌크연산 이전에 조회했던 영속성 컨텍스트의 정보의 정합성에 어긋남이 발생할 수 있다.
int resultCount = entityManager.createQuery("update Member m set m.age = 20")
                .executeUpdate();
System.out.println("resultCount : " + resultCount);

Member findMember = entityManager.find(Member.class, member.getId());
System.out.println("findMember.getAge : " + findMember.getAge());
    /* update
        Member m
    set
        m.age = 20 */ update
            Member
        set
            age=20
resultCount : 3
findMember.getAge : 10

벌크연산을 통해 모든 Member의 age를 20으로 수정했지만, member1의 데이터를 불러올경우 update이전의 10의 age 정보를 가져온다.

이는, 벌크연산은 영속성 컨텍스트에 영향을 주지 않으므로 벌크연산으로 적용한 update의 정보가 반영되지 않는다. 기존에 영속성 컨텍스트의 1차캐시에서 member1의 데이터를 가져온것이라고 볼 수 있다.

int resultCount = entityManager.createQuery("update Member m set m.age = 20")
        .executeUpdate();
System.out.println("resultCount : " + resultCount);

/* 영속성 컨텍스트 초기화 추가 */
entityManager.clear();

Member findMember = entityManager.find(Member.class, member.getId());
System.out.println("findMember.getAge : " + findMember.getAge());

해당 기능을 실행해서 영속성 컨텍스트를 초기화해야 최신 정보를 조회해 올 수 있다.

findMember.getAge : 20