개요
개인 프로젝트에서 데이터 삭제 시 hard delete를 통해 실제로 해당 데이터를 DB에서 지우는 방식을 선택했지만, 해당 방식은 추후 삭제된 데이터를 복구해야 하는 하는 경우에 문제가 될 수 있다 판단했다. 그래서 실제 데이터를 삭제하는 대신, soft delete를 통해 deleted와 같은 필드를 따로 만들어 삭제 여부를 확인할 수 있도록 변경하기로 했다.
이 글에서는 현재 프로젝트에서 해당 기능을 JPA에서 어떻게 적용하였는지 설명한다.
Soft Delete 적용하기
삭제 여부 필드 추가
Soft delete를 적용하기 위해서는 제일 먼저 삭제 여부 필드를 entity에 생성해야 한다. 하지만, 모든 entity에 일일이 해당 필드를 생성하는 것은 매우 불편하다고 생각했기 때문에, 아래와 같이 @MappedSuperClass
를 사용한 추상 클래스를 먼저 만들었다.
@Getter
@MappedSuperclass
public abstract class DeletionCheckEntity {
private boolean deleted;
}
위의 DeletionCheckEntity
클래스는 deleted
라는 삭제 여부 필드를 갖고 있다. 이제 모든 entity 클래스가 이 클래스를 상속받도록 설정하면 되는데, 현재 모든 entity는 BaseTimeEntity
라는 추상 클래스를 상속받고 있기 때문에 아래와 같이 BaseTimeEntity
가 DeletionCheckEntity
를 상속받게만 만들어주었다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity extends DeletionCheckEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
삭제 방식 변경
삭제 여부 필드가 만들어졌으니, 다음으로 해야 할 일은 삭제 방식을 변경하는 것이다. 기존에 적용했던 hard delete는 DB에 DELETE 쿼리를 전달해 실제로 데이터를 삭제하는 방식이었지만, 이제는 이 방식이 아니라 UPDATE 쿼리를 통해 deleted 필드를 true로 수정하는 방식을 선택해야 한다.
Hibernate는 해당 기능을 편리하게 적용할 수 있는 @SQLDelete
라는 어노테이션을 제공한다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SQLDelete(sql = "UPDATE category SET deleted = true WHERE id = ?")
@Entity
public class Category extends BaseTimeEntity {
...
}
위와 같이 entity 클래스에 @SQLDelete
을 붙이고, native sql 구문을 적어주었다. 이렇게 하면 해당 entity에 대해 DELETE 쿼리가 수행될 경우, 해당 쿼리 대신에 해당 어노테이션에 적은 SQL문이 적용되도록 변경된다.
@SQLDelete를 통해 변경된 쿼리도 마찬가지로 바로 적용되는 것이 아닌, 영속성 컨텍스트에서 변경사항이 관리되다 트랜잭션이 끝난 후 commit되는 경우에 적용된다.
쿼리 방식 변경
데이터를 쿼리하는 방식도 마찬가지로 변경해야 한다. 일반적인 조회 및 수정의 경우, 삭제된 데이터는 해당되지 않으므로 deleted = true
로 되어있는 row는 쿼리 대상에서 배제해야 한다. 따라서, SELECT * FROM category WHERE deleted = false;
와 같이 매 쿼리마다 삭제 여부를 확인하는 조건을 붙여야 한다.
이 기능 또한 Hibernate에서 편리하게 적용할 수 있도록 @SQLRestriction
이라는 어노테이션을 제공한다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SQLDelete(sql = "UPDATE category SET deleted = true WHERE id = ?")
@SQLRestriction("deleted = false")
@Entity
public class Category extends BaseTimeEntity {
...
}
위와 같이 entity 클래스에 @SQLRestriction
을 붙이고 deleted = false
조건을 적어주었다. 이렇게 하면 해당 entity에 수행되는 모든 쿼리에는 어노테이션에 적은 조건문이 붙게 된다. 따라서, 삭제되지 않은 데이터에만 쿼리가 적용될 수 있게 변경할 수 있다.
참고로,
@SQLRestriction
은 Hibernate ORM 6.3 버전에 도입된 어노테이션이다. 이전에는@Where
라는 어노테이션이 있었지만, 해당 어노테이션은 6.3 버전부터 deprecated 되었기 때문에,@SQLRestriction
를 대신 사용한다.
마무리
이렇게 Hibernate가 제공하는 어노테이션들로 soft delete를 편리하게 구현할 수 있었다. 더 유연한 기능을 제공하는 @Filter
나 @FilterDef
라는 어노테이션도 있으니 필요하다면 찾아보는 것도 좋을 것이다.
참고
https://www.baeldung.com/spring-jpa-soft-delete
Is there a replacement for the in 6.3 deprecated @Where and @Loader
Since hibernate 6.3 org.hibernate.annotations.Where and Loader are deprecated. We are using these annotations together with @NamedQuery and @SQLDelete to implement soft deletion. What is de non-
stackoverflow.com
'JPA' 카테고리의 다른 글
[JPA] 임베디드 타입으로 엔티티 개선하기 (0) | 2024.07.19 |
---|---|
[Spring /JPA] Spring JPA Delete Query 작성 시 주의할 점 (0) | 2023.12.26 |
[JPA] JPA Auditing (0) | 2023.12.24 |
[JPA] 지연 로딩과 즉시 로딩 (0) | 2023.12.24 |
[JPA] JPA 연관 관계 정리 (0) | 2023.12.24 |