在企业级应用中,数据删除往往不是"真的删除"。在多数情况下,我们更倾向于**伪删除(Soft Delete)**通过标记字段的方式,将记录逻辑上设为删除状态,而非从数据库物理移除。
本文将从伪删除与真实删除的对比出发,带你深入理解 JPA 中的 @FilterDef
与 @Filter 注解如何优雅地实现伪删除逻辑,同时介绍 Hibernate 6.4+
引入的 @SoftDelete,帮助你构建更安全、可维护的数据层。
一、伪删除 vs 实际删除
实际删除(Hard 直接执行 简单、节省存储空间 数据无法恢复;外键约束可能被破坏;历史审计困难)
伪删除(Soft 添加一个状态字段(如 deleted 或 可恢复、可审计、安全性高 查询逻辑需额外过滤;容易遗忘过滤导致数据"穿透")
举个例子:
用户表user中,有字段is_deleted。
当我们调用"删除用户"接口时,实际执行的 SQL 不是DELETE,而是:UPDATE user SET is_deleted = true WHERE id = ?;同时,我们希望所有查询都自动加上
WHERE is_deleted = false。
这正是 @FilterDef 派上用场的地方。
二、Hibernate Filter 的设计思想
Hibernate 提供了一种优雅的机制:动态 SQL 过滤(Dynamic Filtering)。
通过 @FilterDef 定义过滤器参数,再通过 @Filter 为实体应用过滤条件。
当过滤器开启后,Hibernate 会自动在生成的 SQL 中附加相应的条件。
优势: - 不需要在每个 Repository 查询中手写过滤条件; -
灵活启用或关闭; - 不影响实体模型结构。
三、FilterDef 实战示例
1. 实体定义
import jakarta.persistence.*;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;
@Entity
@Table(name = "users")
@FilterDef(
name = "deletedFilter",
parameters = @ParamDef(name = "isDeleted", type = Boolean.class)
)
@Filter(
name = "deletedFilter",
condition = "is_deleted = :isDeleted"
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@Column(name = "is_deleted")
private Boolean isDeleted = false;
// getter/setter
}2. 启用过滤器
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.hibernate.Session;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserService {
private final EntityManager entityManager;
public List<User> findAllActiveUsers() {
Session session = entityManager.unwrap(Session.class);
session.enableFilter("deletedFilter")
.setParameter("isDeleted", false);
List<User> users = session.createQuery("from User", User.class).list();
session.disableFilter("deletedFilter");
return users;
}
public void softDelete(Long userId) {
Session session = entityManager.unwrap(Session.class);
User user = session.get(User.class, userId);
if (user != null) {
user.setIsDeleted(true);
session.merge(user);
}
}
}💡 使用 autoEnabled 自动启用过滤器
Hibernate 6+ 支持 autoEnabled 属性,可以自动激活过滤器。
@Entity
@Table(name = "users")
@FilterDef(
name = "deletedFilter",
parameters = @ParamDef(name = "isDeleted", type = Boolean.class)
)
@Filter(
name = "deletedFilter",
condition = "is_deleted = :isDeleted",
autoEnabled = true
)
public class User {
...
}无需再手动启用 session.enableFilter(),Hibernate 会自动附加过滤条件。
💡 组合使用伪删除与多租户 Filter
@Entity
@Table(name = "users")
@FilterDef(
name = "deletedFilter",
parameters = @ParamDef(name = "isDeleted", type = Boolean.class)
)
@Filter(
name = "deletedFilter",
condition = "is_deleted = :isDeleted",
autoEnabled = true
)
@FilterDef(
name = "tenantFilter",
parameters = @ParamDef(name = "tenantId", type = Long.class)
)
@Filter(
name = "tenantFilter",
condition = "tenant_id = :tenantId",
autoEnabled = true
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@Column(name = "tenant_id")
private Long tenantId;
@Column(name = "is_deleted")
private Boolean isDeleted = false;
// getter/setter
}Session session = entityManager.unwrap(Session.class);
session.enableFilter("deletedFilter").setParameter("isDeleted", false);
session.enableFilter("tenantFilter").setParameter("tenantId", tenantId);Hibernate 自动生成:
select * from users where is_deleted = false and tenant_id = 1001;四、BaseEntity 统一管理伪删除与租户过滤
我们可以将伪删除与租户字段统一放在 BaseEntity 中。
@MappedSuperclass
@Filter(name = "deletedFilter")
@Filter(name = "tenantFilter")
public abstract class BaseEntity {
@Column(name = "is_deleted", nullable = false)
protected Boolean isDeleted = false;
@Column(name = "tenant_id", nullable = false)
protected Long tenantId;
// getters/setters
}package-info.java
@FilterDef(
name = "deletedFilter",
condition = "is_deleted = false",
autoEnabled = true
)
@FilterDef(
name = "tenantFilter",
parameters = @ParamDef(name = "tenantId", type = Long.class),
condition = "tenant_id = :tenantId",
autoEnabled = true
)
package com.demo.domain;这样所有继承自 BaseEntity 的实体都自动具备伪删除与多租户过滤。
五、Hibernate 6.4+ 的 @SoftDelete
Hibernate 6.4 开始提供了官方注解 @SoftDelete,用于自动管理软删除:
import org.hibernate.annotations.SoftDelete;
import jakarta.persistence.*;
@Entity
@Table(name = "company_base_grade")
@SoftDelete // 默认 deleted=true 代表已删除
public class CompanyBaseGrade {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Company company;
private Long gradeId;
private Short type;
private Short state;
}优点
- 无需手动定义
isDeleted字段或过滤器; delete()自动改写为UPDATE ... SET deleted=true;- 所有 JPQL 查询自动附加
deleted=false;
注意
- 若需查询包含已删除数据,必须使用
nativeQuery=true,这个其实是使用原生的 SQL 进行查询; - 若需"恢复",使用普通
UPDATE将deleted=false; - 若需多租户或复杂条件,仍建议使用 FilterDef 机制。
六、Spring Data JPA 下的实践
在 Spring Data JPA 中,直接使用 Repository 也可与 @SoftDelete
无缝结合:
@Repository
public interface CompanyBaseGradeRepository<GRADE extends CompanyBaseGrade>
extends JpaRepository<GRADE, Long> {
@Query("from CompanyBaseGrade WHERE company = ?1 AND gradeId = ?2 AND state = 1")
Optional<GRADE> findByCompanyAndId(Company company, Long gradeId);
Integer findMaxLevelByCompanyAndType(Company company, Short type);
// 查询包含已删除
@Query(value = "SELECT * FROM company_base_grade WHERE company_id = :companyId", nativeQuery = true)
List<Map<String, Object>> findAllRowsByCompany(@Param("companyId") Long companyId);
// 恢复
@Modifying
@Query(value = "UPDATE company_base_grade SET deleted = false WHERE id = :id", nativeQuery = true)
int restoreById(@Param("id") Long id);
}七、何时使用 SoftDelete,何时使用 Filter
场景 推荐方案
只需默认隐藏删除数据 @SoftDelete
需要可开关过滤、多租户 @FilterDef + @Filter
想集中管理所有实体过滤 BaseEntity + package-info + autoEnabled
需要访问被删数据 原生 SQL 查询
八、总结
- 对核心业务数据(用户、订单、资金等),推荐伪删除 + FilterDef;
- 对非核心或简单表,可以直接使用 @SoftDelete;
- 若系统有多租户、审计等需求,优先设计通用的 BaseEntity + package-info 全局过滤方案;
- Spring Data JPA 场景下,可透明兼容上述两种模式。