【Mybatis源码分析 14】mybatis连接池源码分析

什么是连接池

存储连接的容器

为什么要使用连接池

由于每次执行sql语句都去创建connection和使用完销毁耗时,使用连接池来管理连接,提高连接的使用率(跟java线程池的概念类似)

源码分析

mybatis提供三种数据库连接池的选择

配置文件的enviroment节点的datasource子标签

1. type="POOLED",对应org.apache.ibatis.datasource.pooled.PoolDataSource连接池;

2. type="UNPOOLED, 对应org.apache.ibatis.datasource.unpooled.UnpooledDataSource,表示不使用连接池,每次执行sql语句都新建连接,使用完后销毁连接;

3. type="JDNI", 对应org.apache.ibatis.datasource.jndi.JndiDataSourceFactory连接池,表示使用外置连接池。

一般常用设置type="POOLED"使用内置PoolDataSource连接池

加载mybatis配置文件获取输入流,通过SqlSessionFactioryBuilder创建SqlSessionFactory的过程中对配置文件解析每个节点,包括environment,封装成Configuration对象,从SqlSessionFactory中获取SqlSession对象时将Configuration对象传递给了SqlSession对象。

断点调试可以看到,SqlSession对象Configuration属性中的environment属性中的dataSource对象是一个PooledDataSource实例,也就是说,连接池在加载配置文件创建SqlSessionFactory的时候就已经初始化,等到SqlSession对象或mapper代理对象执行sql的时候,再从连接池中去拿空闲连接,使用完后再归还连接。

org.apache.ibatis.datasource.pooled.PoolDataSource源码分析

PooledDataSource中定义了一个PollState类型的state对象,维护了两个List<PooledConnection>对象:idleConnections(保存可用的空间连接)、activeConnections(保存活动连接)

获取连接

方法:private PooledConnection popConnection(String username, String password) throws SQLException;

    private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countedWait = false;
        PooledConnection conn = null;
        long t = System.currentTimeMillis();
        int localBadConnectionCount = 0;

        while(conn == null) {
            // 读写PoolState对象中的idleConnections空闲连接,为保证线程安全,加同步锁
            synchronized(this.state) {
                PoolState var10000;
               
                if (!this.state.idleConnections.isEmpty()) {
                    // 如果idleConnections空闲连接数连接不为空,则将第一个空闲连接拿出来
                    conn = (PooledConnection)this.state.idleConnections.remove(0);
                    // 省略代码

                } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                    // idleConnections为空,没有可用的空闲连接,PoolState对象中的activeConnections活动连接数小于最大活动连接数(默认10)
                    // 创建新的连接(同时将新连接注册到activeConnections)
                    conn = new PooledConnection(this.dataSource.getConnection(), this);
                    // 省略代码

                } else {
                    // idleConnections为空,没有可用的空闲连接,并且PoolState对象中的activeConnections活动连接数大于或等于最大活动连接数
                    // 从activeConnections中拿到第一个连接(最早使用的连接)
                    PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                    
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                        // 省略代码

                        // 如果连接超时,删除该连接
                        this.state.activeConnections.remove(oldestActiveConnection);
                        // 省略代码

                        // 然后就可以创建新的连接来执行任务
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        // 省略代码

                    } else {
                        // 等待
                    }
                }

                if (conn != null) {
                    if (conn.isValid()) {
                        // 如果连接不为空并且有效
                        // 省略代码

                        //每一个连接获取唯一的类型hash值,由url\username\password计算得到
                        conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                        // 设置最后一次检查超时和最后一次使用时间戳(刚获取的连接都设置当前系统时间)
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        // 将该连接注册到activeConnections活动连接列表中
                        this.state.activeConnections.add(conn);
                        // 省略代码
                    } else {
                        //连接不为空但是无效
                        // 省略代码

                        // 坏连接数+1,并将该连接置空
                        ++this.state.badConnectionCount;
                        ++localBadConnectionCount;
                        conn = null;
                        // 如果坏连接数超过最大空闲连接数+最大可容忍坏连接数,则抛出异常
                        if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
                            if (log.isDebugEnabled()) {
                                log.debug("PooledDataSource: Could not get a good connection to the database.");
                            }

                            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }
        }

        //最后检查一次,如果连接为空抛出异常
        if (conn == null) {
            if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
            }

            throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        } else {
            return conn;
        }
    }

归还连接

方法:protected void pushConnection(PooledConnection conn) throws SQLException;

    protected void pushConnection(PooledConnection conn) throws SQLException {
        // 加锁
        synchronized(this.state) {
            // 将已使用完的连接从activeConnections中移除
            this.state.activeConnections.remove(conn);
            if (conn.isValid()) {
                PoolState var10000;
                
                if (this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {
                    // 如果idleConnections空闲连接数小于最大空闲连接数(默认5个),并且连接属性相同(conn.getConnectionTypeCode(),得到每个pooledConnection的属性,使用url+username+password,进行hashcode算法,获得的int类型数据)
                    // 省略代码

                    // 使用该连接初始化一个新的连接
                    PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                    // 将该连接注册到idleConnections空闲连接中
                    this.state.idleConnections.add(newConn);
                    // 设置连接创建和最后使用的时间戳
                    newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                    newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                    // 使原来的连接无效
                    conn.invalidate();
                    if (log.isDebugEnabled()) {
                        log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                    }

                    this.state.notifyAll();
                } else {
                    // 省略代码

                    // 关闭该连接的原连接,并使该连接无效
                    conn.getRealConnection().close();
                    
                    conn.invalidate();
                }
            } // 省略代码

        }
    }

总结

popConnection

获取连接时,会从连接池的空闲连接列表中进行获取空闲连接,

如果连接池中没有可用的空闲连接, 判断活动连接列表中连接数是否达到上限,

如果未达到, 则创建新的连接

如果已经达到上限,判断活动连接列表中最先使用的连接是否过期,

如果过期,则活动连接列表删除该连接,然后创建新的连接

如果没有过期,则需要等待设置的等待时间,然后再进行循环获取.

获取连接后,对连接进行校验,会调用pingConnection()方法,如果连接不可用则为坏连接,并将连接置为null,继续进行获取连接,当坏连接的数量>最大空闲连接数量+3时,抛出异常,

最后对连接进行!null判断,如果获得的连接为空,则抛出异常.

pushConnection

将连接从活动连接列表中移除,

如果空闲连接列表中连接数未达到上限,并且连接属性和预期一致,则使用该连接初始化一个新连接,将新连接注册到空闲连接列表,并使原连接无效,

否则直接关闭原连接

 

PoolConnection

org.apache.ibatis.datasource.pooled.PooledConnection实现了InvocationHandler接口,用户拿到的是PoolConnection的代理对象,实现invoke方法中,如果执行close()方法,并不是将连接关闭,而是调用pushConnection方法将连接归还给连接池

 

class PooledConnection implements InvocationHandler {

    // ...

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("close".equals(methodName)) {
            // 当执行sqlSession.close();关闭各种资源,其中就会关闭数据库连接,如果是POOLED方式使用连接池,关闭连接的方法在这里被拦截执行归还连接的方法
            this.dataSource.pushConnection(this);
            return null;
        } else {
            try {
                if (!Object.class.equals(method.getDeclaringClass())) {
                    this.checkConnection();
                }

                return method.invoke(this.realConnection, args);
            } catch (Throwable var6) {
                throw ExceptionUtil.unwrapThrowable(var6);
            }
        }
    }

}

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值