You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

更新Task时同步更新Workflow的lastModifiedDate遇栈溢出问题求助

问题

有Workflow和Task两个实体类,Task通过@ManyToOne关联Workflow。需求是当Task对象更新时,同步更新关联Workflow的lastModifiedDate字段。为避免重复代码,使用带@PreUpdate注解的TaskAuditListener实现逻辑,在监听器中调用WorkflowRepository的自定义更新方法,但调用workflowRepository.updateLastModifiedDateById()时触发java.lang.StackOverflowError异常。

疑问:

  • 更新的是Workflow对象而非Task,为何@PreUpdate会被循环触发?
  • 是否有更优实现方案?

相关代码

Workflow 实体

@Entity
public class Workflow {

  @Column(name = "last_modified_date")
  private Date lastModifiedDate;
  // 其他字段和方法
}

Task 实体

@Entity
@EntityListeners(TaskAuditListener.class)
public class Task {

  @ManyToOne
  @JoinColumn(name = "workflow_id")
  private Workflow workflow;
  // 其他字段和方法
}

TaskAuditListener 监听器

@Component
public class TaskAuditListener {

  private static WorkflowRepository workflowRepository;

  @Autowired
  public void TaskAuditListener(WorkflowRepository workflowRepository) {
    TaskAuditListener.workflowRepository = workflowRepository;
  }

  @PreUpdate
  void beforeUpdate(Task task) {
    workflowRepository.updateLastModifiedDateById(task.getWorkflow().getId(),
        new Date(System.currentTimeMillis()));
  }
}

WorkflowRepository 更新方法

@Modifying
@Transactional
@Query("UPDATE Workflow w SET w.lastModifiedDate = :lastModifiedDate WHERE w.id = :workflowId")
void updateLastModifiedDateById(String workflowId, Date lastModifiedDate);

原因分析

栈溢出的核心是事务内的实体状态同步循环:

  1. Task的@PreUpdate监听器触发后,调用WorkflowRepository的JPQL更新方法,该操作处于同一个事务中。
  2. Hibernate执行JPQL更新后,会检查当前持久化上下文里的关联实体——由于Task对象仍持有被更新的Workflow实例,Hibernate会尝试同步Task的关联状态,这会再次触发Task的@PreUpdate监听器。
  3. 上述过程反复循环,最终导致栈溢出。

优化方案

方案1:禁用JPQL更新后的上下文刷新

在WorkflowRepository的更新方法上添加查询提示,让Hibernate执行JPQL后不立即刷新持久化上下文,打断循环:

@Modifying
@Transactional
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "COMMIT"))
@Query("UPDATE Workflow w SET w.lastModifiedDate = :lastModifiedDate WHERE w.id = :workflowId")
void updateLastModifiedDateById(String workflowId, Date lastModifiedDate);

方案2:直接操作持久化实体

通过EntityManager获取Workflow实体直接修改,利用Hibernate自动同步机制,同时避免触发额外监听:

@Component
public class TaskAuditListener {

  @PersistenceContext
  private EntityManager entityManager;

  @PreUpdate
  void beforeUpdate(Task task) {
    Workflow workflow = entityManager.find(Workflow.class, task.getWorkflow().getId());
    if (workflow != null) {
      // 若Workflow有自己的PreUpdate监听器,可临时禁用避免连锁触发
      Session session = entityManager.unwrap(Session.class);
      PreUpdateEventListener[] listeners = session.getEventListeners().getPreUpdateEventListeners();
      session.getEventListeners().setPreUpdateEventListeners(new PreUpdateEventListener[0]);
      
      workflow.setLastModifiedDate(new Date());
      
      // 恢复监听器
      session.getEventListeners().setPreUpdateEventListeners(listeners);
    }
  }
}

方案3:数据库触发器(推荐)

直接在数据库层面创建触发器,当Task表更新时自动同步Workflow的时间字段,完全绕过Java代码的事务和监听逻辑:

-- MySQL 示例
DELIMITER //
CREATE TRIGGER update_workflow_last_modified
AFTER UPDATE ON task
FOR EACH ROW
BEGIN
  UPDATE workflow 
  SET last_modified_date = NOW() 
  WHERE id = NEW.workflow_id;
END //
DELIMITER ;

内容的提问来源于stack exchange,提问作者Martin Kršek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 00:05:23