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
相关产品推荐
相关产品推荐

