[JPA] soft delete 자동으로 처리하기
데이터를 삭제하는 방법에는 hard delete
, soft delete
2가지 종류가 있습니다.
hard delete
는 delete 쿼리를 날려서 데이터베이스에서 실제로 삭제하는 방법을 말합니다.
soft delete
는 실제로 데이터베이스에서 데이터를 삭제하는 것이 아니라, 테이블에 deleted와 같은 필드를 추가해주고, update 쿼리를 날려서 deleted 값을 변경해주는 방법입니다.
soft delete
를 한 경우 조회 쿼리 결과로 삭제 처리된 값이 반환되면 안되기 때문에 where deleted = false
과 같은 조건을 추가해주거나 어플리케이션 단에서 삭제되지 않은 데이터만 필터링한다던지 하는 작업이 필요합니다. 하지만 모든 조회 로직 하나하나 직접 조건을 달아준다면 빼먹을 수도 있습니다.
JPA의 구현체인 하이버네이트에는 아래 2가지 기능이 있습니다.
1) 삭제시 delete 쿼리 대신 다른 구문 실행
2) 어떤 엔티티를 조회하는 모든 쿼리에 where 조건을 추가해주는 기능
이를 이용하면 soft delete처리와 삭제되지 않은 데이터 조회를 편리하게 할 수 있습니다.
예시
아래와 같은 엔티티를 예시로 들어보겠습니다. 처음 생성시 삭제 여부 기본값을 false로 줍니다.
@Entity
public class Shop {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
먼저 원래 삭제를 시도하면 아래와 같이 처리됩니다. delete 호출 이후에 findById로 삭제된 Shop을 조회하려고하면 실제 데이터베이스에 저장된 값이 삭제되었기 때문에 optional이 비어있게됩니다.
@Test
@DisplayName("Shop soft delete")
public void delete() {
Shop shop = new Shop("가게이름", "대구광역시");
Shop savedShop = shopRepository.save(shop);
assertThat(savedShop.getId()).isNotNull();
assertThat(savedShop.isDeleted()).isFalse();
shopRepository.delete(savedShop);
entityManager.flush();
Optional<Shop> afterDelete = shopRepository.findById(savedShop.getId());
assertThat(afterDelete).isEmpty();
}
delete 쿼리가 발생하는 것도 볼 수 있습니다.
delete
from
shop
where
id=?
@SQLDelete
삭제 로직이 수행되면 delete 쿼리 대신, update 쿼리를 통해 deleted 필드 값만 true로 변경시켜줘야합니다. 이때 @SQLDelete
를 사용할 수 있습니다.
@SQLDelete
는 엔티티 삭제가 발생했을 때 delete 쿼리 대신 실행시켜줄 커스텀 sql 구문을 뜻하는 어노테이션입니다. 이렇게 @SQLDelete
를 적어주면 엔티티 삭제 요청시 delete 쿼리 대신 적어준 update 쿼리가 실행됩니다.
@Entity
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
public class Shop {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
아까 했던 테스트를 실행시켜보겠습니다. 실제 delete를 하지 않았기 때문에 optional이 비어있지 않게되고, deleted값이 true로 변경된 것을 확인할 수 있습니다.
@Test
@DisplayName("Shop soft delete")
public void delete() {
Shop shop = new Shop("가게이름", "대구광역시");
Shop savedShop = shopRepository.save(shop);
assertThat(savedShop.getId()).isNotNull();
assertThat(savedShop.isDeleted()).isFalse();
shopRepository.delete(savedShop);
entityManager.flush();
Optional<Shop> afterDelete = shopRepository.findById(savedShop.getId());
assertThat(afterDelete).isNotEmpty();
assertThat(afterDelete.get().isDeleted()).isTrue();
}
update 쿼리가 발생한 것도 볼 수 있습니다.
UPDATE
shop
SET
deleted = true
WHERE
id = ?
*SQLDelete는 영속성컨텍스트에서 관리되다가 트랜잭션이 끝나고 실제 DB에 쿼리를 보낼때 처리를 해준다고 합니다.
@Where
soft delete 처리를 하는 경우 조회 요청시 삭제처리되지 않은 데이터만 가져와야합니다. 이때 @Where
을 이용할 수 있습니다.
@Where
어노테이션은 기본적으로 적용할 where 구문을 뜻하는 어노테이션입니다. 일반적으로 soft delete를 할 때 사용합니다.
@SQLDelete
와 마찬가지로 엔티티 위에 적어주면 됩니다. 이렇게 하면 이 엔티티를 조회하는 모든 요청에 default 옵션으로 적용됩니다.
@Entity
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class Shop {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
이렇게 한 후 조회를 하게 된다면(ex. shopRepository.findAll()
) 아래와 같이 자동으로 where 조건이 추가된 것을 볼 수 있습니다.
select
shop0_.id as id1_11_,
shop0_.address as address2_11_,
shop0_.deleted as deleted3_11_,
shop0_.name as name4_11_
from
shop shop0_
where
(
shop0_.deleted = false
)
상속 관계인 경우
soft delete를 처리하려는 엔티티가 상속 관계인 경우가 있다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Shop {
}
@Entity
@DiscriminatorValue("C")
public class Restaurant extends Shop {
}
@Entity
@DiscriminatorValue("R")
public class Cafe extends Shop {
}
이런 경우 부모클래스에만 @SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
처리를 해주면 자식 테이블에서는 실제 삭제가 일어나게 된다.
아래와 같이 각 자식 클래스에 @OnDelete(action = OnDeleteAction.CASCADE)
처리를 해줘서 막아줄 수 있다.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
public abstract class Shop {
}
@Entity
@DiscriminatorValue("C")
@OnDelete(action = OnDeleteAction.CASCADE)
public class Restaurant extends Shop {
}
@Entity
@DiscriminatorValue("R")
@OnDelete(action = OnDeleteAction.CASCADE)
public class Cafe extends Shop {
}
reference
- https://www.baeldung.com/spring-jpa-soft-delete
- https://steady-coding.tistory.com/579#%EA%B3%84%EC%B8%B5%ED%98%95_%EC%97%94%ED%8B%B0%ED%8B%B0%EC%97%90%EC%84%9C_%EC%A3%BC%EC%9D%98%ED%95%A0_%EC%A0%90
Author And Source
이 문제에 관하여([JPA] soft delete 자동으로 처리하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@max9106/JPA-soft-delete저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)