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

压测场景下Hibernate出现ConcurrentModificationException的问题排查与解决方案咨询

压测场景下Hibernate出现ConcurrentModificationException的问题排查与解决方案咨询

我这边有个基于以下技术栈搭建的应用:

  • HK2(依赖注入框架)
  • Hibernate(ORM框架)
  • GrizzlyHttpServer(Web服务器)

平时运行一切正常,但一进行压测就抛出了ConcurrentModificationException异常,麻烦帮忙分析下问题根源,还有怎么修复,比如配置连接池这类的方案?

应用核心代码展示

Hibernate配置类

import org.hibernate.*;
import org.hibernate.boot.*;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.jvnet.hk2.annotations.Service;

@Service
public class HibernateOracle {

    private Session session;
    private SessionFactory factory;

    public HibernateOracle() {
        final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure() // 从hibernate.cfg.xml加载配置
                .build();
        factory = new MetadataSources(registry).addPackage("com.art.entity").buildMetadata().buildSessionFactory();
        session = factory.openSession();
    }

    public Session getSession() {
        if (session == null || !session.isOpen())
            session = factory.openSession();
        return this.session;
    }
}

Repository中的查询方法

@jakarta.inject.Inject
private HibernateOracle hibernateOracle;

@Override
public User get(String username) throws UserNotFoundException {
    Session session = hibernateOracle.getSession();
    try {
        return (User) session.createNamedQuery("User.findOneByUserName")
                .setParameter("username", username).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
        throw new UserNotFoundException();
    } finally {
        // 此处未做Session关闭操作
    }
}

压测时抛出的异常栈

java.util.ConcurrentModificationException at
java.base/java.util.HashMap.forEach(HashMap.java:1424) at
org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl.releaseResources(ResourceRegistryStandardImpl.java:328)
at
org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.afterTransaction(AbstractLogicalConnectionImplementor.java:60)
at
org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.afterTransaction(LogicalConnectionManagedImpl.java:167)
at
org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.afterTransaction(JdbcCoordinatorImpl.java:276)
at
org.hibernate.internal.SessionImpl.afterOperation(SessionImpl.java:550)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1464) at
org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1649)
at
org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617)
at
org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1665)
at
com.art.vesal.backend.core.dao.impl.UserDaoImpl.get(UserDaoImpl.java:238)
at
com.art.vesal.backend.core.controller.customer_management.UserControllerImpl.get(UserControllerImpl.java:159)
at
com.art.vesal.backend.core.controller.customer_management.CustomerControllerImpl.checkCustomer(CustomerControllerImpl.java:598)
at
com.art.vesal.backend.core.controller.messaging.MTSMSControllerImpl.sendMessageManyToMany(MTSMSControllerImpl.java:282)
at
com.art.vesal.backend.core.controller.messaging.ArtMTSMSImpl.sendMessageManyToMany(ArtMTSMSImpl.java:140)
at
com.art.vesal.backend.core.controller.rest.MessageRelayRest.sendMessageManyToMany(MessageRelayRest.java:26)
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown
Source) at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568) at
org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
at
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124)
at
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167)
at
org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:219)
at
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79)
at
org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475)
at
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397)
at
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
at
org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248) at
org.glassfish.jersey.internal.Errors$1.call(Errors.java:244) at
org.glassfish.jersey.internal.Errors.process(Errors.java:292) at
org.glassfish.jersey.internal.Errors.process(Errors.java:274) at
org.glassfish.jersey.internal.Errors.process(Errors.java:244) at
org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
at
org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
at
org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:680)
at
org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:356)
at
org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:190)
at
org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:535)
at
org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:515)
at java.base/java.lang.Thread.run(Thread.java:833)

## 问题根源分析
核心问题出在`HibernateOracle`类的设计上:
- HK2的`@Service`默认是**单例模式**,整个应用只会创建一个`HibernateOracle`实例
- 这个单例里维护了一个全局共享的`Session`对象,压测时多个请求线程会同时操作这个Session
- Hibernate的`Session`本身是**线程不安全**的,多线程并发访问会导致其内部状态(比如资源注册表的HashMap)被并发修改,从而触发`ConcurrentModificationException`

另外还有两个辅助问题:
1. Repository方法的finally块没有关闭Session,会导致数据库连接泄漏,压测时容易出现连接池耗尽的情况
2. 未配置Hibernate连接池,默认的连接管理方式无法应对高并发场景

## 修复方案

### 方案一:每次获取新的Session并手动关闭
修改`HibernateOracle`,去掉全局Session,每次调用`getSession()`都返回新的Session:
```java
@Service
public class HibernateOracle {

    private SessionFactory factory;

    public HibernateOracle() {
        final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure()
                .build();
        factory = new MetadataSources(registry).addPackage("com.art.entity").buildMetadata().buildSessionFactory();
    }

    // 每次返回新的Session,由调用方负责关闭
    public Session getSession() {
        return factory.openSession();
    }
}

然后修改Repository方法,在finally块中关闭Session:

@Override
public User get(String username) throws UserNotFoundException {
    Session session = hibernateOracle.getSession();
    try {
        return (User) session.createNamedQuery("User.findOneByUserName")
                .setParameter("username", username).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
        throw new UserNotFoundException();
    } finally {
        if (session != null && session.isOpen()) {
            session.close(); // 用完关闭Session,释放数据库连接
        }
    }
}

方案二:配置HikariCP连接池

在hibernate.cfg.xml中配置HikariCP(Hibernate 5.4+默认支持),让Hibernate自动管理连接池:

<!-- 启用HikariCP连接池 -->
<property name="hibernate.connection.provider_class">org.hibernate.hikaricp.internal.HikariCPConnectionProvider</property>
<!-- 连接池大小配置,根据压测需求调整 -->
<property name="hibernate.hikari.maximumPoolSize">20</property>
<property name="hibernate.hikari.minimumIdle">5</property>
<!-- 连接超时设置 -->
<property name="hibernate.hikari.idleTimeout">30000</property>
<property name="hibernate.hikari.connectionTimeout">20000</property>

配置后Hibernate会自动复用连接,避免频繁创建销毁连接的开销,同时也能更好地应对高并发场景。

方案三:使用请求级Scope绑定Session

将HibernateOracle的Scope改为请求级,让每个请求拥有独立的Session,避免多线程冲突:

import org.glassfish.hk2.api.RequestScope;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.annotations.Scope;
import javax.annotation.PreDestroy;

@Service
@Scope(RequestScope.class) // 每个请求创建一个实例
public class HibernateOracle {

    private Session session;
    private SessionFactory factory;

    public HibernateOracle() {
        final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure()
                .build();
        factory = new MetadataSources(registry).addPackage("com.art.entity").buildMetadata().buildSessionFactory();
        session = factory.openSession();
    }

    public Session getSession() {
        return this.session;
    }

    // 请求结束时自动关闭Session
    @PreDestroy
    public void closeSession() {
        if (session != null && session.isOpen()) {
            session.close();
        }
    }
}

这样每个请求都会有专属的Session,既避免了多线程问题,又不用手动管理Session的关闭。

备注:内容来源于stack exchange,提问作者reza ramezani matin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 12:39:12