1. 소개

테이블에서 물리적으로 데이터를 삭제하는 것은 데이터베이스와 상호 작용할 때 일반적인 요구 사항입니다. 그러나 때로는 데이터베이스에서 데이터를 영구적으로 삭제하지 않는 비즈니스 요구 사항이 있습니다. 예를 들어 이러한 요구 사항은 데이터 기록 추적 또는 감사의 필요성과 참조 무결성과 관련이 있습니다.

데이터를 물리적으로 삭제하는 대신 애플리케이션 프런트 엔드에서 액세스할 수 없도록 해당 데이터를 숨길 수 있습니다.

이 예제에서는 soft delete와 Spring JPA 로 이 기술을 구현하는 방법에 대해 알아봅니다 .

2. 일시 삭제란 무엇입니까?

일시 삭제는 일부 데이터를 데이터베이스의 테이블에서 물리적으로 삭제하는 대신 삭제된 것으로 표시하는 업데이트 프로세스를 수행합니다. 일시 삭제를 구현하는 일반적인 방법은 데이터가 삭제되었는지 여부를 나타내는 필드를 추가하는 것입니다.

예를 들어 다음과 같은 구조의 제품 테이블이 있다고 가정해 보겠습니다.1 번 테이블

이제 테이블에서 레코드를 물리적으로 삭제할 때 실행할 SQL 명령을 살펴보겠습니다.

delete from table_product where id=1

이 SQL 명령은 데이터베이스의 테이블에서 id=1 인 제품을 영구적으로 제거합니다.

이제 위에서 설명한 일시 삭제 메커니즘을 구현해 보겠습니다.테이블2

삭제 라는 새 필드를 추가했습니다 . 이 필드에는 0 또는 1 값이 포함됩니다 .

1 은 데이터가 삭제 되었음을 나타내고 0 은 데이터가 삭제되지 않았음을 나타냅니다. 기본값으로 0 을 설정해야 하며 모든 데이터 삭제 프로세스에 대해 SQL 삭제 명령을 실행하지 않고 대신 다음 SQL 업데이트 명령을 실행합니다.

update from table_product set deleted=1 where id=1

이 SQL 명령을 사용하여 실제로 행을 삭제하지 않고 삭제된 것으로 표시만 했습니다. 따라서 읽기 쿼리를 수행하고 삭제되지 않은 행만 원할 때 SQL 쿼리에 필터만 추가해야 합니다.

select * from table_product where deleted=0

3. Spring JPA에서 Soft Delete 구현 방법

Spring JPA를 사용하면 소프트 삭제 구현이 훨씬 쉬워졌습니다. 이 목적을 위해 몇 가지 JPA 어노테이션만 필요합니다.

아시다시피 JPA에서는 일반적으로 몇 가지 SQL 명령만 사용합니다. 뒤에서 대부분의 SQL 쿼리를 생성하고 실행합니다.

이제 위와 동일한 테이블 예제를 사용하여 Spring JPA에서 일시 삭제를 구현해 보겠습니다.

3.1. 엔티티 클래스

가장 중요한 부분은 엔터티 클래스를 만드는 것입니다.

제품 엔터티 클래스 를 만들어 보겠습니다 .

@Entity
@Table(name = "table_product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private double price;

    private boolean deleted = Boolean.FALSE;

    // setter getter methods
}

보시다시피 기본 값이 FALSE 로 설정된 삭제된 속성을 추가했습니다 .

다음 단계는 JPA 저장소에서 삭제 명령을 재정의하는 것입니다.

기본적으로 JPA 리포지토리의 삭제 명령은 SQL 삭제 쿼리를 실행하므로 먼저 엔터티 클래스에 몇 가지 어노테이션을 추가해 보겠습니다.

@Entity
@Table(name = "table_product")
@SQLDelete(sql = "UPDATE table_product SET deleted = true WHERE id=?")
@Where(clause = "deleted=false")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private double price;

    private boolean deleted = Boolean.FALSE;
   
    // setter getter method
}

삭제 명령을 재정의하기 위해 @SQLDelete 어노테이션을 사용하고 있습니다. 삭제 명령을 실행할 때마다 실제로 데이터를 영구적으로 삭제하는 대신 삭제된 필드 값을 true로 변경하는 SQL 업데이트 명령으로 전환했습니다 .

반면 에 @Where 어노테이션은 제품 데이터를 읽을 때 필터를 추가합니다. 따라서 위의 코드 예제에 따라 값이 deleted = true 인 제품 데이터 는 결과에 포함되지 않습니다.

3.2. 저장소

리포지토리 클래스에는 특별한 변경 사항이 없으며 Spring Boot 애플리케이션의 일반 리포지토리 클래스처럼 작성할 수 있습니다.

public interface ProductRepository extends CrudRepository<Product, Long>{
    
}

3.3. 서비스

또한 서비스 클래스의 경우 아직 특별한 것이 없습니다. 원하는 저장소에서 함수를 호출할 수 있습니다.

이 예에서는 세 가지 리포지토리 함수를 호출하여 레코드를 만든 다음 일시 삭제를 수행합니다.

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;

    public Product create(Product product) {
        return productRepository.save(product);
    }

    public void remove(Long id){
        productRepository.deleteById(id);
    }

    public Iterable<Product> findAll(){
        return productRepository.findAll();
    }
}

4. 삭제된 데이터를 얻는 방법?

@Where 어노테이션 을 사용하면 삭제된 데이터에 액세스할 수 있기를 원하는 경우 삭제된 제품 데이터를 가져올 수 없습니다. 이에 대한 예는 전체 액세스 권한이 있고 "삭제된" 데이터를 볼 수 있는 관리자 수준의 사용자입니다.

이를 구현하려면 @Where 어노테이션 을 사용하지 말고 @FilterDef@Filter 라는 두 가지 어노테이션을 사용해야 합니다. 이러한 어노테이션을 사용하여 필요에 따라 조건을 동적으로 추가할 수 있습니다.

@Entity
@Table(name = "tbl_products")
@SQLDelete(sql = "UPDATE tbl_products SET deleted = true WHERE id=?")
@FilterDef(name = "deletedProductFilter", parameters = @ParamDef(name = "isDeleted", type = "boolean"))
@Filter(name = "deletedProductFilter", condition = "deleted = :isDeleted")
public class Product {

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

    private String name;

    private double price;

    private boolean deleted = Boolean.FALSE;
}

여기서 @FilterDef 어노테이션은 @ 필터 어노테이션 에서 사용할 기본 요구 사항을 정의합니다 . 또한 동적 매개변수 또는 필터를 처리하도록 ProductService 서비스 클래스의 findAll() 함수 도 변경해야 합니다.

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private EntityManager entityManager;

    public Product create(Product product) {
        return productRepository.save(product);
    }

    public void remove(Long id){
        productRepository.deleteById(id);
    }

    public Iterable<Product> findAll(boolean isDeleted){
        Session session = entityManager.unwrap(Session.class);
        Filter filter = session.enableFilter("deletedProductFilter");
        filter.setParameter("isDeleted", isDeleted);
        Iterable<Product> products =  productRepository.findAll();
        session.disableFilter("deletedProductFilter");
        return products;
    }
}

여기서는 Product 엔터티 를 읽는 프로세스에 영향을 주는 개체 필터 에 추가할 isDeleted 매개 변수를 추가합니다 .

5. 결론

Spring JPA를 사용하여 일시 삭제 기술을 구현하는 것은 쉽습니다. 우리가 해야 할 일은 행이 삭제되었는지 여부를 저장할 필드를 정의하는 것입니다. 그런 다음 해당 특정 엔터티 클래스 에서 @SQLDelete 어노테이션을 사용하여 삭제 명령을 재정의해야 합니다 .

더 많은 제어가 필요한 경우 @FilterDef@Filter 어노테이션을 사용하여 쿼리 결과에 삭제된 데이터가 포함되어야 하는지 여부를 결정할 수 있습니다.

이 문서의 모든 코드는 GitHub에서 사용할 수 있습니다 .

Persistence footer banner