πŸ“š[Spring] Soft Delete

텁텁·2025λ…„ 5μ›” 29일

논리적 μ‚­μ œ(Soft Delete)

DBμ—μ„œ μ‹€μ œ 데이터λ₯Ό μ‚­μ œν•˜λŠ”κ²ƒμ΄ μ•„λ‹Œ deleted 와 같은 μ‚­μ œ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν•„λ“œλ₯Ό
μΆ”κ°€ν•˜μ—¬ λ°μ΄ν„°μ˜ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 방법이닀.

public abstract class BaseEntity {
    ...
    @Column(nullable = false)
    private Boolean deleted = false;
    ...
}

BaseEntity 에 ν•΄λ‹Ή ν•„λ“œλ₯Ό μΆ”κ°€ν•˜μ—¬ λ°μ΄ν„°μ˜ μƒνƒœλ₯Ό μΆ”μ ν•˜λ„λ‘ μ„€κ³„ν–ˆλ‹€.


μ‚­μ œ 둜직 λ³€κ²½

κΈ°μ‘΄ μ‚­μ œ λ©”μ„œλ“œ

public void delete(Long id, CustomUserDetails userDetails) {
    Post foundPost = postDomainUtils.validateUserAccessToPost(id, userDetails);
    postRepository.delete(foundPost); // DBμ—μ„œ 데이터가 μ‚­μ œλ¨
}

JPA Repository의 κΈ°λ³Έ delete λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ μ‹€μ œ DB 데이터가 μ‚­μ œλ˜κΈ°μ—
논리적 μ‚­μ œλ₯Ό μ μš©ν•  경우 μ•„λž˜μ™€ 같이 μ‚­μ œ λ©”μ„œλ“œμ˜ 변경이 ν•„μš”ν•˜λ‹€.

//service
@Transactional
public void delete(Long id, CustomUserDetails userDetails) {
    Post foundPost = postDomainUtils.validateUserAccessToPost(id, userDetails);
    foundPost.setDeleted(true); // μƒνƒœκ°’ λ³€κ²½μœΌλ‘œ 논리적 μ‚­μ œ 처리
}

μ—”ν‹°ν‹°μ˜ deleted ν•„λ“œλ₯Ό true 둜 λ³€κ²½ν•˜λŠ” λ°©μ‹μœΌλ‘œ
JPA 의 더티체킹(dirty checking)에 μ˜ν•΄ 변경사항이 μžλ™μœΌλ‘œ DB에 λ°˜μ˜λœλ‹€.


데이터 쑰회 방법

논리적 μ‚­μ œ μ‹œ DBμ—λŠ” 데이터가 κ·ΈλŒ€λ‘œ μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— findById(id)와 같이 κΈ°λ³Έ 쑰회 λ©”μ„œλ“œ 호좜 μ‹œ μ‚­μ œλœ 데이터도 쑰회될 수 μžˆλ‹€.
λ”°λΌμ„œ 논리적 μ‚­μ œλ₯Ό μ μš©ν•˜λ©΄ 쑰회 λ‘œμ§μ„ λ³€κ²½ν•΄μ•Ό ν•œλ‹€.

λ‹€μŒ 두 κ°€μ§€ 방법이 λŒ€ν‘œμ μ΄λ‹€.


  1. @Where μ–΄λ…Έν…Œμ΄μ…˜ ν™œμš©
...
@Where(clause = "deleted = false")
public class Post extends BaseEntity { ... }

ν•΄λ‹Ή μ–΄λ…Έν…Œμ΄μ…˜μ„ 뢙이면 Hibernate κ°€ μ—”ν‹°ν‹° 쑰회 μ‹œ 항상 deleted = false 쑰건을 μžλ™μœΌλ‘œ μΆ”κ°€ν•΄μ€€λ‹€.
κ°„λ‹¨ν•˜κ³  νŽΈλ¦¬ν•˜μ§€λ§Œ Hibernate μ „μš© κΈ°λŠ₯이고 졜근 λ²„μ „μ—μ„œ deprecated λ˜μ–΄ μ‚¬μš©μ΄ ꢌμž₯λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 단점이 μžˆλ‹€.

λ„ˆλ¬΄ μˆ¨κ²¨μ§„ 필터라 디버깅이 νž˜λ“€μ–΄ 더 λͺ…μ‹œμ μΈ 쿼리 필터링 방식을 ꢌμž₯함


  1. λͺ…μ‹œμ  쑰건을 ν¬ν•¨ν•œ λ©”μ„œλ“œ μ„ μ–Έ
public interface PostRepository extends JpaRepository<Post, Long> {
    Optional<Post> findByIdAndDeletedFalse(Long id);
}

쑰회 λ©”μ„œλ“œμ— deleted = false 쑰건을 λͺ…μ‹œμ μœΌλ‘œ ν¬ν•¨μ‹œμΌœμ„œ μ‘°νšŒν•œλ‹€.
JPA ν‘œμ€€ 방식이고 deprecated λ¬Έμ œλ„ μ—†μœΌλ©° λ©”μ„œλ“œ 이름에 쑰건이 λͺ…ν™•ν•΄ 가독성과 디버깅에 μœ λ¦¬ν•˜λ‹€.
단 μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œλ§ˆλ‹€ 쑰건을 직접 μΆ”κ°€ν•΄μ•Ό ν•˜λŠ” 단점이 μžˆλ‹€.


정리

방법μž₯점단점
@Where μ–΄λ…Έν…Œμ΄μ…˜μžλ™ 필터링, 간단함Hibernate μ „μš©, deprecated됨
λͺ…μ‹œμ  쑰건 λ©”μ„œλ“œ μ„ μ–ΈJPA ν‘œμ€€, 가독성 μ’‹μŒ, deprecated 문제 μ—†μŒλ§€λ²ˆ 쑰건을 μΆ”κ°€ν•΄μ•Ό λ²ˆκ±°λ‘œμ›€

νŒ€ νšŒμ˜μ—μ„œ 논리적 μ‚­μ œ κ΅¬ν˜„ μ‹œ μœ„ 두 κ°€μ§€ 쑰회 방법을 μ œμ•ˆν•  μ˜ˆμ •μ΄λ‹€.
ν˜„μž¬ ν”„λ‘œμ νŠΈ 상황과 μœ μ§€λ³΄μˆ˜ νŽΈμ˜μ„±μ„ κ³ λ €ν•΄ 선택할 수 μžˆλ„λ‘ μ•ˆλ‚΄ν•˜λ©΄ 쒋을 것 κ°™λ‹€.

profile
μ°¨κ·Όμ°¨κ·Ό

0개의 λŒ“κΈ€