[TroubleShooting/JPA] deleteById() 사용시 select 가 선행 되는 문제

반응형

문제

Spring Data Jpa 에서 제공하는 deleteById 메소드를 사용해서 데이터를 삭제 했을 경우에 삭제 쿼리 하나만 실행되길 바랬는데. 삭제 쿼리가 실행되기 전에 삭제할 데이터를 찾는 조회 쿼리가 선행 되는 문제가 발생했다.

해결

@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {

    Assert.notNull(entity, "Entity must not be null!");

    if (entityInformation.isNew(entity)) {
        return;
    }

    Class<?> type = ProxyUtils.getUserClass(entity);

    T existing = (T) em.find(type, entityInformation.getId(entity));

    // if the entity to be deleted doesn't exist, delete is a NOOP
    if (existing == null) {
        return;
    }

    em.remove(em.contains(entity) ? entity : em.merge(entity));
}


@Transactional
@Override
public void deleteById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
            String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
}

JPA 가 구현 되어 있는 SimpleJpaRepository 를 확인해보면 delete 메소드는 엔티티 를 삭제하며, deleteById 함수는 delete 함수를 사용하고, 매개변수로 받은 Id 값으로 findById 로 엔티티를 조회해 엔티티 삭제를 진행한다. deleteById 이지만 삭제 대상은 엔티티 이기 때문에 Id로 엔티티를 조회해 삭제를 진행하는 것이다.

 

만약 삭제 쿼리만 단일로 실행되길 바란다면 JPQL 쿼리로 직접 삭제 메소드를 구현하면 된다. 

@Transactional
@Modifying
@Query("delete Member m where m.id = :id")
void deleteByMemberId(Long id);

JPQL 로 삭제 쿼리를 사용하면 엔티티를 삭제해 영속성 컨텍스트를 거쳐 삭제 되는 것이 아닌, DB 에 바로 질의하는 것이기 때문에 해당 데이터의 불일치가 발생할 수 있다.


그럴 경우 영속성 콘텍스트를 비워서 JPQL 를 통해 업데이트 된 데이터를 영속성 콘텍스트에 다시 반영해주면 된다.
비우는(다시 쓰는) 동작을 진행하기 위해 Modifying 에 추가 설정을 주면 된다.

@Modifying(clearAutomatically = true)

그리고 JPQL를 통한 데이터 수정, 삭제 진행 시에 @Transactional 어노테이션을 달아주어야 한다. 이유는SimpleJpaRepository의 Transactinal 의 기본 설정이 readOnly = true로 되어 있기 때문에 조회 외의 쿼리를 실행하기 위해 readOnly 설정을 오버라이딩 해주기 위해 붙여야 한다.

 

 

반응형