Mybatis源码解析之——数据源池化技术(二)

一.介绍

   随着上一篇文章的内容,讲解了Mybatis中数据源池化技术中的无池化技术,在这一章我们开始讲解有池化技术。

二.分析

1.部分

Mybatis源码中实现有池化技术的板块中主要包含了四个部分:

1.有池化代理的链接PooledConnection

2.池状态PoolState

3.有池化数据源实现PooledDataSource

4.池化工厂PooledDataSourceFactory

2.有池化代理的链接PooledConnection

就是你可以看作一个个连接池里的连接对象个体,每个连接里面都需要包含自己的代理连接、合法性、hashCode码、创建时间等

读之前建议你要思考如果你自己设计一个连接对象,都需要包含哪些属性点,读完源码之后,你自己再比对和你设计有什么不同,带着你自己的疑问去学习应该会更容易理解

class PooledConnection implements InvocationHandler {
    private static final String CLOSE = "close";
  
    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    //hashCode码
    private int hashCode = 0;
    //池化数据源
    private PooledDataSource dataSource;
    //真实的连接对象
    private Connection realconnection;
    //代理的连接对象
    private Connection proxyConnection;
    //连接取出当前时间
    private long checkoutTimestamp;
    //创建时间
    private long createTimestamp;
    //最近使用时间
    private long lastUsedTimestamp;
    //连接的标识码
    private int connectionTypeCode;
    //连接是否合法
    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
//这个hashCode码就得到Connection对象的hashCode码
        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
//通过Proxy类的newProxyInstance方法去创建链接代理
        this.proxyConnection = (Connection)Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }

//设置连接失效
    public void invalidate() {
        this.valid = false;
    }

//判断连接是否合法,就是是否有效
    public boolean isValid() {
        return this.valid && this.realConnection != null && this.dataSource.pingConnection(this);
    }


   。。。还是省略一些set、get方法 自行阅读源码就好,很简单

//重写equals,判断是不是同一个连接
    public boolean equals(Object obj) {
        if (obj instanceof PooledConnection) {
            return this.realConnection.hashCode() == ((PooledConnection)obj).realConnection.hashCode();
        } else if (obj instanceof Connection) {
            return this.hashCode == obj.hashCode();
        } else {
            return false;
        }
    }


//这个就不用多说,主要实现InvocationHandler接口,实现invoked代理方法。从而实现连接本身所包含的方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
//如果调用CLOSE方法,就代表要关闭连接,并把连接放入到连接池中,返回null值
        if ("close".equals(methodName)) {
            this.dataSource.pushConnection(this);
            return null;
        } else {
            try {
                if (!Object.class.equals(method.getDeclaringClass())) {
               //除了toString方法以外,任何一个连接对象包含方法在调用之前,都要检查连接对象是否合法,不合法抛出异常
                    this.checkConnection();
                }
//最后通过真实的连接对象去调用方法,也就是传入前面构造器中的连接对象
                return method.invoke(this.realConnection, args);
            } catch (Throwable var6) {
                throw ExceptionUtil.unwrapThrowable(var6);
            }
        }
    }

//判断连接是否合法
    private void checkConnection() throws SQLException {
        if (!this.valid) {
            throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
        }
    }
}

3.池状态PoolState

讲完了连接个体,就要去讲讲他们的容器-连接池,连接池也是有很多状态的,例如池中空闲队列的情况、活跃链接的情况,还涉及到连接池一些参数,比如每个连接请求的平均请求时间等

public class PoolState {

    //关联的数据源
    private PooledDataSource dataSource;
    //空闲链接
    protected final List<PooledConnection> idleConnections = new ArrayList<>();
    //活跃链接
    protected final List<PooledConnection> activeConnections = new ArrayList<>();
    //请求次数
    protected long requestCount = 0L;
    //总请求时间
    protected long accumulatedRequestTime = 0L;
    //累计连接被使用的时间,从pop到push的时间。
    protected long accumulatedCheckoutTime = 0L;
    //过期的连接总数目
    protected long claimedOverdueConnectionCount = 0L;
    //累计过期连接的使用总时长
    protected long accumulatedCheckoutTimeOfOverdueConnections = 0L;
    //总等待时间
    protected long accumulatedWaitTime = 0L;
    //要等待的次数
    protected long hadToWaitCount = 0L;
    //失败连接次数
    protected long badConnectionCount = 0L;

    public PoolState(PooledDataSource dataSource) {
        this.dataSource = dataSource;
    }

//获取请求连接的总数量
    public synchronized long getRequestCount(){
        return requestCount;
    }

  //连接的平均请求时间
    public synchronized long getAverageRequestTime(){
        return requestCount == 0L ? 0L : accumulatedRequestTime /requestCount;
    }
  
  //链接的平均等待时间
    public synchronized long getAverageWaitTime(){
        return hadToWaitCount == 0L ? 0L : accumulatedWaitTime / hadToWaitCount;
    }

//获取等待连接的总数量
    public synchronized long getHadToWaitCount() {
        return hadToWaitCount;
    }

//获得失败链接总数量
    public synchronized long getBadConnectionCount() {
        return badConnectionCount;
    }

//获得过期连接所使用的总时间
    public synchronized long getClaimedOverdueConnectionCount() {
        return claimedOverdueConnectionCount;
    }

  //过期链接的平均使用时间
    public synchronized long getAverageOverdueCheckoutTime() {
        return claimedOverdueConnectionCount == 0L ? 0L : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
    }

  //单个链接平均被使用时间
    public synchronized long getAverageCheckoutTime() {
        return requestCount == 0L ? 0L : accumulatedCheckoutTime / requestCount;
    }

  //获取空闲连接数
    public synchronized int getIdleConnectionCount() {
        return idleConnections.size();
    }

  //获取活跃连接数
    public synchronized int getActiveConnectionCount() {
        return activeConnections.size();
    }
}

4.有池化数据源实现PooledDataSource

前面全是铺垫,这个地方才是核心!!

!!个人建议,读这个地方的时候,稍作休息一会,喝喝水溜达一下,建议看看我写的流程逻辑,带着逻辑去研究源码,效果更好更快。

流程逻辑

1)创建连接

1.创建连接时,如果空闲队列中有连接,直接去第一个连接使用即可;如果空闲队列中没有连接,判断活跃队列中数量情况

2.池中有一个最大活跃连接数的变量,当活跃队列中数量小于最大活跃连接数时,证明活跃队列连接数不足,之间创建新的连接即可。

3.当活跃队列中数量等于最大活跃连接数时,证明活跃队列连接数已满,取出活跃队列中最老的连接,判断是否过期,过期删除,没有过期,等待过期后删除,然后重新创建新的连jie即可

2)关闭连接

1.关闭连接时,池中有一个最小空闲连接数的变量,当删除活跃连接后导致空闲队列中的连接数太少的时候,通过这个活跃连接创建一个新的连接加入空闲队列即可。

2.当删除活跃连接后没有导致空闲队列中的连接数太少的时候,直接关闭连接即可。

看完流程,有个大概认识,就可以学习源码具体怎么实现的啦!!

(注:这个部分代码量太多,为了读者能看下去,我会拆开,一部分一部分给你们讲解) 

代码实现

1.成员属性

必须要有池状态,通过池状态进行池的状态判断,还需要有对链接数的限定:比如活跃连接数、空闲连接数等,还有一些不可少的辅助属性

public class PooledDataSource implements DataSource {

//打印日志用的
    private static final Log log = LogFactory.getLog(PooledDataSource.class);
//池状态
    private final PoolState state = new PoolState(this);

    //无池化数据源
    private final UnpoopledDataSource dataSource;

    // 活跃连接数
    protected int poolMaximumActiveConnections = 10;
    // 空闲连接数
    protected int poolMaximumIdleConnections = 5;
    // 在被强制返回之前,池中连接被检查的时间
    protected int poolMaximumCheckoutTime = 20000;
    // 这是给连接池一个打印日志状态机会的低层次设置,还有重新尝试获得连接, 这些情况下往往需要很长时间 为了避免连接池没有配置时静默失败)。
    protected int poolTimeToWait = 20000;
//就是允许最大的失败连接数
   protected int poolMaximumLocalBadConnectionTolerance = 3;
    // 发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 接受请求。默认是“NO PING QUERY SET” ,这会引起许多数据库驱动连接由一 个错误信息而导致失败
    protected String poolPingQuery = "NO PING QUERY SET";
    // 开启或禁用侦测查询,就是看看能不能ping成功,你可以暂时这理解
    protected boolean poolPingEnabled;
    // 用来配置 poolPingQuery 多次时间被用一次
    protected int poolPingConnectionsNotUsedFor;
    //预连接类型码
    private int expectedConnectionTypeCode;

set、get方法还有一些构造器和配置方法,我暂时不赘述啦,主要讲一些和核心方法。

2.核心方法

这里面有很多方法,我只讲一下主要的核心方法。

获取链接(popConnection)

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) {
             //加锁
            synchronized(this.state) {
                PoolState var10000;

               //如果有空闲链接,返回第一个
                if (!this.state.idleConnections.isEmpty()) {
//从空闲队列中删除第一个并取出
                    conn = (PooledConnection)this.state.idleConnections.remove(0);
                    if (log.isDebugEnabled()) {
                        log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } 
                           //如果无空闲链接,创建新的链接
                    else 
                           //活跃链接数不足
                    if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                    conn = new PooledConnection(this.dataSource.getConnection(), this);
                    if (log.isDebugEnabled()) {
                        log.debug("Created connection " + conn.getRealHashCode() + ".");
                    }
                } 
                        //活跃连接数已满
                    else {
                       //取出活跃列表中最老的一个链接
                    PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                     //获取这个最老的链接存在的时间
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
    
                       //判断是否超时,如果超时就标记过期
                    if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                        //过期连接数+1
                        ++this.state.claimedOverdueConnectionCount;
                        var10000 = this.state;
                           //更新过期连接的使用时间
                        var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        var10000 = this.state;
                        //更新总请求时间
                        var10000.accumulatedCheckoutTime += longestCheckoutTime;
                        //从活跃连接数中删除这个连接
                        this.state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            try {
                                oldestActiveConnection.getRealConnection().rollback();
                            } catch (SQLException var15) {
                                log.debug("Bad connection. Could not roll back");
                            }
                        }

                            //超时,删除最老的链接,然后重新建立一个链接
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled()) {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    } 
                         //如果没有过期,等待超时
                       else {
                        try {
                            if (!countedWait) {
                                ++this.state.hadToWaitCount;
                                countedWait = true;
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
                            }

                            long wt = System.currentTimeMillis();

                               //等待超时
                            this.state.wait((long)this.poolTimeToWait);
                            var10000 = this.state;
                            var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        } catch (InterruptedException var16) {
                            break;
                        }
                    }
                }

                if (conn != null) {
                        //链接不为空
                    if (conn.isValid()) {
                            //连接合法
                        if (!conn.getRealConnection().getAutoCommit()) {
                            //判断链接是否自动提交,如果不是就要触发回滚
                            conn.getRealConnection().rollback();
                        }
                            //设置连接的标识码
                        conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                            //记录checkOut时间
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        //记录连接使用最近的时间
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        //添加到活跃连接队列中
                        this.state.activeConnections.add(conn);
                        //池内请求连接计数加一
                        ++this.state.requestCount;
                        //池内累计请求链接时间
                        var10000 = this.state;
                        var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                        }

                        //如果不合法,统计信息,失败连接+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;
        }
    }

三.总结

恭喜您🎉,到这里你已经学习了一大部分有池化数据源的核心原理,在这里你可以再休息一会,

方便您能坚持下去,我把剩下的几个核心方法还有数据源工厂放到下一章。您完全有时间好好消化一下,再读下一章

如果您对本章内容理解到位,不妨可以先试试自己学习Mybatis源码中的回收链接(pushConnnection)、强制关闭所有链接(forceCloseAll)等方法来,我会在下一篇文章继续讲解

———————————————————————————————————————————

-如果您对我文章满意,不妨点个赞关注我,会持续更新高质量作品。

-如果您对我文章有疑问,欢迎您评论指导,我会虚心接受改正

--说明:本篇文章属于个人原创,如需文章部分内容,请在您的文章开头加入本篇文章的链接!

相关推荐

  1. Mybatis解析——数据技术

    2024-02-08 21:52:01       31 阅读
  2. 自定义ORM(mybatis)()-解析mapper.xml

    2024-02-08 21:52:01       37 阅读
  3. 解析mybatis调用链获取sqlSession

    2024-02-08 21:52:01       35 阅读
  4. MyBatis-PageHelper 解说

    2024-02-08 21:52:01       9 阅读
  5. MyBatis-Plus 解说

    2024-02-08 21:52:01       4 阅读
  6. MyBatis3深度解析(十七)MyBatis整合Spring框架

    2024-02-08 21:52:01       23 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-08 21:52:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-08 21:52:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-08 21:52:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-08 21:52:01       20 阅读

热门阅读

  1. 系统架构评估

    2024-02-08 21:52:01       33 阅读
  2. c++ 子进程交互 逻辑

    2024-02-08 21:52:01       35 阅读
  3. dockerfile 详细讲解

    2024-02-08 21:52:01       26 阅读
  4. redis加锁实现方式

    2024-02-08 21:52:01       36 阅读
  5. OS X(MACOS) 上面打开 utun 驱动,并且读写/C++

    2024-02-08 21:52:01       27 阅读
  6. Compose | UI组件(十五) | Scaffold - 脚手架

    2024-02-08 21:52:01       41 阅读
  7. python软件说明

    2024-02-08 21:52:01       23 阅读
  8. 2、卷积和ReLU激活函数

    2024-02-08 21:52:01       36 阅读
  9. FreeRtos任务的挂起和恢复实验示例(后续)

    2024-02-08 21:52:01       36 阅读