πŸ”¦Soft Delete vs Hard Delete

λ°•μš©λ―ΌΒ·2024λ…„ 3μ›” 5일
0

λ°μ΄ν„°λŠ” 미래의 μ„μœ μ΄λ‹€

λ°μ΄ν„°λŠ” ν˜„μž¬ 맀우 μ€‘μš”ν•œ μžμ›μ΄λ‹€. νŠΉνžˆλ‚˜ ν˜„λŒ€μ—λŠ” 인곡지λŠ₯을 ν•™μŠ΅μ‹œν‚€λŠ” 데 μžˆμ–΄μ„œ λ°μ΄ν„°λŠ” μ„μœ μ™€λ„ 같은 κ°€μΉ˜λ₯Ό μ§€λ‹ˆκΈ°μ— ν•¨λΆ€λ‘œ μ‚­μ œν•˜λŠ” 것은 λˆμ„ 땅에닀 λ²„λ¦¬λŠ” 것과 κ°™μŠ΅λ‹ˆλ‹€. 차라리 땅에 λ–¨μ–΄μ‘ŒμœΌλ©΄ λˆ„κ°€ κ°€μ Έκ°€μ„œ λ§›μžˆλŠ”κ±°λΌλ„ 사먹지 μ‚­μ œλŠ” μ™„μ „ λΆˆνƒœμš°λŠ” 정도? 라고 μƒκ°λœλ‹€.πŸ”₯πŸ”₯πŸ”₯

ν•˜μ§€λ§Œ 데이터λ₯Ό μ‚­μ œν•˜λŠ” 것을 κ±°λΆ€ν•΄μ„œλŠ” μ•ˆ λœλ‹€. 고객이 μžμ‹ μ˜ 데이터λ₯Ό μ‚­μ œν•˜κΈΈ 원할 λ•Œ 이λ₯Ό κ±°λΆ€ν•˜λ©΄ ν•΄λ‹Ή μ„œλΉ„μŠ€λ₯Ό 더 이상 μ΄μš©ν•˜μ§€ μ•Šμ„ 것이닀. κ·Έλž˜μ„œ μš°λ¦¬λŠ” μ‹€μ œλ‘œ 데이터λ₯Ό μ‚­μ œν•˜λŠ” κ²ƒμ²˜λŸΌ λ³΄μ΄λŠ” λ°©μ‹μœΌλ‘œ μ²˜λ¦¬ν•˜λŠ” Soft Deleteλ₯Ό μ‚¬μš©ν• κ²ƒ 이닀.

πŸͺHard Delete

물리적 μ‚­μ œλΌκ³ λ„ ν•˜λŠ”λ° μš°λ¦¬κ°€ ν‰μ†Œμ— SQL문을 μ‚¬μš©ν• λ•Œ DELETE λͺ…령문이 κ·Έ μ˜ˆμ‹œλ‹€.

DELETE FROM TABLE WHERE CONDITION = ?

ν•΄λ‹Ή 쿼리λ₯Ό μ‹€ν–‰ν•˜λ©΄ DBμ—μ„œ μ™„μ „νžˆ μ‚­μ œν•˜λŠ”κ²ƒμ΄λ‹€. 곡간을 ν™•λ³΄ν•˜κ±°λ‚˜ μ„±λŠ₯ κ°œμ„ μ„ μœ„ν•΄ μ‚¬μš©λ˜λ©° λ³΄μ•ˆ μ •μ±… 및 κ°œμΈμ •λ³΄ 보호 법λ₯ λ“±μ— 따라 μ‚¬μš©λ  수 μžˆλ‹€.
데이터λ₯Ό 영ꡬ히 μ‚­μ œν•˜κΈ°μ— μ‹ μ€‘ν•˜κ²Œ κΈ°λŠ₯을 λ§Œλ“€μ–΄μ•Όν•˜λ©° 영ꡬ μ‚­μ œμ‹œ 데이터λ₯Ό λ°±μ—…λ“±μ˜ λ³΄μ•ˆ μš”μ†Œλ₯Ό μΆ”κ°€λ‘œ ꡬ성해야 ν•œλ‹€.

λ§Œμ•½ μ–΄λ–€ μ•…μ„± μœ μ €κ°€ κ²Œμ‹œλ¬Όμ— 도배λ₯Ό ν•˜κ³  μŒλž€λ¬Όλ“±μ„ μ˜¬λ¦¬λŠ” λ“±μ˜ ν–‰μœ„λ₯Ό ν•˜μ˜€μ„λ•Œ 좔적을 ν”Όν•˜κΈ° μœ„ν•΄ κ²Œμ‹œλ¬Όμ„ μ‚­μ œν•˜λ©΄ μ–΄λ–»κ²Œ 될까?
ν•΄λ‹Ή κ²Œμ‹œλ¬Όμ„ μ‘°μ‚¬ν• λ•Œ μ—­ μΆ”μ ν•˜κΈ° μ–΄λ €μšΈκ²ƒμ΄λ‹€. κ·Έλ¦¬ν•˜μ—¬ μ‚¬μš©ν•˜λŠ” 방식이 Soft Delete이닀.

🏷️Soft Delete

논리적 μ‚­μ œλΌκ³ λ„ ν•˜λ©° λ ˆμ½”λ“œλ₯Ό μ œκ±°ν•˜λŠ” λŒ€μ‹  μ‚­μ œλ˜μ—ˆλ‹€λŠ” ν‘œμ‹œλ₯Ό λ‚¨κΈ°λŠ” 방법이닀.
DBμ—λŠ” μ—¬μ „νžˆ 남아 μžˆμœΌλ‚˜ 쑰회λ₯Ό ν•  λ•Œ 더 이상 μ‚­μ œλœ 데이트λ₯Ό μ‘°νšŒν•˜μ§€ μ•ŠλŠ”λ‹€.
데이터 보쑴이 κ°€λŠ₯ν•˜λ©° 볡ꡬ가 κ°€λŠ₯ν•˜κ³  μ‚­μ œλœ 데이터 이λ ₯을 좔적이 κ°€λŠ₯ν•˜λ‹€λŠ” μž₯점이 μžˆλ‹€.

ν‘œμ‹œλ˜μ—ˆλ‹€λŠ” 방법을 2κ°€μ§€λ‘œ 생각할 수 μžˆλ‹€.

λ¨Όμ € μ‚­μ œλ˜μ—ˆλ‹€κ³  μ²΄ν¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ deleted μ»¬λŸΌμ„ μ§€μ •ν•˜μ—¬ μ‚­μ œμ‹œ μ—…λ°μ΄νŠΈλ₯Ό ν•˜λŠ” 방법이닀

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private boolean deleted; // μ‚­μ œ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν•„λ“œ
}
User user = userRepository.findById(userId);
user.setDeleted(true); // μ‚­μ œ ν•„λ“œ μ—…λ°μ΄νŠΈ
userRepository.save(user);

λ‹€μŒμœΌλ‘œ deleted_at μ»¬λŸΌμ„ μ§€μ •ν•˜μ—¬ μ‚­μ œμ‹œ μžλ™μœΌλ‘œ μ‚­μ œλœ λ‚ μ§œκ°€ μ—…λ°μ΄νŠΈ λ˜λŠ” 방식이닀
λ¨Όμ € Timestamped μΆ”μƒν΄λž˜μŠ€λ‘œ λ“±λ‘ν•œλ‹€. 여기에 μ‚­μ œλ  λ•Œ μ—…λ°μ΄νŠΈλ₯Ό ν•  컬럼 deleted_at을 μΆ”κ°€ν•œλ‹€.

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
    @CreatedDate
    @Column(updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createAt;

    @LastModifiedDate
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime modifiedAt;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime deletedAt; // μ‚­μ œλ  λ•Œ μžλ™μœΌλ‘œ μ—…λ°μ΄νŠΈ
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@SQLDelete(sql = "UPDATE users SET deleted_at=CURRENT_TIMESTAMP where id=?")
@Where(clause = "deleted_at IS NULL")
@Table(name = "users")
public class User extends Timestamped {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(unique = true, nullable = false)
  private String email;

  @Column(nullable = false)
  private String password;
  }

@SQLDelete

λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ νŠΉμ • λ ˆμ½”λ“œλ₯Ό μ™„μ „νžˆ μ œκ±°ν•˜λŠ” SQL λ¬Έμž₯이닀.
μ‚­μ œμ‹œ νŠΉμ • 쑰건을 λ§žλŠ” ν•΄λ‹Ή SQL을 μ‹€ν–‰ν•˜λŠ” 문ꡬ이닀.
이 μΏΌλ¦¬λŠ” users ν…Œμ΄λΈ”μ—μ„œ νŠΉμ • idλ₯Ό 가진 μ‚¬μš©μžκ°€ μ‚­μ œλ μ‹œ deleted_at ν•„λ“œλ₯Ό ν˜„μž¬ μ‹œκ°„μœΌλ‘œ μ„€μ •ν•˜μ—¬ ν•΄λ‹Ή μ‚¬μš©μžλ₯Ό μ†Œν”„νŠΈ μ‚­μ œν•˜λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•œλ‹€.

@Where

νŠΉμ • 쑰건을 μΆ©μ‘±ν•˜λŠ” 쿼리 κ²°κ³Όλ₯Ό κ°€μ Έμ˜€λ„λ‘ ν•„ν„°λ§ν•˜λŠ”λ° μ‚¬μš©λœλ‹€.
ν•΄λ‹Ή μ½”λ“œμ—μ„œ @Where(clause = "deleted_at IS NULL") λŠ” deleted_atκ°€ NULL이 μ•„λ‹λ•Œ 즉 μ‚­μ œ 된적이 μ—†λŠ” μ—”ν‹°ν‹°λ§Œ κ°€μ Έμ™€λΌλŠ” λœ»μ΄λ‹€.

πŸš€κ²°κ³Ό

κ²°κ³Όλ₯Ό 보면 deleted_at에 μ‚­μ œλœ ν˜„μž¬ λ‚ μ§œκ°€ μ—…λ°μ΄νŠΈκ°€ λ˜μ—ˆλ‹€.

🚨주의점

@Where(clause = "deleted_at = null") 처럼 μ‚¬μš©ν•΄μ„œλŠ” μ•ˆλœλ‹€. μ²˜μŒμ— 이것 λ•Œλ¬Έμ— λ§Žμ€ 고민을 ν•˜μ˜€κ³  NULLμΌλ•Œ μ²΄ν‚Ήν•˜λŠ” IS NULL을 μ‚¬μš©ν•˜μ—¬ 검증을 ν•΄μ•Όν•œλ‹€. λ§Œμ•½ = null둜 deleted_at = null둜 μž‘μ„±μ‹œ λ‹€μŒκ³Ό 같은 쿼리문이 μ‹€ν–‰λ˜μ–΄ κ²°κ΅­ 아무것도 κ°€μ Έμ˜€μ§€ λͺ»ν•œλ‹€.

πŸŽ‰DONE

0개의 λŒ“κΈ€