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

Hibernate事件监听器操作触发ConcurrentModificationException问题排查

问题

为数据库表配置了Hibernate监听器,触发时想查询另一张表并更新数据,但出现如下错误:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction 
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:571)
...
Caused by: java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)

代码示例:

@Slf4j
@RequiredArgsConstructor
@Component
public class MyTableHibernateListener implements PostInsertEventListener, PreUpdateEventListener, PostUpdateEventListener {
    private final JPQLQueryFactory queryFactory;

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        try {
            ...

            // Some working logic to set some data within the current MyTable.

            // Try to query another table below
            AnotherTable row = queryFactory.select(QAnotherTable.anotherTable)
                .from(QAnotherTable.anotherTable)
                .where(...)
                .fetchOne();
            ...
            log.info("Success");
            return false;
        } catch (Exception e) {
            log.error("Failure", e);
            throw e;
        }
    }
}

日志已打印“Success”且无失败日志,说明错误出现在监听器方法外部。尚未对另一张表执行修改,仅查询就触发了问题。

原因分析

  1. 监听器执行时机与Hibernate内部流程冲突:PreUpdateEventListener在事务提交前、Hibernate维护实体脏数据的关键阶段运行。此时Hibernate正遍历内部集合(比如待更新实体列表),而你用JPQLQueryFactory执行查询时,可能触发Session自动flush操作——这会修改Hibernate正在遍历的内部集合,直接引发ConcurrentModificationException。
  2. QueryFactory绑定当前Session的副作用:如果JPQLQueryFactory复用当前事务的Session,查询操作会触发Hibernate自动flush脏数据,干扰监听器执行时的内部状态维护逻辑,导致集合并发修改。

解决办法

方法1:使用独立Session执行查询

避开当前事务的Session,新建独立Session执行跨表查询,避免干扰Hibernate内部流程:

// 注入EntityManagerFactory
private final EntityManagerFactory entityManagerFactory;

@Override
public boolean onPreUpdate(PreUpdateEvent event) {
    try {
        // ...原有逻辑

        // 打开独立Session,不参与当前事务
        try (Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession()) {
            JPQLQueryFactory independentQueryFactory = new JPQLQueryFactory(session);
            AnotherTable row = independentQueryFactory.select(QAnotherTable.anotherTable)
                .from(QAnotherTable.anotherTable)
                .where(...)
                .fetchOne();
            // 后续操作
        }

        log.info("Success");
        return false;
    } catch (Exception e) {
        log.error("Failure", e);
        throw e;
    }
}

方法2:临时禁用自动Flush

在查询前将当前Session的FlushMode设为MANUAL,避免查询触发自动flush,执行完后恢复原有模式:

@Override
public boolean onPreUpdate(PreUpdateEvent event) {
    try {
        // ...原有逻辑

        Session session = event.getSession();
        FlushMode originalMode = session.getFlushMode();
        try {
            session.setFlushMode(FlushMode.MANUAL);
            AnotherTable row = queryFactory.select(QAnotherTable.anotherTable)
                .from(QAnotherTable.anotherTable)
                .where(...)
                .fetchOne();
            // 后续操作
        } finally {
            session.setFlushMode(originalMode);
        }

        log.info("Success");
        return false;
    } catch (Exception e) {
        log.error("Failure", e);
        throw e;
    }
}

方法3:改用PostCommit阶段执行操作

如果业务允许,切换到PostCommitUpdateEventListener,该监听器在事务提交完成后触发,此时Hibernate内部状态维护已结束,不会出现并发修改问题:

@Slf4j
@RequiredArgsConstructor
@Component
public class MyTableHibernateListener implements PostCommitUpdateEventListener {
    private final JPQLQueryFactory queryFactory;

    @Override
    public void onPostUpdateCommitFailed(PostUpdateEvent event) {
        // 处理提交失败场景
    }

    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        try {
            AnotherTable row = queryFactory.select(QAnotherTable.anotherTable)
                .from(QAnotherTable.anotherTable)
                .where(...)
                .fetchOne();
            // 执行更新操作(需开启新事务)
            log.info("Success");
        } catch (Exception e) {
            log.error("Failure", e);
            throw e;
        }
    }
}

注意:PostCommit阶段不在原事务范围内,若需修改数据需手动开启新事务。

内容的提问来源于stack exchange,提问作者Azianese

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 22:15:52