Development/썬카(SunCar) - 개인 프로젝트

[썬카/백엔드] Soft delete 구현과 Base Entity 작성 및 적용

양선규 2025. 3. 9. 16:17
728x90
반응형

중고차 거래 플랫폼은 가치가 큰 물건을 거래하는 만큼 신뢰성 있는 데이터가 중요하기에, 실제로 데이터를 삭제하지 않고 논리적으로만 삭제하는 Soft delete 방식이 적합하다고 생각했다. 실수로 데이터가 삭제되어도 쉽게 복구할 수 있기 때문에 데이터 관리와 안정성이 중요한 경우 적합한 방식이라고 할 수 있다.

 

Soft delete

- 데이터베이스에서 레코드를 물리적으로 삭제하지 않고, 논리적으로 삭제된 것처럼 표시하는 기술

- 일반적으로 테이블에 is_deleted 등의 필드를 추가하여 삭제된 상태를 표시한다.

 

Soft delete - 필요성

- 안전한 데이터 관리 : 우발적인 데이터 손실 방지

- 데이터 복구 용이성 : 필요시 간단히 플래그만 변경하여 복구 가능

- 유연한 데이터 접근 : 필요에 따라 삭제된 데이터에 접근 가능

- 시간적 컨텍스트 유지 : 데이터가 언제 삭제되었는지 타임스탬프로 추적

- 분석 및 보고 : 모든 데이터(삭제된 데이터 포함)를 분석에 활용 가능

 

 

 

 

 

 

 

1. JpaConfig 설정

package com.yangsunkue.suncar.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing // JPA Auditing 활성화 -> EntityListener, CreatedDate, LastModifiedDate 를 사용하기 위함
@Configuration
public class JpaConfig {
}

 

JpaConfig 설정 클래스를 생성하고, 클래스 레벨에 @EnableJpaAuditing 어노테이션을 추가한다.

해당 어노테이션을 추가하면 엔티티 생성/수정 시간을 관리하는 어노테이션들을 사용할 수 있다.

 

soft delete를 구현하는데 왜 이 설정을 추가하느냐 하면, 레코드 생성/수정 시간을 저장하는 필드도 함께 추가할 것이기 때문이다. soft delete 필드와 생성/수정 시간 필드가 함께 있으면 데이터 추적/분석에 용이하다.

 

2. Base Entity 클래스 작성

package com.yangsunkue.suncar.entity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

/**
 * 모든 엔티티가 상속받아야 하는 Base Entity 입니다.
 *
 * 생성일, 마지막 수정일, 삭제여부(SoftDelete) 컬럼을 가지고 있습니다.
 */
@EntityListeners(AuditingEntityListener.class) // 엔티티 생성/수정 시간을 관리하는 리스너
@MappedSuperclass // 이 클래스를 상속받은 엔티티들에게, DB 매핑정보를 제공하는 상위 클래스임을 나타낸다.
@Getter
public abstract class BaseEntity {

    @CreatedDate // 엔티티가 처음 저장될 때 현재 시간 자동 저장
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate // 엔티티가 수정될 때마다 현재 시간 자동 저장
    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;

    @Column(name = "is_deleted", nullable = false)
    private Boolean isDeleted = false;

    /**
     * Soft delete : 삭제를 위한 메서드
     */
    public void delete() {
        this.isDeleted = true;
    }

    /**
     * Soft delete : 복구를 위한 메서드
     */
    public void restore() {
        this.isDeleted = false;
    }
}

 

Base Entity 추상 클래스를 작성한다.

 

@EntityListeners(AuditingEntityListener.class) 어노테이션으로 클래스에 엔티티 생성/수정 시간을 관리하는 리스너를 추가한다.

@MappedSuperclass는 이 클래스가 엔티티 자체가 아닌, DB와의 매핑정보만을 제공하는 상속 받아서 사용해야 하는 상위 클래스 라는걸 의미한다.

@CreatedDate 어노테이션으로 created_at 컬럼에 엔티티 생성 시간을 자동 저장하고, @LastModifiedDate 어노테이션으로 엔티티 수정 시간을 자동 저장한다.

 

추가로 soft delete 작업을 위한  delete(), restore() 메서드를 만든다.

 

3. 엔티티에서 Base Entity를 상속받기

package com.yangsunkue.suncar.entity.user;

import com.yangsunkue.suncar.entity.BaseEntity;
import com.yangsunkue.suncar.common.enums.UserRole;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

/**
 * 회원 정보를 담는 엔티티입니다.
 */
@Entity
@Table(name = "users") // 테이블명은 DB에서 복수형이 관례, 자바는 단수형이 관례
@SQLDelete(sql = "UPDATE users SET is_deleted = true WHERE id = ?") // delete 메서드를 softdelete로 동작하게 함
@SQLRestriction("is_deleted = false") // 조회 시 삭제되지 않은 컬럼 조건 자동 추가
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Getter
@Builder
public class User extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id", unique = true, nullable = false)
    private String userId;

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

    @Column(name = "username", length = 50, nullable = false)
    private String username;

    @Column(name = "password_hash", length = 2000, nullable = false)
    private String passwordHash;

    @Column(name = "phone_number", length = 20, nullable = false)
    private String phoneNumber;

    /**
     * 계정 권한 - 회원, 딜러, 관리자
     */
    @Column(name = "role", nullable = false)
    @Enumerated(EnumType.STRING)
    private UserRole role;
}

 

엔티티에서 Base Entity를 extends 받으면, Base Entity에 만들어 두었던 created_at, updated_at, is_deleted 컬럼이 자동으로 추가된다.

 

여기서 soft delete를 위한 어노테이션 2개를 추가한다.

@SQLDelete 어노테이션으로, JPA가 제공하는 delete 메서드의 기능을 재정의한다. 실제로 데이터를 삭제하는 것이 아닌, is_deleted 컬럼의 값을 true로 바꾸도록 한다.

@SQLRestriction 어노테이션으로, 해당 엔티티(User) 조회 시 is_deleted = false 조건을 자동으로 추가한다. soft delete 되지 않은 데이터만 조회하도록 하는 것이다.

 

사실 soft delete 기능은 BaseEntity에서 메서드로 따로 정의해 두었지만, 혹시 모를 상황을 위해 JPA의 delete 메서드도 soft delete로 동작하게 해 둔다.

 

Base Entity 적용 확인

 

데이터베이스에서 직접 확인해 보면, created_at, updated_at, is_deleted 컬럼이 추가된 것을 확인할 수 있다.

 

 

흐름 정리

1. JpaConfig 설정, 생성/수정 시간 사용을 위한 @EnableJpaAuditing 추가

2. Base Entity 추상 클래스 작성, @EntityListeners, @MappedSuperclass 추가

3. Base Entity 추상 클래스에서, 생성 시간을 저장할 컬럼에 @CreatedDate, 수정 시간을 저장할 컬럼에 @LastModifiedDate 추가, soft delete를 위한 메서드 추가

4. 엔티티 클래스에서 Base Entity를 상속받고 @SQLDelete, @SQLRestriction 어노테이션을 통해 softdelete 설정 추가

 

=============

 

728x90
반응형