自動処理[JPA]ソフトdelete


データを削除する方法はhard deletesoft delete2種類あります.hard deleteは、クエリを削除することによってデータベースから実際に削除する方法である.soft deleteは、実際にはデータベースからデータを削除するのではなく、テーブルに「削除」(Deleted)などのフィールドを追加し、クエリーを更新することで削除値を変更します.soft deleteの場合、クエリ結果の削除値を返すべきではありません.したがって、where deleted = falseなどの条件を追加するか、アプリケーション側が削除していないデータのみをフィルタする必要があります.ただし、各クエリーロジックに直接条件を追加すると、漏れてしまう可能性があります.
JPAの実装チェーンHypernetには、次の2つの機能があります.
1)削除時にdeleteクエリではなく他の構文を実行する
2)あるエンティティをクエリーするすべてのクエリーにwhere条件を追加する機能
これにより、soft delete処理と削除されていないデータのクエリーを簡素化できます.


次に、エンティティの例を示します.初期作成時にデフォルト値falseを削除するかどうか.
@Entity
public class Shop {

	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private String address;
    
    private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
元のファイルを削除しようとすると、次のように処理されます.delete呼び出し後にfindByIdとして削除したShopをクエリーしようとすると、実際のデータベースに格納されている値が削除されたため、optionalは空になります.
    @Test
    @DisplayName("Shop soft delete")
    public void delete() {
        Shop shop = new Shop("가게이름", "대구광역시");
        Shop savedShop = shopRepository.save(shop);

        assertThat(savedShop.getId()).isNotNull();
        assertThat(savedShop.isDeleted()).isFalse();

        shopRepository.delete(savedShop);
        entityManager.flush();

        Optional<Shop> afterDelete = shopRepository.findById(savedShop.getId());
        assertThat(afterDelete).isEmpty();
    }
deleteクエリの発生も表示されます.
    delete 
    from
        shop 
    where
        id=?

@SQLDelete


削除ロジックを実行する場合は、updateクエリを使用してdeleteクエリではなく、削除したフィールド値をtrueに変更する必要があります.この場合、@SQLDeleteを使用することができる.
@SQLDeleteは、deleteクエリではなく、エンティティの削除時に実行されるCustomsql文を表す構文です.@SQLDeleteに書き込まれると、deleteクエリではなくエンティティの削除を要求するときに更新クエリが実行されます.
@Entity
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
public class Shop {

	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private String address;
    
    private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
さっきやったテストを実行しましょう.実際に削除されていないため、optionalは空ではなく、削除値がtrueに変更されていることがわかります.
    @Test
    @DisplayName("Shop soft delete")
    public void delete() {
        Shop shop = new Shop("가게이름", "대구광역시");
        Shop savedShop = shopRepository.save(shop);

        assertThat(savedShop.getId()).isNotNull();
        assertThat(savedShop.isDeleted()).isFalse();

        shopRepository.delete(savedShop);
        entityManager.flush();

        Optional<Shop> afterDelete = shopRepository.findById(savedShop.getId());
        assertThat(afterDelete).isNotEmpty();
        assertThat(afterDelete.get().isDeleted()).isTrue();
    }
また、更新クエリーが発生していることもわかります.
    UPDATE
        shop 
    SET
        deleted = true 
    WHERE
        id = ?
*SQLDeleteは、永続性コンテキストで管理され、トランザクションが完了した後に実際のデータベースにクエリーが送信されたときに処理されます.

@Where


ソフトウェアdeleteを処理すると、クエリー要求時に削除されていないデータしかインポートできません.この場合、@Whereを使用することができる.
デフォルトでは、@Where宣言は適用されるwhere構文を表します.通常ソフトウェアdeleteを行うために使用されます.@SQLDeleteと同様に、エンティティに書き込むだけです.これはdefaultオプションとして、このエンティティをクエリーするすべてのリクエストに適用されます.
@Entity
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class Shop {

	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private String address;
    
    private boolean deleted = Boolean.FALSE; // 삭제 여부 기본값 false
}
その後、クエリー(ex. shopRepository.findAll())を行うと、where条件が自動的に追加されます.
    select
        shop0_.id as id1_11_,
        shop0_.address as address2_11_,
        shop0_.deleted as deleted3_11_,
        shop0_.name as name4_11_ 
    from
        shop shop0_ 
    where
        (
            shop0_.deleted = false
        )

継承関係の場合


ソフトウェアdeleteを処理するエンティティは、継承関係である場合があります.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Shop {

}
@Entity
@DiscriminatorValue("C")
public class Restaurant extends Shop {

}
@Entity
@DiscriminatorValue("R")
public class Cafe extends Shop {

}
親に対してのみ@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")の処理を行うと、サブテーブルは実際に削除されます.
下図に示すように、各サブクラスに対して@OnDelete(action = OnDeleteAction.CASCADE)の処理を行い、阻止することができる.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
@SQLDelete(sql = "UPDATE shop SET deleted = true WHERE id = ?")
public abstract class Shop {

}
@Entity
@DiscriminatorValue("C")
@OnDelete(action = OnDeleteAction.CASCADE)
public class Restaurant extends Shop {

}
@Entity
@DiscriminatorValue("R")
@OnDelete(action = OnDeleteAction.CASCADE)
public class Cafe extends Shop {

}

reference

  • https://www.baeldung.com/spring-jpa-soft-delete
  • https://steady-coding.tistory.com/579#%EA%B3%84%EC%B8%B5%ED%98%95_%EC%97%94%ED%8B%B0%ED%8B%B0%EC%97%90%EC%84%9C_%EC%A3%BC%EC%9D%98%ED%95%A0_%EC%A0%90