BEFORE_COMPLETION阶段事务CDI观察者触发MyEntity重复插入异常
根因分析
该异常由Payara 5默认集成的JPA实现EclipseLink的事务BEFORE_COMPLETION阶段特殊刷新逻辑导致,触发流程如下:
- 业务逻辑中执行
MyEntity的getOrSave方法时,persist后手动flush触发了第一次INSERT,此时实体已纳入当前持久化上下文,但EclipseLink内部的变更队列不会清除该实体的新增标记 TransactionPhase.BEFORE_COMPLETION是事务提交前的最后回调阶段,EclipseLink在此阶段执行任何JPA查询前,会强制触发全量持久化上下文变更刷新,且不会校验实体是否已经刷入数据库,直接对标记为新增的实体再次生成INSERT语句,触发唯一键冲突- 你确认
getOrSave和fetchSoftwareSearchViewById共用同一个EntityManager,持久化上下文完全共享,因此查询触发的刷新会扫到之前已经刷入的MyEntity实例
可行修复方案
可根据业务需求选择以下任意方案:
- 调整CDI事件监听阶段:将监听的事务阶段从
TransactionPhase.BEFORE_COMPLETION改为TransactionPhase.AFTER_SUCCESS,若读模型更新不需要和业务事务保持原子性,该方案改造成本最低,完全规避提交前的额外刷新操作 - 禁用查询触发的自动刷新:在
fetchSoftwareSearchViewById方法的JPA查询执行前,设置查询刷新模式为FlushModeType.COMMIT,示例代码:
TypedQuery<SoftwareSearchView> query = entityManager.createQuery("你的查询逻辑", SoftwareSearchView.class); query.setFlushMode(FlushModeType.COMMIT); return query.getSingleResult();
- 替换JPA实现:该行为为EclipseLink特有,将Payara默认JPA实现替换为Hibernate即可解决,Hibernate在
BEFORE_COMPLETION阶段的刷新逻辑会校验实体已刷入状态,不会生成重复INSERT语句
验证方法
开启EclipseLink的SQL参数绑定日志,确认两次INSERT语句的唯一键、主键值完全一致,即可排除事务传播异常、多实例调用等其他问题。
内容的提问来源于stack exchange,提问作者matthjes
相关产品推荐
相关产品推荐

