数据源连接池未关闭的问题 Could not open JDBC Connection for transaction

Posted 程序员的一亩三分地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据源连接池未关闭的问题 Could not open JDBC Connection for transaction相关的知识,希望对你有一定的参考价值。

背景

公司线上运行的项目最近报了这个错,Could not open JDBC Connection for transaction,无法获取数据源连接池了。

分析

阅读源码,看看各个情况下是否都能自动释放数据源连接吧。

MyBatis释放连接

MyBatis自己单独运行的时候运行SQL语句是不会自动释放数据源连接的,但和Spring整合后就会自动释放数据源连接了。Spring改变了MyBatis的SqlSession,改成Spring整合包中的SqlSessionTemplate,关键代码如下:

public class SqlSessionTemplate implements SqlSessionDisposableBean {
    //...
    //省略一些代码
    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = getSqlSession(
                    SqlSessionTemplate.this.sqlSessionFactory,
                    SqlSessionTemplate.this.executorType,
                    SqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                if (sqlSession != null) {
                    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
        }
    }
}

在最后的finally中,会关闭session,释放数据源连接。

事务@Transactional释放连接

在方法上添加注解@Transactional将该方法标记成事务,也会自动释放连接,关键代码如下:

public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManagerInitializingBean 
{
    //...
    //省略一些代码
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.unbindResource(this.dataSource);
        }
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            if (txObject.isMustRestoreAutoCommit()) {
                con.setAutoCommit(true);
            }
            DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
        }
        catch (Throwable ex) {
            logger.debug("Could not reset JDBC Connection after transaction", ex);
        }
        if (txObject.isNewConnectionHolder()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
            }
            DataSourceUtils.releaseConnection(con, this.dataSource);
        }
        txObject.getConnectionHolder().clear();
    }

这其中,DataSourceUtils.releaseConnection(con, this.dataSource)方法会关闭数据源连接。

找问题

公司项目用的是Druid数据源,最大连接数设的50,按照上面的分析,一般情况下是不可能用完的,肯定是有代码没有释放连接。
找了好半天,最终定位到如下代码:

@Autowired
private SqlSessionFactory sqlSessionFactory;
public void batchInsert(List<TaskCenter> list) {
    if(list == null || list.size() == 0){
        return;
    }
    try {
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        TaskCenterMapper mapper = sqlSession.getMapper(TaskCenterMapper.class);
        for(TaskCenter taskCenter : list){
            mapper.insertSelective(taskCenter);
        }
        sqlSession.flushStatements();
        sqlSession.commit();
        log.info("批量插入成功: " + list.size()+"条数据");
    }catch (Exception ex){
        log.error("批量插入失败: ", ex);
    }
}

这段代码的意思是使用MyBatis的批量插入功能批量插入数据,我们上面分析过,使用MyBatis的SqlSession是不会自动关闭数据源连接的,需要使用Spring包装过的SqlSessionTemplate才会自动关闭数据源连接。所以每次执行这个batchInsert方法,都会占用一个数据源连接而不会释放,最终导致数据源连接池被占满,无法开启新的连接。

解决问题

根据以上的分析,现在有两种方案可以解决该问题
1、将该方法加入事务,在方法上增加注解@Transactional,代码如下:

@Transactional
public void batchInsert(List<TaskCenter> list) {
    if(list == null || list.size() == 0){
        return;
    }
    // 以下省略
    // ...

2、使用完sqlSession后手动关闭sqlSession,代码如下:

public void batchInsert(List<TaskCenter> list) {
    if(list == null || list.size() == 0){
        return;
    }
    SqlSession sqlSession = null;
    try {
        sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        TaskCenterMapper mapper = sqlSession.getMapper(TaskCenterMapper.class);
        for(TaskCenter taskCenter : list){
            mapper.insertSelective(taskCenter);
        }
        sqlSession.flushStatements();
        sqlSession.commit();
        log.info("批量插入成功: " + list.size()+"条数据");
    }catch (Exception ex){
        log.error("批量插入失败: ", ex);
    }finally {
        if (sqlSession != null) {
            SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
        }
    }
}

结语

这篇根据一个生产上的问题通过分析源码了解了MyBatis框架和Spring事务管理自动关闭数据源连接池的功能,了解了原理才好解决问题。


以上是关于数据源连接池未关闭的问题 Could not open JDBC Connection for transaction的主要内容,如果未能解决你的问题,请参考以下文章

php连接数据库提示could not find driver 问题解决

数据库连接池could not inspect JDBC autocommit mode 问题处理

数据库连接池could not inspect JDBC autocommit mode 问题处理

数据库连接池could not inspect JDBC autocommit mode 问题处理

pytthon问题 pytcharm Automatic upload failed: could not resolve file “sftp://10.xx.xx.xx 已解决

黄聪:PHP数据库连接失败--could not find driver 解决办法