概述
在使用Spring Boot JPA時,執行批量刪除操作時,遇到邏輯刪除失效的問題。具體而言,當使用deleteAllInBatch
方法時,數據會被物理刪除,而不是進行邏輯刪除;但是當使用deleteAll
時,邏輯刪除操作可以正常生效。經過調查,發現deleteAllInBatch
方法和deleteAll
方法的行為有所不同,導致邏輯刪除失敗。
癥狀
- 使用
deleteAllInBatch
方法時,數據直接從數據庫中物理刪除。 - 使用
deleteAll
方法時,邏輯刪除生效,數據并未被物理刪除,而是更新了delLogic
字段。
使用的實體類代碼
@Entity
public class TemplateField {@Idprivate Long id;private Integer delLogic; // 用于標記是否被邏輯刪除@PreRemovepublic void templateField() {this.setDelLogic(1); // 設置邏輯刪除標記}@SQLDelete(sql = "UPDATE t_template_field SET del_logic = 1 WHERE id = ?")@Where(clause = "del_logic = 0") // 過濾刪除標記為0的數據public void setDelLogic(Integer delLogic) {this.delLogic = delLogic;}
}
問題原因
問題的根本原因是deleteAllInBatch
和deleteAll
在執行刪除操作時的實現方式不同,導致生命周期回調方法(如@PreRemove
)未被觸發。
deleteAllInBatch
和deleteAll
的區別
-
deleteAll()
:該方法會逐個加載實體,并在JPA上下文中處理每個實體的刪除操作。每次刪除實體時,都會觸發實體的生命周期回調方法(如@PreRemove
、@PostRemove
等)。因此,當使用deleteAll()
方法時,你在實體類上定義的邏輯刪除(例如通過@PreRemove
標記設置刪除標記)可以生效。 -
deleteAllInBatch()
:該方法是一個批量刪除操作,通常是直接生成SQL語句一次性刪除數據,不會逐個加載實體,因此也不會觸發實體的生命周期回調方法。批量操作的優勢在于效率較高,但缺點是無法觸發與實體相關的生命周期事件,如@PreRemove
和@PostRemove
。
deleteAllInBatch
導致物理刪除的原因
deleteAllInBatch()
方法并不會按@PreRemove
中的邏輯設置delLogic
字段,而是直接執行數據庫的物理刪除操作。這就是為什么在使用deleteAllInBatch()
時,數據會被直接從數據庫中刪除,而不是進行邏輯刪除的原因。
問題解決方案
要解決這個問題,通常有以下幾種方式:
方案 1:使用deleteAll()
替代deleteAllInBatch()
如果邏輯刪除的需求比性能更為重要,并且不介意性能稍微下降,可以直接使用deleteAll()
方法。這會逐個處理實體,并觸發相應的生命周期回調方法,從而確保邏輯刪除(即更新delLogic
字段)生效。
方案 2:自定義批量更新方法
如果依然希望使用批量刪除操作(如deleteAllInBatch()
),可以自定義一個批量更新的方法,通過直接執行SQL更新操作來實現邏輯刪除。這種方式可以保證批量操作時的效率,同時避免物理刪除數據。
例如,使用@Modifying
和@Query
注解,執行批量更新操作:
@Modifying
@Query("UPDATE TemplateField tf SET tf.delLogic = 1 WHERE tf.id IN :ids")
int batchLogicalDelete(@Param("ids") List<Long> ids);
該方法會直接更新符合條件的記錄,將delLogic
字段設置為1,達到邏輯刪除的效果。
方案 3:手動更新實體后再執行批量刪除
可以先通過查詢獲取所有需要“刪除”的實體,將它們的delLogic
字段設置為邏輯刪除標志,然后再調用deleteAllInBatch()
進行刪除操作。
代碼示例:
List<TemplateField> fields = templateFieldRepository.findAllById(ids);
fields.forEach(field -> field.setDelLogic(1)); // 更新邏輯刪除標志
templateFieldRepository.saveAll(fields); // 保存更新
templateFieldRepository.deleteAllInBatch(fields); // 執行批量刪除
這種方式在批量刪除前,先手動更新實體,確保邏輯刪除字段被正確設置。
問題示例代碼
使用deleteAll()
進行邏輯刪除
List<TemplateField> fields = templateFieldRepository.findAllById(ids);
fields.forEach(field -> field.setDelLogic(1)); // 更新邏輯刪除標志
templateFieldRepository.saveAll(fields); // 保存更新
templateFieldRepository.deleteAll(fields); // 執行逐個刪除(觸發生命周期方法)
自定義批量更新方法進行邏輯刪除
@Modifying
@Query("UPDATE TemplateField tf SET tf.delLogic = 1 WHERE tf.id IN :ids")
int batchLogicalDelete(@Param("ids") List<Long> ids);// 調用自定義批量邏輯刪除方法
templateFieldRepository.batchLogicalDelete(ids);
手動更新實體后再執行批量刪除
List<TemplateField> fields = templateFieldRepository.findAllById(ids);
fields.forEach(field -> field.setDelLogic(1)); // 更新邏輯刪除標志
templateFieldRepository.saveAll(fields); // 保存更新
templateFieldRepository.deleteAllInBatch(fields); // 執行批量刪除
總結
deleteAllInBatch()
方法直接執行SQL批量刪除,不會觸發實體的生命周期回調方法(如@PreRemove
),導致邏輯刪除無效。- 如果需要觸發回調方法,可以使用
deleteAll()
,但會影響性能。 - 也可以自定義批量邏輯刪除方法,通過直接更新
delLogic
字段來避免物理刪除。