Hard Delete -> Soft Delete 리펙토링

김건우·2023년 1월 3일
0

Spring Data JPA

목록 보기
7/11
post-thumbnail

JPA에서의 DELETE 방법

DB의 삭제 구현 방법은 두가지가 있습니다.
1. Soft Delete
데이터를 실제로 삭제하지 않고, 삭제 flag를 변경하는 방법입니다.
2. Hard Delete
실제로 데이터를 삭제하는 방법입니다.

hard delete는 delete 쿼리를 날려서 데이터베이스에서 실제로 삭제하는 방법을 말합니다. soft delete는 실제로 데이터베이스에서 데이터를 삭제하는 것이 아니라, 테이블에 deleted와 같은 필드를 추가해주고, update 쿼리를 날려서 deleted 값을 변경해주는 방법입니다.
soft delete를 한 경우 조회 쿼리 결과로 삭제 처리된 값이 반환되면 안되기 때문에 where deleted = false과 같은 조건을 추가해주거나 어플리케이션 단에서 삭제되지 않은 데이터만 필터링한다던지 하는 작업이 필요합니다. 하지만 모든 조회 로직 하나하나 직접 조건을 달아준다면 빼먹을 수도 있습니다.
JPA의 구현체인 하이버네이트에는 아래 2가지 기능이 있습니다.
1) 삭제시 delete 쿼리 대신 다른 구문 실행
2) 어떤 엔티티를 조회하는 모든 쿼리에 where 조건을 추가해주는 기능
이를 이용하면 soft delete처리와 삭제되지 않은 데이터 조회를 편리하게 할 수 있습니다.

Post 엔티티 적용

@Getter
@Entity
@NoArgsConstructor
@Where(clause = "deleted = false")
@SQLDelete(sql = "UPDATE Post SET deleted = true WHERE post_id = ?")
public class Post extends BaseEntity{

    @Id
    @GeneratedValue
    @Column(name = "post_id")
    private Long id;

    private String title;

    private String body;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id") // 연관관계의 주인
    private User user;

    @OneToMany(mappedBy = "post",cascade = REMOVE, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "post",cascade = REMOVE, orphanRemoval = true)
    private List<LikeEntity> likes = new ArrayList<>();

    /**SoftDeleteColumn**/
    @Column(name = "deleted")
    private boolean deleted = Boolean.FALSE;


    /**SoftDelete**/
    public void delete() {
        this.deleted = true;
    }

}

@SQLDelete

삭제 로직이 수행되면 delete 쿼리 대신, update 쿼리를 통해 deleted 필드 값만 true로 변경시켜줘야합니다. 이때 @SQLDelete를 사용할 수 있습니다. @SQLDelete는 엔티티 삭제가 발생했을 때 delete 쿼리 대신 실행시켜줄 커스텀 sql 구문을 뜻하는 어노테이션입니다. 이렇게 @SQLDelete를 적어주면 엔티티 삭제 요청시 delete 쿼리 대신 적어준 update 쿼리가 실행됩니다.

Soft-Delete 사용

/**글 삭제**/
    public PostDeleteResponse deletePost(Long postId, String userName) {
        Optional<Post> optionalPost = postRepository.findById(postId);
        Post post =
                optionalPost.orElseThrow(() -> new PostException(ErrorCode.POST_NOT_FOUND, postId + "번 글은 존재하지 않아서 삭제할 수 없습니다."));

        User user = userRepository.findOptionalByUserName(userName)
                .orElseThrow(() -> new UserException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s not founded", userName)));

/**여기다 !!! 엔티티 클래스에 @SQLDelete(sql = "UPDATE Post SET deleted = true WHERE post_id = ?")의 작동되는 부분**/
        postRepository.delete(post);
        PostDeleteResponse deleteResponse = new PostDeleteResponse("포스트 삭제 완료", post.getId());
        return deleteResponse;
    }

DELETE 구문이 ▶ UPDATE 구문으로

예를 들어 postId가 70 번인 글이 존재하고 delete를 적용한다면 다음과 같은 SQL구문으로 나가게됩니다.원래는 DELETE구문이 나가야하는데 @SQLDelete(sql = "UPDATE Post SET deleted = true WHERE post_id = ?")를 적용해주어서 다음과같은 쿼리문이 나오게됩니다. 🔽

Hibernate: 
    UPDATE
        Post 
    SET
        deleted = true 
    WHERE
        post_id = ?

@Where의 필요성

soft delete 처리를 하는 경우 조회 요청시 삭제처리되지 않은 데이터만 가져와야합니다.(soft delete를 사용하면 실제로 DB의 삭제가 되는 것이 아니기 때문입니다) 이때 @Where을 이용할 수 있습니다.@Where어노테이션은 기본적으로 적용할 where 구문을 뜻하는 어노테이션입니다. 일반적으로 soft delete를 할 때 사용합니다. @SQLDelete와 마찬가지로 엔티티 위에 적어주면 됩니다. 이렇게 하면 이 엔티티를 조회하는 모든 요청에 default 옵션으로 적용됩니다.

  • 저는 post엔티티 위에 @Where(clause = "deleted = false")어노테이션을 붙여주어서 아래와 같이 sql구문 조건에 where( post0_.deleted = 0 ) 이 붙은 것을 볼 수 있습니다. 🔽
select
        post0_.post_id as post_id1_3_,
        post0_.registeredAt as register2_3_,
        post0_.updatedAt as updateda3_3_,
        post0_.body as body4_3_,
        post0_.deleted as deleted5_3_,
        post0_.title as title6_3_,
        post0_.user_id as user_id7_3_ 
    from
        Post post0_ 
    where
        (
            post0_.deleted = 0 
        ) 
    order by
        post0_.registeredAt desc limit ?
profile
Live the moment for the moment.

0개의 댓글