Server/Spring JPA

QueryDSL - 벌크(Bulk) 연산 시 주의할 점!

JaeHoney 2022. 5. 8. 16:20

벌크(Bulk) 연산 주의사항

Querydsl에서 Bulk 연산을 할 때 주의사항이 있다. 예를 통해 살펴보자.

 

아래 메서드는 모든 member의 money를 0으로 초기화한다.

public void bulkUpdate() {

    // init Money
    queryFactory
            .update(member)
            .set(member.money, 0)
            .execute();
            
}

문제는 이때 쿼리를 날려서 DB에 반영은 하지만, 영속성 컨텍스트에 반영하지 않는다.

 

문제 상황

기존에 아래와 같이 Member가 3개가 있다고 가정하자.

- id: 1, money: 10000

- id: 2, money: 20000

- id: 3, money: 30000

 

해당 Member들이 영속성 컨텍스트에 들어 있을 때 Bulk 연산을 하면 문제가 발생한다.

public void bulkUpdate() {

    memberRepository.findAll();
    
    // (영속성 컨텍스트에 Member 3개가 있는 상태)

    queryFactory
            .update(member)
            .set(member.money, 0)
            .execute();
            
    // update문이 쿼리로 나가서 DB에 반영이 됨. 영속성 컨텍스트에는 반영되지 않음
            
    Member member = queryFactory
        .select(member)
        .from(member)
        .where(member.id.eq(1L))
        .fetchOne();

    System.out.println(member.getMoney()); // 10000 출력!          
}

위에서 영속성 컨텍스트에 3개의 Member가 있었다. 해당 Member들의 money를 0으로 Update하는 쿼리를 날려서 DB에도 반영이 되었다.

 

근데 Querydsl에서 Bulk 연산은 DB에 Update문을 날리고 영속성 컨텍스트에 반영해주지 않는다. 결국 영속성 컨텍스트에 값이 변경되지 않은 Member 3개가 남아있게 되고 조회를 하면 영속성 컨텍스트에 있는 데이터가 나온다. (불일치 발생!)

해결 방법

Querydsl에서 Bulk 연산을 하면 데이터를 수정하면 DB랑 영속성 컨텍스트가 안맞게 된다.

그래서 Bulk 연산을 하면 영속성 컨텍스트를 초기화시키는 작업이 필요하다.

public void bulkUpdate() {

    memberRepository.findAll();
    
    // (영속성 컨텍스트에 Member 3개가 있는 상태)

    queryFactory
            .update(member)
            .set(member.money, 0)
            .execute();
            
    // 추가! - 영속성 컨텍스트 초기화
    em.clear();
            
    Member member = queryFactory
        .select(member)
        .from(member)
        .where(member.id.eq(1L))
        .fetchOne();

    System.out.println(member.getMoney()); // 0 출력!          
}

이후에는 수정사항이 반영된 데이터를 조회할 수 있다. 추가적으로 영속성 컨텍스트를 초기화할 때 이전의 변경사항이 있다면 em.flush()로 반영해야 한다.

 

정리하자면 이러한 불일치를 막기 위해서는 아래와 같은 작업들이 필요하다.

  • bulk 연산 후에 변경이 적용된 데이터로 뭔가를 한다면 그 전에 em.clear()로 영속성 컨텍스트를 초기화한다.
  • 다른 변경사항이 있다면 em.flush()로 변경사항을 즉시 반영한다.
  • 가능하면 트랜잭션은 작은 단위로 설계하는 것이 바람직하다.

 


Reference