Hibernate save调用引发线程挂起问题排查求助
先说说你的场景:咱们系统用的是Hibernate 4.3.10 + Spring的DAO模式,每个数据库操作对应独立DAO类,数据库是SQL Server。最近碰到个棘手的情况——调用Hibernate的save()方法时线程直接挂起,但同款代码在另一个同版本的独立服务器+独立数据库环境下跑完全没问题。更奇怪的是,应用代码已经两年没改过了,这就有点让人摸不着头脑。
先贴一下你提供的关键代码和配置:
核心DAO代码
// BusinessAlerts是普通POJO类 @Override @Transactional(propagation = Propagation.REQUIRED) public void persistBusinessAlert(BusinessAlerts businessAlert) throws DAOException { if (businessAlert == null) { return; } try { currentSession().save(businessAlert); } catch (HibernateException e) { throw new DAOException(e.getMessage(), e); } }
Hibernate相关配置(Spring XML)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <tx:annotation-driven transaction-manager="priceTransactionManager" /> <bean class="mro.util.CryptoPropertyPlaceholderConfigurer"> <property name="location" value="common_db.properties" /> </bean> <bean id="priceDataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${db.driver}" /> <property name="url" value="${alert.db.url}" /> <property name="username" value="${alert.db.username}" /> <property name="password" value="${alert.db.password}" /> </bean> <bean id="priceSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="priceDataSource" /> <property name="annotatedClasses"> <list> <value>BusinessAlerts</value> <value>Executions</value> <value>AlertCodes</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <bean id="priceTransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="priceSessionFactory" /> </bean> </beans>
currentSession()的调用逻辑
protected final org.hibernate.Session currentSession() throws org.springframework.dao.DataAccessResourceFailureException; 0 aload_0 [this] 1 invokevirtual org.springframework.orm.hibernate4.support.HibernateDaoSupport.getSessionFactory() : org.hibernate.SessionFactory [10] 4 invokeinterface org.hibernate.SessionFactory.getCurrentSession() : org.hibernate.Session [11] [nargs: 1] 9 areturn Line numbers: [pc: 0, line: 129] Local variable table: [pc: 0, pc: 10] local: this index: 0 type: org.springframework.orm.hibernate4.support.HibernateDaoSupport
可能的排查方向
结合你的场景,我整理了几个优先级较高的排查点:
1. 数据库连接池耗尽(DBCP配置缺失)
你用的是org.apache.commons.dbcp.BasicDataSource,但目前的配置里完全没设置连接池的核心参数——比如最大连接数、获取连接的超时时间。如果当前环境的并发量比正常环境高,或者存在未释放的连接(比如事务异常导致连接没回收),很容易把连接池占满,后续请求在获取连接时就会无限挂起。
- 建议:
- 给DBCP补充必要的配置参数,比如:
<property name="maxActive" value="20"/> <!-- 最大活跃连接数 --> <property name="maxWait" value="3000"/> <!-- 获取连接的超时时间(毫秒),超时会抛异常而不是挂起 --> <property name="minIdle" value="5"/> <!-- 最小空闲连接数 --> <property name="removeAbandoned" value="true"/> <!-- 自动回收废弃连接 --> <property name="removeAbandonedTimeout" value="180"/> <!-- 废弃连接的超时时间(秒) --> - 查看SQL Server的
sys.dm_exec_connections视图,统计当前的数据库连接数,确认是否接近或达到上限。
- 给DBCP补充必要的配置参数,比如:
2. 数据库层面的阻塞或死锁
代码没改,但数据库环境可能存在锁竞争或者死锁。当Hibernate执行save()对应的INSERT语句时,如果这条语句需要等待某个锁(比如其他事务长时间占用了BusinessAlerts表的锁,或者有未提交的事务持有相关行锁),线程就会卡在等待锁的状态。
- 建议:
- 在SQL Server里执行
sp_who2命令,查看是否有阻塞会话,找到阻塞源(BlkBy列不为空的行),检查对应的事务是否长时间运行。 - 查看SQL Server的死锁日志(可以通过SQL Server Management Studio的“管理->扩展事件”或者系统视图
sys.dm_tran_deadlocks),确认是否有死锁发生。
- 在SQL Server里执行
3. Hibernate Session/事务的上下文绑定异常
你的currentSession()是通过SessionFactory.getCurrentSession()获取的,这个方法依赖于Spring事务管理器绑定的线程上下文Session。如果当前环境的线程上下文存在异常(比如类加载器不一致,或者事务传播行为的隐性冲突),可能导致Session无法正常获取或释放,进而卡住线程。
- 建议:
- 查看线程堆栈的阻塞点,如果是卡在
getCurrentSession()或者DataSource.getConnection()这一步,那大概率是Session/连接的绑定问题。 - 临时做个测试:把
currentSession()换成sessionFactory.openSession(),用完手动关闭(session.close()),看是否还会挂起。如果问题消失,说明是getCurrentSession()的上下文绑定出了问题。
- 查看线程堆栈的阻塞点,如果是卡在
4. 环境隐性差异(JDBC驱动/数据库补丁)
你说其他环境版本一致,但可能存在细微的隐性差异:
- 比如当前环境的SQL Server JDBC驱动版本和正常环境不一致,或者数据库的补丁级别不同,导致Hibernate的底层JDBC调用出现阻塞。
- 建议:
- 对比两个环境的JDBC驱动jar包(比如
sqljdbc4.jar之类的),确保版本完全一致。 - 检查SQL Server的版本和补丁,确认是否有已知的JDBC兼容性问题(比如某些补丁会导致INSERT操作的锁行为变化)。
- 对比两个环境的JDBC驱动jar包(比如
5. 服务器资源瓶颈
当前环境的服务器资源(CPU、内存、磁盘IO)不足,也可能导致线程挂起:
- 比如内存不足导致Hibernate的一级/二级缓存无法正常工作,或者数据库服务器的磁盘IO过高,导致写入操作缓慢,进而阻塞线程。
- 建议:
- 检查应用服务器的CPU、内存使用率,是否有资源耗尽的情况(比如内存溢出前的频繁GC,或者CPU被其他进程占满)。
- 检查数据库服务器的磁盘读写延迟、队列长度,确认是否有IO瓶颈。
内容的提问来源于stack exchange,提问作者Vikash Jain

