如何正确使用 @Transactional 注解确保级联删除的事务一致性

在 spring 中为删除方法添加 @transactional 注解时,若涉及多表级联操作(如先删子表再删主表),因 hibernate 默认延迟刷新(flush)可能导致外键约束失败;需显式调用 flush() 或合理配置级联策略来保证事务内操作顺序与数据库一致性。

当我们在服务层对 removeById() 方法添加 @Transactional 注解后,看似逻辑更健壮了——整个删除流程被纳入同一数据库事务,出错可整体回滚。但实际运行却抛出如下异常:

ERROR: NULL value in column "car_id" of relation "violations" violates NOT NULL constraint

该错误表面是违反了 violations.car_id NOT NULL 约束,根本原因在于:Hibernate 在事务提交前并未立即执行 SQL 删除语句,而是将 violationService.removeByCarId(id) 的删除操作缓存在一级缓存中,直到 flush 或 commit 时才真正发出 DELETE 语句。而紧接着 repository.deleteById(id) 尝试删除 cars 表中的主记录时,数据库仍检测到 violations 表中存在 car_id = id 的残留数据(尚未物理删除),从而触发外键/NOT NULL 校验失败(尤其当 violations.car_id 被设为非空且无级联时)。

✅ 正确解决方案是在子表删除后、主表删除前强制刷新一级缓存,使子表删除 SQL 立即执行:

@Service
public class CarService {

    @Transactional
    @Override
    public void removeById(Long id) {
        violationService.removeByCarId(id);
        // 关键:强制刷新,确保 violations 删除语句已发送至数据库
        violationService.flush(); // 或:entityManager.flush();
        repository.deleteById(id);
    }
}

对应地,ViolationService 需暴露 flush() 方法(假设使用 JPA):

@Service
public class ViolationService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void removeByCarId(Long carId) {
        String jpql = "DELETE FROM Violation v WHERE v.car.id = :carId";
        entityManager.createQuery(jpql).setPar

ameter("carId", carId).executeUpdate(); // 注意:JPQL 批量删除不触发实体生命周期事件,也不影响一级缓存中的已有实体 // 若使用 deleteAll(Iterable) 等基于实体的操作,则需手动 clear/flush } // 提供显式 flush 支持 public void flush() { entityManager.flush(); } }

⚠️ 注意事项:

  • 不要依赖 @Modifying(clearAutomatically = true) 自动清空缓存,它仅适用于 @Query + @Modifying 场景,且 clearAutomatically=true 会清空整个持久化上下文,可能影响其他并发操作;
  • 更优雅的长期方案是:在 Car 实体中配置 @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true),并让 JPA 自动管理级联删除(此时 repository.deleteById(id) 即可一并删除关联 Violation),避免手动分步删除;
  • 若必须分步操作,务必在关键步骤间插入 flush(),而非仅靠 @Transactional 保证“原子性”——事务控制的是提交/回滚边界,而 flush 控制的是 SQL 执行时机。

总结:@Transactional 保证事务边界,但不控制 SQL 发送顺序;面对跨表依赖删除,应主动调用 flush() 显式同步状态,才能真正实现“要么全成功,要么全回滚”的强一致性语义。