03. Soft Delete 더 편하게 하기

들어가며
DB를 만들면서 우리는 삭제 하는 방법은 두 가지 방법이 있다.
방식 설명
Soft Delete
UPDATE table SET delete = 1 WHERE id = 2 형태로
ROW가 삭제 되지 않고 flag를 통한 제어 하는 방식
Hard Delete DELETE FROM table WHERE id = 2 형태로 ROW가 실제로 삭제 되는 방식

Soft Delete로 버그가 생긴다면, 대략 정신이 멍해진다.
Hard Delete를 하는 경우도 있지만, Soft Delete를 하는 경우가 더 많다.
이런 경우에 JPA Entity Graph 의 경우는 Soft Delete시 데이터가 같이 담겨서 온다.
Application level에서 필터 하면 되는거 아니냐? 라고 생각 할 수 있지만,** 인간이기 때문에 로직에 구멍이 많이 생길 수** 있다.
오늘은 JPA에서 Soft Delete를 더욱 편하게 하는 방법에 대해 이야기 해보려고 한다.
@Where Annotation
https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/annotations/Where.html
[
Where (Hibernate JavaDocs)
docs.jboss.org
](https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/annotations/Where.html) " Where clause to add to the element Entity or target entity of a collection. The clause is written in SQL. A common use case here is for soft-deletes." ****
@Where 어노테이션은 Entity의 Default 조건을 지정 할 수 있는 어노테이션 이다.
장점은 'Soft Delete'로 인하여, 객체 그래프에서 발생 할 수 있는 문제를 신경쓰지 않아도 된다.
또한,@OneToMany, @ManyToOne 등의 조인이 걸린 경우에도 따로 쿼리 조건을 지정 할 수 있기 때문에 편해진다.
단점은, 무조건 조건이 적용 되기 때문에, 삭제된 걸 가져와야 하는 상황에는 또 애매해진다.
(이 경우는 @FilterDef을 활용하면 된다고 하는데, 조만간 따로 다뤄볼려고 한다)
(그리고 개인적으론 삭제에 대한걸 다시 가져오는 것도 모순 아닐까.. 싶은데..)
예를 들어 아래와 같은 Entity가 있다는 가정을 해보자.
@Entity
@Where("del=0")
class Product(
var name: String,
val del: Boolean = false
) {
@Id
@GeneratedValue
var id: Long? = null
protected set
@ManyToOne
val image: Image? = null
fun expire() {
this.del = true
}
}
이 데이터가 id=1 이고, del=true 상황이라면, productRepository.findById(1) 로 조회시 데이터가 없다고 반환 되게 된다.
@SQLDelete Annotation

조회를 할때는 기본적인 조건이 붙었기 때문에 무리 없이 사용 할 수 있다.
하지만 인간의 욕심은 끝이 없기 때문에, 삭제가 될때도 flag를 직접 변경 하는 것이 아닌 ORM Level에서 지원을 해줬으면 좋겠다 라는 생각이 들기 시작한다. 그것을 해결 하기 위해 나온것이** @SQLDelete 어노테이션**이다.
"Custom SQL statement for delete of an entity/collection."
[
SQLDelete (Hibernate JavaDocs) check For persistence operation what style of determining results (success/failure) is to be used. docs.jboss.org
](https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/annotations/SQLDelete.html)
아래의 예시를 보자
@Entity
@Where("del=0")
@SQLDelete("UPDATE product SET del=1 WHERE id = ?")
class Product(
var name: String,
val del: Boolean = false
) {
@Id
@GeneratedValue
var id: Long? = null
protected set
@ManyToOne
val image: Image? = null
}
@SQLDelete 어노테이션을 통해, 실제 삭제가 이루어질때 수행 할 쿼리를 적으면 된다.
그럼 productRepository.deleteById(1L) 형태로 진행이 되면 'DELETE FROM product WHERE id = 1' 형태로 쿼리가 동작 하는 것이 아닌 'Update product SET del 1 WHERE id = 1' 형태로 쿼리가 발생되어 동작하게 된다.