Backend

Soft Delete 테스트: @SQLDelete와 @Where의 효과적인 검증 방법

지미닝 2024. 11. 30. 11:34

 Soft Delete는 데이터베이스에서 레코드를 물리적으로 삭제하지 않고 상태 플래그를 변경해 비활성화 처리하는 방식입니다. 이를 구현한 후 예상대로 동작하는지 철저히 검증하는 테스트 전략이 중요합니다. 이번 글에서는 Soft Delete와 Delete의 내부 동작 방식, 그리고 Soft Delete 구현 시 테스트 코드 작성 방법에 대한 구체적인 전략을 다룹니다.

 

1. @SQLDelete와 @Where의 내부 동작 방식

1. @SQLDelete

@SQLDelete는 엔티티가 삭제될 때 Hibernate가 생성하는 기본 DELETE SQL 대신 사용자 정의 SQL을 실행하도록 설정하는 어노테이션입니다. 내부적으로 아래의 단계를 거칩니다.

1단계: Hibernate Entity Persister를 설정

Hibernate는 각 엔티티에 대해 EntityPersister를 생성합니다. 이는 엔티티에 대한 SQL 작업(삽입, 업데이트, 삭제 등)을 처리하는 핵심 컴포넌트입니다. @SQLDelete가 선선언된 경우, EntityPersister는 기본 DELETE 쿼리 대신 사용자 SQL 쿼리를 참조합니다. Hibernate는 EntityPersister.delete() 메서드를 호출할 때, 커스텀 SQL이 실행됩니다.

2단계: SQL 실행

@SQLDelete에 정정의된 SQL은 데이터베이스에 전달되어 실행됩니다. 보통 delete=true와 같이 상태 플래그를 업데이트 하는 SQL로 작성됩니다.

 

2. 엔티티의 영속성 컨텍스트와 Soft Delete

Hibernate의 영속성 컨텍스트는 엔티티의 상태를 관리합니다. 이때 Soft Delete를 구현할 때 고려해야합니다.

  1. 삭제된 엔티티도 영속성 컨텍스트에 남아 있을 수 있습니다.
  2. 영속성 컨텍스트에서 삭제된 엔티티를 처리하지 않으면 잘못된 데이터가 캐시에 남아 있을 수 있습니다.

해결책: EntityManager.clear() 또는 flush()를 적절히 사용하여 영속성 컨텍스트를 초기화하거나 동기화합니다.

 

3. Soft Delete 테스트 코드 작성 전략

Soft Delete는 물리적 삭제가 아닌 논리적 삭제가 되어야 하기에 실제 Database에서는 해당 값이 존재하여야 합니다. 하지만, @Where 조건을 활용해여 deletedAt=null인 경우에만 조회하도록 설계하였기 때문에 실제로 논리적 삭제가 잘 구현되었는지에 관해서는 알 수 없었습니다.

 

방법 1) @Rollback(value = false) 를 활용하여 로컬 Database에 직접 확인

장점: 간단히 로컬 DB에서 결과를 확인 가능하다.

단점: 매번 직접 확인해야 하므로 자동화되지 않는다.

제가 참고한 글에서는 @SpringBootTest를 통해서 통합 테스트를 진행시킨 후 @Transactional annotation을 활용해 테스트 할 수 있음 을 알 수 알 수 있었습니다.

@Test
@Rollback(value = false)
public void testSoftDelete() {
    // given
    Long id = createTestEntity();

    // when
    repository.deleteById(id);

    // then
    Optional<Entity> deletedEntity = repository.findById(id);
    assertTrue(deletedEntity.isPresent());
    assertNotNull(deletedEntity.get().getDeletedAt());
}

하지만, 매번 Soft delete가 제대로 동작하는지 확인하기 위해서 직접 매번 database에서 확인하는 방법은 괜찮아보이진 않았습니다.

 

방법 2) Where 조건절을 무시하는 쿼리를 작성

장점: 삭제된 데이터도 조회 가능해 논리적 삭제 확인에 적합하다.

단점: Native Query 사용 시 유지보수 복잡성이 증가한다.

그 다음 방법은, Where 조건 절을 무시하는 쿼리를 작성하는 것입니다. 사실 테스트코드를 작성할 때 외에도 탈퇴 혹은 삭제 처리 된 record를 가져와야 하는 상황에서 @Where 조건으로 원하는 데이터를 가져오지 못하는 경우가 있어 불편함을 느낀적이 있었습니다.

따라서 JPA의 Where 조건절을 무시하고자 Native Query를 사용하는 방법이 있습니다. 해당 코드는 다음과 같이 작성할 수 있습니다.

@Repository
public interface MemberCustomerRepository extends JpaRepository<MemberCustomer, Long> {
    Optional<MemberCustomer> findByMemberIdAndCustomerIdAndDeletedAtIsNull(Long memberId, Long customerId);

    Optional<MemberCustomer> findByMemberIdAndDeletedAtIsNull(Long memberId);

    @Query(value = "SELECT * FROM MemberCustomer", nativeQuery = true) 
    Optional<MemberCustomer> findByAllMemberWithDeletedAt();

    @Query(value = "SELECT * FROM MemberCustomer WHERE member_id = ?1", nativeQuery = true)
    Optional<MemberCustomer> findByMemberIdWithDeletedAt(Long memberId);
}

위와 같이 Native Query를 활용한다면 deletedAt가 null이 아닌 값도 가져올 수 있습니다. 이를 활용하여 테스트코드에 적용시키면 실제로 논리적 삭제가 정상적으로 동작했는지 확인할 수 있습니다.

 

참고 문헌