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

Quarkus读写分离EntityManager引发ConcurrentModificationException异常

Quarkus读写分离JDBC配置下出现ConcurrentModificationException的解决方法

问题场景

在Quarkus应用中配置了独立的读写JDBC连接,执行服务类的查询与写入方法时,抛出以下异常:

java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1221)
	at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder.getOrCreateSession(RequestScopedSessionHolder.java:21)
	at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder_ClientProxy.getOrCreateSession(Unknown Source)
	at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.acquireSession(TransactionScopedSession.java:103)
	at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.createQuery(TransactionScopedSession.java:356)
	at org.hibernate.engine.spi.SessionLazyDelegator.createQuery(SessionLazyDelegator.java:547)
	at org.hibernate.engine.spi.SessionLazyDelegator.createQuery(SessionLazyDelegator.java:65)
	at org.hibernate.Session_fa5422f4bd76316308253b9ba0cef11c854fad1c_Synthetic_ClientProxy.createQuery(Unknown Source)
	at com.coda.codapay.payout.param.repository.PaymentMethodTypeHibernateEntityRepository.lambda$findByType$1(PaymentMethodTypeHibernateEntityRepository.java:46)
	at io.smallrye.context.impl.wrappers.SlowContextualFunction.apply(SlowContextualFunction.java:21)
	at io.smallrye.mutiny.operators.uni.UniOnItemTransform$UniOnItemTransformProcessor.onItem(UniOnItemTransform.java:36)
	at io.smallrye.mutiny.operators.uni.UniEmitOn$UniEmitOnProcessor.lambda$onItem$0(UniEmitOn.java:34)
	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)

涉及的Repository代码:

@ApplicationScoped 
public class PaymentMethodTypeHibernateEntityRepository implements PaymentMethodTypeRepository { 
    private final Vertx vertx;
    private final EntityManager readEntityManager;

    public PaymentMethodTypeHibernateEntityRepository(Vertx vertx,
                                                      @PersistenceUnit("read")  EntityManager readEntityManager) {
        this.vertx = vertx;
        this.readEntityManager = readEntityManager;
    }

    @Override
    public Uni<PaymentMethodType> findByType(String type) {
        return Uni.createFrom()
                .voidItem()
                .emitOn(Infrastructure.getDefaultExecutor())
                .onItem()
                .transform(unused -> (HibernatePaymentMethodType) readEntityManager.createQuery("SELECT paymentMethodType FROM HibernatePaymentMethodType paymentMethodType WHERE paymentMethodType.name = :type")
                        .setParameter("type", type)
                        .getSingleResult())
                .emitOn(MutinyHelper.executor(vertx))
                .onItem()
                .ifNotNull()
                .transform(HibernatePaymentMethodType::toPaymentMethodType);
    }
}

问题根源

EntityManager不是线程安全的,但你将其作为类成员变量持有,并在Mutiny的异步线程切换(emitOn)中跨线程使用。Quarkus默认的Hibernate ORM EntityManager是请求作用域的,跨线程访问时会触发SessionHolder内部HashMap的并发修改——HashMap本身不支持线程安全的computeIfAbsent操作,最终抛出异常。

解决方案

方案1:通过EntityManagerFactory创建线程独立的EntityManager

放弃持有全局EntityManager实例,改为在需要使用的线程内通过EntityManagerFactory创建新实例,并确保资源正确释放:

@ApplicationScoped 
public class PaymentMethodTypeHibernateEntityRepository implements PaymentMethodTypeRepository { 
    private final Vertx vertx;
    private final EntityManagerFactory readEntityManagerFactory;

    public PaymentMethodTypeHibernateEntityRepository(Vertx vertx,
                                                      @PersistenceUnit("read") EntityManagerFactory readEntityManagerFactory) {
        this.vertx = vertx;
        this.readEntityManagerFactory = readEntityManagerFactory;
    }

    @Override
    public Uni<PaymentMethodType> findByType(String type) {
        return Uni.createFrom()
                .voidItem()
                .emitOn(Infrastructure.getDefaultExecutor())
                .onItem()
                .transform(unused -> {
                    try (EntityManager em = readEntityManagerFactory.createEntityManager()) {
                        return (HibernatePaymentMethodType) em.createQuery("SELECT paymentMethodType FROM HibernatePaymentMethodType paymentMethodType WHERE paymentMethodType.name = :type")
                                .setParameter("type", type)
                                .getSingleResult();
                    }
                })
                .emitOn(MutinyHelper.executor(vertx))
                .onItem()
                .ifNotNull()
                .transform(HibernatePaymentMethodType::toPaymentMethodType);
    }
}

每个线程使用独立的EntityManager实例,从根本上避免线程安全问题。

方案2:使用Quarkus Panache简化异步查询

如果实体继承了PanacheEntity,可以直接利用Panache的异步API,它会自动处理EntityManager的线程安全和生命周期:

@ApplicationScoped
public class PaymentMethodTypeHibernateEntityRepository implements PaymentMethodTypeRepository { 
    private final Vertx vertx;

    public PaymentMethodTypeHibernateEntityRepository(Vertx vertx) {
        this.vertx = vertx;
    }

    @Override
    public Uni<PaymentMethodType> findByType(String type) {
        return HibernatePaymentMethodType.find("name", type)
                .singleResult()
                .emitOn(MutinyHelper.executor(vertx))
                .onItem()
                .ifNotNull()
                .transform(HibernatePaymentMethodType::toPaymentMethodType);
    }
}

方案3:用@Blocking保持请求上下文

如果坚持使用注入的EntityManager,可通过@Blocking注解标记方法,让Quarkus在工作线程池执行时保留请求上下文,避免跨线程访问问题:

@Override
@Blocking
public Uni<PaymentMethodType> findByType(String type) {
    return Uni.createFrom()
            .item(() -> (HibernatePaymentMethodType) readEntityManager.createQuery("SELECT paymentMethodType FROM HibernatePaymentMethodType paymentMethodType WHERE paymentMethodType.name = :type")
                    .setParameter("type", type)
                    .getSingleResult())
            .emitOn(MutinyHelper.executor(vertx))
            .onItem()
            .ifNotNull()
            .transform(HibernatePaymentMethodType::toPaymentMethodType);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:58:15