본문 바로가기
웹 개발/JPA

deleteAllByIdInBatch는 영속성 컨텍스트를 동기화하지 않는다 (feat.문서를 잘 읽자…)

by 어썸오184 2022. 7. 18.
728x90
반응형

delete 관련 기능을 개발하다가 trouble shooting(?)한 경험을 간단히 공유합니다.

JpaRepository의 deleteAllByIdInBatch 메서드를 사용하는 서비스 코드를 테스트하는 과정에서, 분명 delete 쿼리가 로그에 찍히는데 findById로 조회를 하면 데이터가 남아있는 문제가 있었습니다.

이해하기 쉽게 예시를 다시 구성해보면

@Test
void Space삭제() {
    Host host = hostRepository.save(Host_생성("1234"));
    Space space = spaceRepository.save(Space_생성(host, "잠실 캠퍼스"));
    Job job1 = jobRepository.save(Job_생성(space, "청소"));
    Job job2 = jobRepository.save(Job_생성(space, "마감"));

    jobRepository.deleteAllByIdInBatch(List.of(job1.getId(), job2.getId()));
    spaceRepository.deleteById(space.getId());

    assertThat(spaceRepository.findById(space.getId())).isEmpty();  // 통과 
    assertThat(jobRepository.findById(job1.getId())).isEmpty(); // Assertion Error! Expecting an empty Optional but was containing value
    assertThat(jobRepository.findById(job2.getId())).isEmpty();
}

위처럼 Space는 검증이 통과하는데 Job은 값이 남아있어 검증이 통과하지 않습니다.

하지만 콘솔에 쿼리 로그에는 delete job 쿼리가 날아가는 것을 볼 수 있었습니다.

Hibernate: 
    delete 
    from
        job 
    where
        id in (
            ? , ?
        )

 

한참을 골머리를 앓다가 다음과 같이 영속성 컨텍스트를 flush, clear 하는 코드를 추가하니 테스트가 성공했습니다.

@Test
void Space삭제() {
    Host host = hostRepository.save(Host_생성("1234"));
    Space space = spaceRepository.save(Space_생성(host, "잠실 캠퍼스"));
    Job job1 = jobRepository.save(Job_생성(space, "청소"));
    Job job2 = jobRepository.save(Job_생성(space, "마감"));

    jobRepository.deleteAllByIdInBatch(List.of(job1.getId(), job2.getId()));
    spaceRepository.deleteById(space.getId());

    entityManager.flush();
    entityManager.clear();

    assertThat(spaceRepository.findById(space.getId())).isEmpty();
    assertThat(jobRepository.findById(job1.getId())).isEmpty();
    assertThat(jobRepository.findById(job2.getId())).isEmpty();
}

왜 이런 일이 발생하는지 이유를 찾다가 뒤늦게 JpaRepository 문서에서 다음과 같은 설명을 발견했습니다.

void deleteAllByIdInBatch( Iterable < ID > ids)

Deletes the entities identified by the given ids using a single query. This kind of operation leaves JPAs first level cache and the database out of sync. Consider flushing the EntityManager before calling this method.

 

deleteAllByIdInBatch 메서드는 영속성 컨텍스트를 동기화하지 않고 database에 바로 쿼리를 날리는 방식으로 동작합니다.

findById는 영속성 컨텍스트를 먼저 조회하고 해당 값이 없으면 DB를 조회하는데, deleteAllByIdInBatch로 삭제한 데이터는 DB에서는 삭제되었지만 영속성 컨텍스트에는 남아있으므로, Job 삭제 검증은 통과를 하지 못한 것입니다.

반면 deleteById는 entityManager.remove()를 호출하기 때문에 Space 삭제는 검증을 통과합니다.

deleteAllByIdInBatch의 문서를 먼저 봤더라면 금방 해결할 수 있었던 문제를, 한참동안이나 헤맸습니다…

728x90
반응형

'웹 개발 > JPA' 카테고리의 다른 글

[JPA] JPA는 왜 사용하나요?  (0) 2021.07.13

댓글