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

Spring5升级后数据库连接无法关闭,单事务占用多连接问题

Spring 5升级后数据库连接异常问题分析与解决

问题描述

项目在Spring 3.2版本下运行正常,升级至Spring 5后出现以下异常:

  • 数据库连接保持打开状态30分钟才释放
  • 单个事务占用7个数据库连接
    使用c3p0作为连接池,未修改业务代码。

相关配置与代码实现

Spring XML配置

<bean id="pum"
      class="com.exp.jpa.integration.KaraPersistenceUnitManager">
    <property name="persistenceXmlLocations" value="${jpa.persistenceXmlLocations}"/>
    <property name="persistenceUnitPostProcessors">
        <bean class="com.exp.framework.jpa.MergingPersistenceUnitPostProcessor"/>
    </property>
    <property name="defaultDataSource" ref="dataSource"/>
</bean>

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="loadTimeWeaver">
        <bean
                class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
    </property>
    <property name="jpaPropertyMap">
        <map>
            <entry key="hibernate.dialect" value="${jpa.hibernate.dialect}"/>
            <entry key="hibernate.default_schema" value="${jpa.schema}"/>
            <entry key="hibernate.connection.autocommit" value="true"/>
            <entry key="hibernate.temp.use_jdbc_metadata_defaults" value="false"/>
            <entry key="hibernate.show_sql" value="${jpa.show_sql}"/>
            <entry key="hibernate.format_sql" value="true"/>
            <entry key="hibernate.proc.param_null_passing" value="true"/>
        </map>
    </property>
    <property name="jpaDialect">
        <bean class="com.exp.framework.db.KaraHibernateJpaDialect">
            <property name="name" value="Main"/>
        </bean>
    </property>
    <property name="jpaVendorAdapter">
        <bean id="jpaVendorAdapter"
              class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="ORACLE"/>
            <property name="showSql" value="false"/>
        </bean>
    </property>
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" lazy-init="true" destroy-method="close">
    <constructor-arg index="0" value="KaraPool"/>
    <property name="driverClass" value="${jpa.dataSource.driverClass}"/>
    <property name="description" value="main pool data source"/>
    <property name="jdbcUrl" value="${jpa.dataSource.jdbcUrl}"/>
    <property name="user" value="${jpa.connection.user}">
    </property>
    <property name="password" value="${jpa.connection.password}">
    </property>
    <property name="autoCommitOnClose" value="false"/>
    <property name="forceIgnoreUnresolvedTransactions" value="false"/>
    <property name="initialPoolSize" value="${jpa.dataSource.initPoolSize:1}"/>
    <property name="minPoolSize" value="${jpa.dataSource.minPoolSize:1}"/>
    <property name="maxPoolSize" value="${jpa.dataSource.maxPoolSize:10}"/>
    <property name="maxIdleTime" value="${jpa.dataSource.maxIdleTime:300}"/>
    <property name="debugUnreturnedConnectionStackTraces" value="true"/>
    <property name="propertyCycle" value="${jpa.dataSource.propertyCycle:60}"/>
    <!-- In Second -->
    <property name="acquireRetryDelay" value="${jpa.dataSource.acquireRetryDelay:1000}"/>
    <!-- In Mili Second -->
    <property name="maxConnectionAge" value="${jpa.dataSource.maxConnectionAge:0}"/>
    <!-- In Second -->
    <property name="checkoutTimeout" value="${jpa.dataSource.checkoutTimeout:5000}"/>
    <!-- In Mili Second -->
    <property name="acquireIncrement" value="${jpa.dataSource.acquireIncrement:1}"/>
    <property name="numHelperThreads" value="${jpa.dataSource.numHelperThreads:5}"/>
    <property name="acquireRetryAttempts" value="${jpa.dataSource.acquireRetryAttempts:1}"/>
    <property name="unreturnedConnectionTimeout"
              value="${jpa.dataSource.unreturnedConnectionTimeout:600}"/>
    <!-- In Second -->
    <property name="breakAfterAcquireFailure" value="${jpa.dataSource.breakAfterAcquireFailure:false}"/>
    <property name="testConnectionOnCheckout" value="${jpa.dataSource.testConnectionOnCheckout:false}"/>
    <property name="testConnectionOnCheckin" value="${jpa.dataSource.testConnectionOnCheckin:false}"/>
    <property name="preferredTestQuery" value="select 0 from dual"/>
</bean>
<bean name="mbeanServer"
      class="org.springframework.jmx.support.MBeanServerFactoryBean">
    <property name="locateExistingServerIfPossible" value="true"/>
    <property name="registerWithFactory" value="true"/>
</bean>

<bean id="register" class="com.mchange.v2.c3p0.management.DynamicPooledDataSourceManagerMBean">
    <constructor-arg index="0" ref="dataSource"/>
    <constructor-arg index="1">
        <bean factory-bean="pooledDataSourceName" factory-method="getPooledDataSourceObjectName"/>
    </constructor-arg>
    <constructor-arg index="2" ref="mbeanServer"/>
</bean>

<bean id="pooledDataSourceName" class="com.exp.framework.db.PooledDataSourceBeanNameFactory">
    <constructor-arg index="0" ref="dataSource"/>
</bean>

<!--
    <bean name="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter"
        lazy-init="false" depends-on="mbeanServer">
        <property name="server" ref="mbeanServer"></property>
        <property name="namingStrategy">
            <bean class="org.springframework.jmx.export.naming.KeyNamingStrategy">
            </bean>
        </property>
        <property name="beans">
            <map>
                <entry key="com.exp.pool.connection:type=c3p0,name=Main" value-ref="dataSource_c3">
                </entry>
            </map>
        </property>
        <property name="assembler" >
         <bean class="org.springframework.jmx.export.assembler.MethodExclusionMBeanInfoAssembler">
          <property name="ignoredMethods">
            <value>
                close,
                getConnection,
                getLastAcquisitionFailure,
                getLastCheckinFailure,
                getLastCheckoutFailure,
                getLastIdleTestFailure,
                getLastConnectionTestFailure,
                getConnectionPoolDataSource,
                getNestedDataSource,
                getReference,
                getConnection,
                getPassword,
                getPooledConnection,
                getProperties,
                getLogWriter,
                getLastAcquisitionFailureDefaultUser,
                getLastCheckoutFailureDefaultUser,
                getLastCheckinFailureDefaultUser,
                getLastIdleTestFailureDefaultUser,
                getLastConnectionTestFailureDefaultUser,
                setPassword,
                setProperties,
                setLogWriter
                isWrapperFor
                unwrap,
                equals,
                doResolve,
                removeVetoableChangeListener,
                addVetoableChangeListener,
                removePropertyChangeListener,
                addPropertyChangeListener,
                setIdentityToken,
                setDataSourceName,
                getIdentityToken,
                setConnectionPoolDataSource,
            </value>
          </property>
         </bean>
        </property>
    </bean>
-->

事务管理器初始化代码

SpringBaseEntityManagerFactoryProvider provider = new SpringBaseEntityManagerFactoryProvider(); 
EntityManagerFactory emf = provider.getEntityManagerFactory(); 
JpaTransactionManager txManager = new JpaTransactionManager(emf);
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put("org.hibernate.flushMode", "COMMIT");
txManager.setJpaPropertyMap(jpaProperties); 

事务使用代码

DefaultTransactionDefinition def = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 
def.setName("LogTransaction"); 
org.springframework.transaction.TransactionStatus transactionStatus = txManager.getTransaction(def); 
try { 
    // 业务逻辑
    txManager.commit(transactionStatus);
} catch (Exception e) {
    txManager.rollback(transactionStatus);
}

可能的原因分析

  1. 自定义JpaDialect兼容性问题:Spring 5对JpaDialect接口的方法规范有调整,自定义的KaraHibernateJpaDialect可能未正确实现连接释放逻辑,导致事务提交后连接未归还给池。
  2. Hibernate配置冲突:hibernate.connection.autocommit=true在Spring 5+高版本Hibernate环境下,与事务管理逻辑冲突,导致连接被持续占用。
  3. 事务管理器初始化方式不当:手动创建JpaTransactionManager而非通过Spring容器管理,无法利用Spring的上下文生命周期管理资源,导致连接未正确清理。
  4. 连接池配置问题:c3p0的maxIdleTime、unreturnedConnectionTimeout等参数设置,结合Spring 5的连接持有逻辑,导致连接释放延迟。

解决方案

1. 适配自定义JpaDialect

检查KaraHibernateJpaDialect是否实现了Spring 5中JpaDialect的所有必要方法,重点关注releaseConnection方法,确保事务完成后将连接归还给连接池:

@Override
public void releaseConnection(Connection con, EntityManager em) throws PersistenceException {
    // 确保调用父类实现或直接将连接归还给池
    super.releaseConnection(con, em);
    // 如有自定义逻辑,确认不会阻止连接释放
}

2. 调整Hibernate autocommit配置

事务场景下建议关闭自动提交,修改jpaPropertyMap中的配置:

<entry key="hibernate.connection.autocommit" value="false"/>

事务的提交/回滚由Spring事务管理器统一控制,避免autocommit=true导致的连接持有异常。

3. 通过Spring容器管理事务管理器

替换手动创建JpaTransactionManager的方式,在Spring配置中定义事务管理器,让Spring负责资源生命周期管理:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
    <property name="jpaPropertyMap">
        <map>
            <entry key="org.hibernate.flushMode" value="COMMIT"/>
        </map>
    </property>
</bean>

业务代码中通过Spring注入获取事务管理器,而非手动实例化。

4. 优化c3p0连接池配置

  • 开启连接校验,确保连接状态正常:
<property name="testConnectionOnCheckin" value="true"/>
<property name="testConnectionOnCheckout" value="true"/>
  • 调整unreturnedConnectionTimeout参数,缩短未归还连接的超时时间,结合debugUnreturnedConnectionStackTraces=true输出的堆栈信息,定位未归还连接的代码路径:
<property name="unreturnedConnectionTimeout" value="300"/> <!-- 5分钟 -->

5. 检查EntityManager的生命周期

如果手动获取EntityManager,确保在事务结束后调用close()方法释放资源:

EntityManager em = emf.createEntityManager();
try {
    // 业务操作
} finally {
    em.close();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 20:27:32