SPARTA 과제/TEAM | commerce

팀) 커머스 - Soft Delete

kjw81024 2026. 2. 27. 22:09

팀과제 때 Soft Delete 를 적용하기로 했는데 적용 못 했다... 잘 적어두고 까먹음 issue 

그래서 몇 개는 삭제 대신 INACTIVE 를 적용하고 또 몇 개는 그냥 repository 에서 삭제하는 형태로 되어있는 상태

 

Soft Delete 에 대해 잘 모르기도 해서 한 번 정리하고 넘어가려고 한다.

 

일단 튜터님이 와서 Soft Delete 는 어떤 방식으로 구현했냐고 하면 어떻게 대답할건지 물어보셨다. 구현을 못 했으니까 대답도 당연히 못 했고 튜터님의 힌트만 열심히 메모했다

 

1. INACTIVE 나 다른 상태값으로 전환

2. Boolean DELETE field 따로 만들어서 TRUE FALSE 로 설정

3. LocalDateTime deletedAt 을 만들어서 삭제 시점을 저장하고 (실제로는 삭제가 아니라 update? 정도하면 될듯) 이후 deletedAt 이 null 이 아니면 삭제되었다고 생각하기 

 

한 번 해보기~


1. INACTIVE 나 다른 상태값으로 전환

사실 코드 자체는 쉬워서 금방 짰는데 ..... 

public enum ProductStatus {
    AVAILABLE("판매중"),
    SOLD_OUT("품절"),
    DISCONTINUED("단종"),
    DELETED("삭제됨");
    
    # 후략
}

ProductStatus 에 DELETED 를 분명 추가하고 재실행 시켜도 DELETED 로 변환이 안 되는 오류 발생.......

 

https://stackoverflow.com/questions/77455887/how-to-update-hibernate-check-constraint-automatically-after-enum-modification-i?utm_source=chatgpt.com

 

How to Update Hibernate Check Constraint Automatically After Enum Modification in Spring Boot?

I have a Spring Boot application using Hibernate, where I recently added new values to the enum ContactTypeConstant, which is used in the entity class BlockItemEntity. The entity is mapped to a

stackoverflow.com

보니까 아래처럼 설정하면 Enum 추가등의 업데이트는 자동으로 되지 않는다는 말인듯했다...

spring.jpa.hibernate.ddl-auto=update

 

열심히 상품 추가해둔게 아깝지만 create 로 바꾸고 다시 실행했더니 삭제가 됨

+ 이후에 TIL 을 작성하며 추가로 찾아보니 Console 에 query 를 입력해도 Enum 타입이 업데이트 된다고 한다!

ALTER TABLE products
    MODIFY COLUMN status ENUM(
        'AVAILABLE',
        'SOLD_OUT',
        'DISCONTINUED',
        'DELETED'
        ) NOT NULL;

다음에 Enum 을 추가하거나 삭제할 때는 이 query 를 사용하는걸로..!

 

어쨌든 결과는 DELETED 로 바뀐 값을 제외하고 출력되어야함 

잘 출력된다!

더보기

ProductService.java

@Transactional
public void delete(Long productId, UserPrincipal userPrincipal) {
    isActiveAdmin(getAdminById(userPrincipal.getId()));
    Product product = getProductById(productId);

    product.updateStatus(ProductStatus.DELETED);
}

 

 ProductRepository.java

@Query("SELECT p FROM Product p WHERE " +
        "(p.status != 'DELETED') AND " + # 기존 코드에서 이 줄만 추가함
        "(:keyword IS NULL OR p.name LIKE %:keyword%) AND " +
        "(:category IS NULL OR p.category = :category) AND " +
        "(:status IS NULL OR p.status = :status)")
Page<Product> searchProducts(
        @Param("keyword") String keyword,
        @Param("category") Category category,
        @Param("status") ProductStatus status,
        Pageable pageable);

2.  Boolean DELETE field 따로 만들어서 TRUE FALSE 로 설정

 

Entity 와 생성자에 boolean isDeleted 값을 추가 후 기본값을 false 로 입력했다.

public void deleted(){
    if (!isDeleted){
        isDeleted = true;
        return;
    }
    throw new ServiceException(ErrorCode.INVALID_STATUS);
}

삭제 로직을 만들고 삭제 상태가 아니라면 isDeleted 를 true 로 업데이트 하는 로직을 Entity 내부에 작성, repository 는 아까 코드에서 p.status 를 확인하던 부분만 isDeleted 를 확인하도록 수정했다.

"(p.isDeleted = false) AND " +

이번엔 2번과 7번을 지웠다

7번은 보이지 않지만 일단 지워졌음..!

1번과 4번은 ProductStatus 로 확인해야 삭제되었다고 뜨기 때문에 isDeleted 로 확인한 repository 로직에서는 정상적으로 출력되는 모습


3. DeletedAt 설정

deletedAt 을 설정하려고 할 때 .... 캡틴 길중님 오셔서 더 간단한 방법을 알려주셨다

 

바로... Entity 에서 SQL 설정해주기 

 

일단 LocalDateTime deletedAt 필드와 boolean deleted 를 생성하고

@SQLDelete(sql = "UPDATE products SET deleted = true, deleted_at = NOW() WHERE id = ?")
@SQLRestriction("deleted = false")

위에 해당 쿼리를 작성한다.

 

그럼 service 에서 그냥 hard deleted 하듯이 구문을 작성해도

update 가 된다는 사실....!!!!

repository 에서는 아예 delete 와 관련된 쿼리를 다 빼주면 된다 

알아서 어노테이션에서 확인하고 해당 쿼리를 실행시켜줌

 

궁금한점

1. 원리

@SQLDelete Entity 를 Delete 하려고 할 때
괄호 내부에 있는 쿼리를 대신 실행시킴 
delete 의 매개변수로 들어오는 값을 ?에 넣어서 사용 
@SQLRestriction 괄호 내부에 있는 쿼리를 자동으로 Where 절에 추가 
해당 테이블에 대해 항상 적용됨 

 

 

2. Deleted 된 값만 출력하려고 하면 어떻게 되는지 

@Query("SELECT p FROM Product p WHERE p.deleted = true")
List<Product> searchDeletedProducts();

repository 에 deleted 조건을 줘도 출력되지 않는다 

@SQLRestriction 이 기본적으로 Where 절에 들어가기 때문에 Where p.deleted = false and where p.deleted = true 이런식으로 인식을 하는듯 함

 

-> Native 쿼리 사용

@Query(value = "SELECT * FROM products WHERE deleted = true", nativeQuery = true)
List<Product> searchDeletedProducts();

 

이렇게 우회해서 사용하면 된다....

아니면 그냥 그냥 Restriction 은 지우고 repository 에서 분리해서 작성해주는 방법이 있음....

public List<GetProductsResponse> getDeletedList() {
    List<Product> products = productRepository.searchDeletedProducts();

	# 상태 업데이트는 그냥 따로 해줬다 
    products.stream().forEach(product -> product.updateStatus(ProductStatus.DELETED));

    return products.stream().map(product -> new GetProductsResponse(
            product.getId(),
            product.getName(),
            product.getCategory().getCategoryName(),
            product.getPrice(),
            product.getStock(),
            product.getStatus().getStatusName(),
            product.getCreatedAt(),
            product.getAdmin().getName()
    )).toList();
}


4. @SoftDelete

이런 어노테이션이 생겼다고 한다... 시작 시간이 6.4, 2023년 완전 말,,,!! 신기해

https://sundries-in-myidea.tistory.com/165  

요거 보고 참고했다

다른글에서 strategy 를 SoftDeleteType.DELETED 로 추가해주던데 어차피 default 가 DELETED 라서 삭제시 업데이트 해줄 컬럼명만 명시해주면 될 것 같다.

어노테이션을 달아주고 

주문 Entity 에는 어노테이션을 달지 않고 실행시켰다

 

고객이 주문을 생성하게 만들고 조회가 잘 되는지 확인했다

고객이 주문한 상품을 삭제하고 다시 주문을 조회하면 

에러가 뜬다 

2026-02-28T13:19:35.443+09:00 ERROR 9232 --- [commerce] [nio-8080-exec-4] c.e.c.g.e.GlobalExceptionHandler         : Exception : Cannot invoke "cohttp://m.example.commerce.product.entity.Product.getName()" because the return value of "cohttp://m.example.commerce.order.entity.Order.getProduct()" is null

getProduct 를 하려고 하는데 안 돼서 getter 가 사용이 안 됨....

어노테이션을 붙여도 안 된다

삭제되면 product 를 삭제된 상품이라고 뜨게 하더라도 출력이 되어야할 거 같다...

 

그 때 7조 발표 자료가 생각났다........ OrderItem 이라는 Class 를 만든 이유가 그거였군아... ㅜㅜ

OrderItem 생성자에서 product 의 중요한 (출력해야할) 값을 저장해두면 product 가 사라져도 order 을 조회하려고 하면 값이 뜸 

 

public class OrderItem {

    private Long productId;

    private String productName;
    private int productPrice;
}

클래스를 만들고

createOrder 부분에서 생성자를 이용해서 넣어주면 OrderItem 이라는 값에 상품 정보가 사진처럼 찍혀서?? 들어가기 때문에 상품이 삭제되어도 조회시에 문제가 발생하지 않는다...!!

 


끝...!!!!!!!

알려주신 길중님

감사.매우매우 감사

'SPARTA 과제 > TEAM | commerce' 카테고리의 다른 글

JWT 트러블슈팅  (0) 2026.03.15
팀) 커머스 피드백 반영  (0) 2026.03.08
팀) 커머스 트러블슈팅 (3)  (0) 2026.02.27
팀) 커머스 - DB 생성 시 값 넣기  (0) 2026.02.27
팀) 커머스 - 리스트 출력  (0) 2026.02.22