Framework/Spring

[Spring] JPA로 일정 시간 후 데이터 삭제 구현하기

leonie. 2025. 2. 8. 15:03

데이터를 삭제할 때, 즉시 데이터베이스에서 제거하는 방식(DELETE)은 간단하지만,

데이터 복구가 어렵고, 감사(audit) 및 트랜잭션 이력을 유지할 수 없다는 단점이 있다.

 

이에 따라, 많은 애플리케이션에서는 논리 삭제(Soft Delete)를 적용하고,

일정 시간이 지난 후 배치 작업을 통해 데이터베이스에서 실제로 삭제하는 방법을 사용한다.

 

이번 글에서는 JPA를 활용하여 논리 삭제 후 배치 작업으로 데이터를 완전 삭제하는 구현 방법을 설명한다.

 

논리 삭제 (Soft Delete) 란?

데이터 베이스에서 데이터를 즉시 제거하지 않고, 삭제된 상태를 나태는 플래그(컬럼)을 추가한다.
위 방식을 이용한다면, 데이터를 유지하면서 삭제된 것처럼 처리할 수 있다.

 

 

 


 

 

1️⃣ 논리 삭제 구현 (@SQLDelete & @Where)

 

(1) 엔티티 설정(SoftDelete)

@Getter
@Setter
@Entity
@Table(name = "user")
@SQLDelete(sql = "UPDATE user SET is_deleted = true, deleted_at = now() WHERE id = ?")
@Where(clause = "is_deleted = false")  // 조회 시 자동으로 필터링
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String email;
    
    @Column(name = "is_deleted")
    private boolean isDeleted = false;  // 기본값 false (삭제 안 됨)
    
    @Column(name = "deleted_at")
    private LocalDateTime deletedAt;  // 삭제 시각
    
    // 삭제 메서드
    public void softDelete() {
        this.isDeleted = true;
        this.deletedAt = LocalDateTime.now();
    }
}

 

 

 

🔹 @SQLDelete로 DELETE 대신 UPDATE 실행

@SQLDelete(sql = "UPDATE users SET is_deleted = true, deleted_at = now() WHERE id = ?")

 

@SQLDelete란?

Hibernate가 DELETE 대신 UPDATE 쿼리를 실행하도록 설정하는 애노테이션입니다.

즉, `DELETE FROM users WHERE id = ?` 명령이 실행될 때, 다음과 같은 쿼리로 대체됩니다.

UPDATE users SET is_deleted = true, deleted_at = now() WHERE id = ?
🔹 @SQLDelete의 장점

✅ 데이터가 물리적으로 삭제되지 않아 복구 가능

✅ 연관 테이블(FK)과의 무결성을 유지할 수 있음

✅ 실수로 데이터를 삭제하는 문제를 방지

 

 

 

🔹 Soft Delete된 데이터 자동 필터링

@Where(clause = "is_deleted = false")  // 조회 시 자동 필터링

@Where란?

JPA 엔터티를 조회할 때, Hibernate가 자동으로 특정 조건을 추가하도록 설정하는 애노테이션입니다.

`clause` 는 SQL에서 `절(Clause)` 를 의미하며, Hibernate가 자동으로 추가할 SQL `WHERE 조건`을 지정하는 부분이다.

 

findAll() 같은 메서드를 실행하면,

`@Where(clause = "is_deleted = false")`의 코드로인해 Hibernate가 자동으로 sql문을 변환합니다.

SELECT * FROM users WHERE is_deleted = false;

즉, 논리적으로 삭제된 데이터(is_deleted = true)는 자동으로 제외됩니다.

 

 

 

2️⃣ 일정 시간이 지나면 물리 삭제 (Batch Job)

 

(1) 레포지토리 (Repository)

import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;
import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 논리 삭제된 데이터 중 6개월 이상 지난 것 찾기
    List<User> findAllByIsDeletedTrueAndDeletedAtBefore(LocalDateTime dateTime);
}

 

 

 

(2) 서비스 설정 : 배치 스케줄링 (Hard Delete)

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    // 자동 삭제
    @Scheduled(cron = "0 0 3 * * ?")  
    @Transactional
    public void deleteOldSoftDeletedUsers() {
        LocalDateTime sixMonthsAgo = LocalDateTime.now().minusMonths(6);
        List<User> usersToDelete = userRepository.findAllByIsDeletedTrueAndDeletedAtBefore(sixMonthsAgo);

        if (!usersToDelete.isEmpty()) {
            userRepository.deleteAll(usersToDelete);
            System.out.println(usersToDelete.size() + " users permanently deleted.");
        }
    }
}

 

 

 

🔹 특정 시간에 메서드를 자동 실행

`@Scheduled` 애노테이션을 사용하면 특정 시간에 메서드를 자동 실행할 수 있습니다.

cron = "0 0 3 * * ?"매일 새벽 3시 정각에 실행된다.

데이터베이스 성능을 고려하여, 시스템 부하가 적은 시간에 실행하는 것이 좋다.

 

 

 

🔹 오류 발생 시 롤백

`@Transactional ` 어노테이션을 사용하여 오류 발생 시 롤백할 수 있다.

이를 통해 데이터가 일관성 있게 유지된다.

 

 

🔹 특정 기간이 지난 데이터는 삭제

LocalDateTime sixMonthsAgo = LocalDateTime.now().minusMonths(6);
List<User> usersToDelete = userRepository.findAllByIsDeletedTrueAndDeletedAtBefore(sixMonthsAgo);

 

`LocalDateTime.now().minusMonths(6)` → 현재 날짜 기준으로 6개월 전의 날짜를 계산한다.

`findAllByIsDeletedTrueAndDeletedAtBefore(sixMonthsAgo)`는 Spring Data JPA의 메소드 이름 기반 쿼리로,

is_deleted = true (논리 삭제된 유저) 이고, deleted_at이 6개월 이전인 데이터들을 조회한다.

 

 

 

🔹 조회된 유저를 물리적으로 삭제

if (!usersToDelete.isEmpty()) {
    userRepository.deleteAll(usersToDelete);
    System.out.println(usersToDelete.size() + " users permanently deleted.");
}

조회된 데이터가 존재하면 물리적으로 삭제 (DELETE FROM user) 한다.

 

 

 


 

 

 

데이터 관리에서 논리 삭제(Soft Delete)는 데이터의 무결성과 감사(Audit) 기능을 유지하면서도

실수로 인한 데이터 손실을 방지할 수 있는 효과적인 방법입니다.

 

JPA의 `@SQLDelete`와 `@Where`를 활용하면 논리 삭제된 데이터를 자동으로 필터링할 수 있으며,

Spring Batch나 `@Scheduled`를 사용해 특정 시점에 물리적으로 데이터를 정리할 수 있습니다.

 

이러한 구현 방식을 적용할 때는 데이터의 삭제 주기, 데이터베이스 성능, 대량 데이터 삭제 시 부하 관리 등을 종합적으로 고려해야 합니다. 특히 대량 데이터 삭제 시 배치 단위 삭제 페이징 처리를 통한 삭제 최적화가 중요합니다.

 

프로젝트의 특성과 요구사항에 맞춰 논리 삭제와 물리 삭제 전략을 병행한다면,

데이터 보존과 성능 최적화라는 두 마리 토끼를 모두 잡을 수 있을 것입니다.

 

 

 

728x90