在 JPA 中,逻辑删除(软删除) 通常通过在实体类中增加一个标记字段(如 isDeleted、deleted、delFlag)来实现。删除时不是物理删除记录,而是将该字段更新为已删除状态,查询时默认过滤掉已删除的数据。
一、基础实现方式
1. 实体类添加逻辑删除字段
java
import javax.persistence.*;
@Entity
@Table(name = "vehicle")
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String vin;
private String plateNumber;
// 逻辑删除字段:0-未删除,1-已删除
@Column(name = "is_deleted")
private Integer isDeleted = 0; // 默认值 0
// 或者使用 Boolean
// @Column(name = "is_deleted")
// private Boolean deleted = false;
// getter/setter…
}
2. 自定义逻辑删除方法
java
@Service
public class VehicleService {
@Autowired
private VehicleRepository vehicleRepository;
@Transactional
public void logicDelete(Long id) {
// 方案一:先查询再更新
Vehicle vehicle = vehicleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("记录不存在"));
vehicle.setIsDeleted(1);
vehicleRepository.save(vehicle);
}
@Transactional
public void logicDeleteBatch(List<Long> ids) {
// 批量逻辑删除
List<Vehicle> vehicles = vehicleRepository.findAllById(ids);
vehicles.forEach(v -> v.setIsDeleted(1));
vehicleRepository.saveAll(vehicles);
}
}
3. 查询时过滤已删除数据
java
public interface VehicleRepository extends JpaRepository<Vehicle, Long> {
// 默认查询只返回未删除的
@Query("SELECT v FROM Vehicle v WHERE v.isDeleted = 0")
List<Vehicle> findAllActive();
@Query("SELECT v FROM Vehicle v WHERE v.isDeleted = 0 AND v.id = :id")
Optional<Vehicle> findActiveById(@Param("id") Long id);
// 如果需要查询全部(包括已删除)
@Query("SELECT v FROM Vehicle v")
List<Vehicle> findAllIncludeDeleted();
}
二、使用 @Where 注解(自动过滤)
Hibernate 提供的 @Where 注解可以在所有查询中自动附加过滤条件:
java
import org.hibernate.annotations.Where;
@Entity
@Table(name = "vehicle")
@Where(clause = "is_deleted = 0") // ✅ 所有查询自动加上 is_deleted = 0
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String vin;
private String plateNumber;
@Column(name = "is_deleted")
private Integer isDeleted = 0;
// getter/setter…
}
这样写之后,findAll()、findById() 等方法自动只查未删除的数据,无需每次手动加条件。
注意:@Where 是 Hibernate 的注解,不是 JPA 标准,但大多数 JPA 实现(如 Hibernate)都支持。
三、使用 @SQLDelete + @SQLRestriction(更优雅)
java
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
@Entity
@Table(name = "vehicle")
@SQLDelete(sql = "UPDATE vehicle SET is_deleted = 1 WHERE id = ?") // 逻辑删除 SQL
@SQLRestriction("is_deleted = 0") // 查询时自动过滤
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String vin;
private String plateNumber;
@Column(name = "is_deleted")
private Integer isDeleted = 0;
// getter/setter…
}
这样 repository.delete(entity) 和 repository.deleteById(id) 会自动执行 UPDATE 而不是 DELETE,查询时自动过滤已删除数据。
四、方案对比
| 手动更新字段 | 简单直接,无需额外注解 | 每个删除都要写 update,容易遗漏 | 简单项目,删除场景少 |
| @Where 自动过滤 | 查询自动过滤,代码简洁 | 无法区分"需要查全部"的场景 | 大多数业务场景 |
| @SQLDelete + @SQLRestriction | 最接近物理删除的体验,delete() 自动变 update | 需要 Hibernate 支持 | 希望 delete 方法自动逻辑删除 |
五、结合 Spring Data JPA 的 @Query 实现
如果不想用 Hibernate 特定注解,可以用 @Query + @Modifying 实现:
java
public interface VehicleRepository extends JpaRepository<Vehicle, Long> {
@Modifying
@Query("UPDATE Vehicle v SET v.isDeleted = 1 WHERE v.id = :id")
int logicDeleteById(@Param("id") Long id);
@Modifying
@Query("UPDATE Vehicle v SET v.isDeleted = 1 WHERE v.id IN :ids")
int logicDeleteByIds(@Param("ids") List<Long> ids);
// 查询时手动过滤
@Query("SELECT v FROM Vehicle v WHERE v.isDeleted = 0")
List<Vehicle> findAllActive();
}
注意:@Modifying 方法需要在 @Transactional 中执行。
六、完整示例
java
@Entity
@Table(name = "vehicle")
@SQLDelete(sql = "UPDATE vehicle SET is_deleted = 1, delete_time = NOW() WHERE id = ?")
@SQLRestriction("is_deleted = 0")
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String vin;
private String plateNumber;
@Column(name = "is_deleted")
private Integer isDeleted = 0;
@Column(name = "delete_time")
private LocalDateTime deleteTime;
// getter/setter…
}
// Service 层
@Service
@Transactional
public class VehicleService {
@Autowired
private VehicleRepository repository;
public void delete(Long id) {
repository.deleteById(id); // ✅ 自动执行 UPDATE
// 执行的 SQL: UPDATE vehicle SET is_deleted = 1, delete_time = NOW() WHERE id = ?
}
public List<Vehicle> list() {
return repository.findAll(); // ✅ 自动过滤 is_deleted = 0
}
}
七、总结
| 标准 JPA + 不想加 Hibernate 注解 | 自定义 @Query + @Modifying |
| 使用 Hibernate + 希望 delete() 自动逻辑删除 | @SQLDelete + @SQLRestriction |
| 查询自动过滤已删除数据 | @Where(clause = "is_deleted = 0") |
| 需要记录删除时间 | 在实体类加 deleteTime 字段,更新 SQL 中一起设置 |





