[JPA] Delete 정책 (soft delete + bulk delete)

나르·2022년 2월 16일
2

Spring

목록 보기
14/25
post-thumbnail

📌 데이터를 어떻게 삭제할 것인가?

데이터베이스에서 데이터를 삭제할 때는, 크게 두가지 방식을 취할 수 있습니다.

  • hard delete (물리 삭제): DELETE 명령어를 사용하여 직접 데이터를 삭제하는 방법
  • soft delete (논리 삭제): UPDATE 명령어를 사용하여 삭제 여부를 알수 있는 컬럼에 데이터가 삭제되었다는 값을 넣어서 표현하는 방법

실제로는 삭제된 데이터를 복구해야 하는 상황이 올 수도 있고, 당장은 필요하지 않지만 후에 사용될 수도 있는 데이터들이 있기 때문에 물리 삭제는 권장하지 않습니다.
때문에 주로 삭제를 의미하는 컬럼을 생성해, 논리 삭제를 진행합니다.

여기서 삭제를 의미하는 컬럼은 boolean, date, bit(0,1) 등 여러 가지 방식으로 채택할 수 있습니다.

- boolean / smallint(tinyint)

deleted 등 삭제 여부에 관한 flag 컬럼을 두는 방식입니다.

#idnamerolecreatedAtdeleted
1koiiladmin2022-02-22false (0)
2jpauser2022-02-22true (1)

- enum

alive, deleted 등 state 컬럼을 두어 상태를 등록하는 방식입니다. 삭제 뿐만 아니라 휴면 등 여러 가지로 확장 가능합니다.

#idnamerolecreatedAtstate
1koiiladmin2022-02-22alive
2jpauser2022-02-22deleted
3joauser2022-02-22dormant

- time stamp / date time

default=null 인 deletedAt 컬럼을 만들고, 삭제 시 해당 컬럼에 time을 update하는 방식입니다.

#idnamerolecreatedAtdeletedAt
1koiiladmin2022-02-22
2jpauser2022-02-222022-02-03

📌 soft delete

JPA 에서 제공하는 @SQLDelete@Where 어노테이션을 이용하여 간편하게 논리 삭제를 적용할 수 있습니다.

@SQLDelete(sql = "update posts set deleted_at=now() where id=?")
@Where(clause = "deleted_at is null")

@SQLDelete 는 repository.delete를 실행했을 때 생성되는 쿼리를 지정할 수 있습니다.
@Where 은 해당 엔티티의 기본 쿼리에 디폴트로 where 절을 적용하는 어노테이션입니다.
where에 논리 삭제되지 않은 레코드를 필터로 걸어두면, select 쿼리 시 논리 삭제 된 레코드는 제외됩니다.

📌 bulk delete

하지만 JPA Repository를 이용한 delete 에는 문제점이 있습니다.

1:N의 Join 관계에서 1의 id를 가지고 N의 레코드를 삭제하면, JPA는 N번의 delete 쿼리를 날리게 됩니다.
데이터가 적을 경우에는 크게 체감이 되지 않지만, 하나에 연관된 데이터가 수백, 수천개가 넘는다면 그만큼의 쿼리를 보내는 것은 성능 상의 이슈를 야기할 것입니다.

때문에 jpa의 설계가 주는 데이터에 대한 신뢰도와, 직접 쿼리를 작성해 bulk delete를 하는 성능 중 서비스에 맞는 정책을 적용하는 것이 좋습니다.

bulk delete를 하는 방법에는 두 가지가 있습니다.

- deleteInBatch


JpaRepository 에서 제공하는 deleteInBatch 를 사용하면 다중 쿼리의 문제를 해결할 수 있습니다.
기존 delete 처럼 find -> get id -> delete X n이 아니라
delete where id=? or id=? or id=?... 이기 때문에 한번의 쿼리로 대량의 삭제가 가능합니다.
하지만 entity 혹은 id list를 인자로 받아야 하기 때문에 결국 최소 한번은 엔티티에 접근해야 합니다.

작성일 기준 deleteInBatch는 Deprecated 되고 deleteAllInBatch(Iterable<T> entities) 로 대체되었습니다.

- @Query@Modifying

@Query로 커스텀 쿼리를 생성하는 방법입니다.

@Modifying 은 영속성 컨텍스트에 캐싱된 데이터와 관련된 어노테이션입니다.

JPA 에서 조회를 실행할 시에 영속성 컨텍스트에 있는 1차 캐시를 확인해서 해당 엔티티가 1차 캐시에 존재한다면 DB에 접근하지 않고, 1차 캐시에 있는 엔티티를 반환합니다.

하지만 벌크 연산은 1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 Query를 실행하기 때문에 영속성 컨텍스트는 데이터 변경을 알 수가 없습니다.

때문에 벌크 연산 실행 시, 1차 캐시(영속성 컨텍스트)와 DB의 데이터 싱크가 맞지 않게 되기 때문에 @Modifying 어노테이션을 추가해줘야 합니다.

또한 @Modifying 을 사용할 때는 clearAutomatically=true, flushAutomatically=true 속성을 주어, 연산 직 후 자동으로 영속성 컨텍스트를 clear하고 DB 에 flush 할 것인지를 명시해 주는 것이 좋습니다.

public interface LikeRepository extends JpaRepository<Like,Long> {

    @Modifying(clearAutomatically = true, flushAutomatically = true)
    @Query("delete from Like l where l.postId = :postId")
    void deleteAllByPostId(Long postId);

}

Ref.

How to Implement a Soft Delete with Spring JPA
[JPA] Qna 미션을 통해 새롭게 배운 내용 정리
Spring Data JPA deleteInBatch() Example
Spring Data JPA @Modifying Annotation
JPA에서 대량의 데이터를 삭제할때 주의해야할 점
Spring Data JPA @Modifying

profile
💻 + ☕ = </>

0개의 댓글