[Java] Soft Delete

Welcome to Seoyun Dev Log·2023년 4월 9일
0

JAVA

목록 보기
2/12

물리 삭제 / 논리 삭제

  • hard delete : 데이터를 삭제 할 때 row를 삭제시키는 것(삭제할 데이터가 추후에 필요성을 못느낄 때 사용)
  • soft delete : 삭제와 관련된 컬럼명의 값을 {true, false}로 구분하거나 {null, 삭제 날짜}로 구분
    (이럴 때는 delete 구문을 update 로 바꾸는 것)

논리삭제(soft delete)를 했을 경우 값을 찾는 쿼리를 날릴 때
JPQL에서 삭제 데이터를 제외하고 조회하거나 애플리케이션에서 제외하는 방법이 좋기 때문에 조건문을 추가하여 null인 값, 즉 삭제되지 않은 값만 찾으면 됩니다.

where deleted_date = null

SoftDelete

soft delete를 사용하는 이유는 삭제된 데이터를 추후에 사용할 가능성이 있기 때문입니다.
남겨두고 복구를 간단하게 할 수 있어야 할 때 사용합니다.

회사 입장에서 생각해보면 두 가지 이유가 있을 것입니다.

  1. 데이터 분석을 통해 활용될 때
  2. 법적으로 데이터의 일정 기간을 가지고 있어야 할 때
  3. 서비스를 운영하다 보면 유저들로부터 많은 컴플레인을 받게 되는데 그중 실수로 인한 삭제에 대한 상황에서의 대처를 유연하게 하려면 논리삭제

사이트나, 앱에 가입할 때, "고객님의 데이터를 1년간 보관하는 것에 동의하십니까?" 라는 문구에 체크를 한 문구를 보았던 기억이 있습니다.
'이러한 경우에 soft delete를 사용했겠구나!' 라고 깨닫은 계기가 되었습니다.

delete를 처리하는 여러가지 방법이 있습니다.

  1. 실제 삭제한다.
  2. delete 필드를 YN으로 두는 방법 (soft delete)
  3. 삭제 데이터는 삭제 테이블로 이동한다.
  • 김영한님 의견
    @sql, @where 등을 실무에서 사용하기가 많이 애매합니다.
    왜냐하면 실무에서는 경우에 따라서 실제 어떤 데이터가 삭제되었는지 삭제된 데이터도 조회할 수 있어야 하기 때문입니다.

📌그래서 soft delete 방법을 사용하더라도 좀 불편해도
JPQL에서 삭제 데이터를 제외하고 조회하거나 애플리케이션에서 제외합니다.


Q5. 회원은 민감한 정보라 실제 삭제를 할때 관련된 연관관계가 있는 엔티티는 어떻게 처리하는게 좋을지 예를들면 member에 null값으로 그냥 냅두는지

A5. 비즈니스 상황에 따라서 다릅니다. soft delete를 하거나 또는 연관관계를 만들지 않는 방법도 있습니다.
회원 탈퇴의 경우 회원을 직접 삭제하는 것이 아니라 회원의 상태가 DELETE로 변경될 수도 있습니다.
그런데 법적으로 탈퇴 회원을 분리해서 보관해야 하는 요구사항이 있다면 실제 회원 데이터를 삭제해야 합니다.
그리고 연관된 데이터는 비즈니스 요구사항에 따라서 연관관계를 끊은 상태로 남겨둘지 아니면 함께 삭제할지 결정해야 합니다.

deletedDateTime은 2가지 역할을 하고 있었습니다.
해당 데이터가 삭제되었는지, 삭제되지 않았는지를 판별하고
삭제된 시간을 기록하고 있었습니다.
이 2가지 역할은 서로 분리하는 것이 좋다는 피드백을 들었습니다.
또한, null은 index에 사용할 수 없기 때문에 null이 들어가는 column으로 FLAG 역할을 하는 것보다 DeletedSales와 같은 별도의 테이블을 만들어 삭제된 Sales를 쌓는것이 좋다는 피드백을 들었습니다.

@Where(clause = "delete = 0")
@Override
    public void softDelete() {
        super.softDelete();
        orderDetails.softDelete();
    }

적용 방법

1. 어노테이션 생성

Java에서 소프트 삭제를 구현하려면, 커스텀 어노테이션을 생성하고 해당 어노테이션을 사용하여 엔티티의 소프트 삭제 상태를 나타내는 필드를 표시할 수 있습니다. 다음은 예시 코드입니다.

먼저, @Retention(RetentionPolicy.RUNTIME)과 @Target(ElementType.FIELD)를 사용하여 SoftDelete라는 어노테이션을 정의합니다. 이렇게 하면 어노테이션을 필드에 적용할 수 있습니다.

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SoftDelete {
}

다음으로, 이 어노테이션을 사용하여 엔티티의 소프트 삭제 상태를 나타내는 필드를 표시할 수 있습니다.

public class MyEntity {
    @Id
    private Long id;
    
    private String name;
    
    @SoftDelete
    private boolean deleted;
    
    // getters and setters
}

위의 코드에서는 MyEntity 클래스에 id, name, 그리고 deleted 필드를 정의하고, @SoftDelete 어노테이션을 사용하여 삭제 상태를 표시합니다.

리포지토리나 서비스 레이어에서 소프트 삭제를 처리하려면, Hibernate의 @Where 어노테이션을 사용하여 삭제된 엔티티를 필터링할 수 있습니다. 예를 들어, 다음과 같이 구현할 수 있습니다.

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
    @Query("SELECT e FROM MyEntity e WHERE e.deleted = false")
    List<MyEntity> findAll();
}

위 코드에서는 @Query 어노테이션을 사용하여 소프트 삭제되지 않은 모든 엔티티를 조회합니다. e.deleted = false 조건을 사용하여 삭제된 엔티티를 필터링합니다.

Java에서 어노테이션을 사용하여 소프트 삭제를 구현하는 방법은 위의 예시 코드와 다를 수 있습니다. 구체적인 요구사항과 사용하는 기술에 따라 구현 방법이 달라질 수 있습니다.


@Where 어노테이션을 사용하는 경우, 몇 가지 주의해야 할 사항이 있습니다.

@Where 어노테이션은 JPA 구현체에서 지원하는 기능이므로, 모든 JPA 구현체에서 동일하게 동작하는 것은 아닙니다. 예를 들어 Hibernate에서는 @Where 어노테이션을 지원하지만, EclipseLink에서는 지원하지 않습니다.

@Where 어노테이션을 사용하면, 해당 엔티티의 모든 조회 쿼리에 조건절이 추가되므로 성능 저하의 가능성이 있습니다. 따라서, 특정 쿼리에 대해서는 @Where 어노테이션을 사용하지 않는 것이 더 효율적일 수 있습니다.

@Where 어노테이션을 사용하면, 소프트 삭제된 엔티티를 복구하는 것이 어렵습니다. @Where 어노테이션을 사용하면, 실제로 데이터베이스에서 데이터가 삭제되지 않기 때문에, 복구할 수 있는 방법이 제한됩니다.

따라서, @Where 어노테이션을 사용할 때에는 위와 같은 주의사항을 숙지하고 사용해야 합니다. 또한, 소프트 삭제된 엔티티를 복구할 필요가 있는 경우에는, @Where 어노테이션 대신에 소프트 삭제 상태를 나타내는 열을 직접 조건절로 사용하는 방법을 고려해볼 수 있습니다.


@Where 조회 쿼리 성능 최적화

@Where 어노테이션을 사용하면, 해당 엔티티의 모든 조회 쿼리에 조건절이 추가되므로 성능 저하의 가능성이 있습니다. 따라서, @Where 어노테이션을 사용하는 경우에는 아래와 같은 방법으로 성능을 최적화할 수 있습니다.

인덱스 추가: @Where 어노테이션에 의해 추가된 조건절의 컬럼에 인덱스를 추가합니다. 인덱스를 추가하면 데이터베이스에서 레코드를 검색할 때, 조건절에 사용된 컬럼으로 검색할 수 있으므로 성능이 향상됩니다.

@Entity
@Table(name = "my_entity")
@Where(clause = "deleted = false")
public class MyEntity {
    // ...

    @Column(name = "deleted", nullable = false)
    private boolean deleted;

    // ...

    @Table(indexes = @Index(name = "idx_deleted", columnList = "deleted"))
    public class MyEntity {
    // ...
}

Named Entity Graph 사용: @Where 어노테이션 대신에 Named Entity Graph를 사용하여 특정 쿼리에만 소프트 삭제를 적용할 수 있습니다. Named Entity Graph는 엔티티 그래프를 정의하여, 특정 쿼리에서 필요한 데이터만 로딩하는 기능을 제공합니다.

@Entity
@Table(name = "my_entity")
@NamedEntityGraph(name = "MyEntity.noDeleted", attributeNodes = {})
public class MyEntity {
    // ...

    @Column(name = "deleted", nullable = false)
    private boolean deleted;

    // ...
}
@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
    @Query("select m from MyEntity m where m.deleted = false")
    List<MyEntity> findAll();

    @EntityGraph(value = "MyEntity.noDeleted", type = EntityGraphType.LOAD)
    List<MyEntity> findAllWithNoDeleted();
}

위와 같이 Named Entity Graph를 정의하고, findAllWithNoDeleted() 메소드에서 @EntityGraph 어노테이션을 사용하여 Named Entity Graph를 적용하면, 소프트 삭제 상태를 무시하고 엔티티를 로딩할 수 있습니다.

이렇게 성능 최적화를 적용하면, @Where 어노테이션을 사용하더라도 성능 저하를 최소화할 수 있습니다.


참고

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글