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); }
可能的原因分析
- 自定义JpaDialect兼容性问题:Spring 5对
JpaDialect接口的方法规范有调整,自定义的KaraHibernateJpaDialect可能未正确实现连接释放逻辑,导致事务提交后连接未归还给池。 - Hibernate配置冲突:
hibernate.connection.autocommit=true在Spring 5+高版本Hibernate环境下,与事务管理逻辑冲突,导致连接被持续占用。 - 事务管理器初始化方式不当:手动创建
JpaTransactionManager而非通过Spring容器管理,无法利用Spring的上下文生命周期管理资源,导致连接未正确清理。 - 连接池配置问题: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
相关产品推荐
相关产品推荐

