浅谈Mybatis内置数据源

Mybatis内置数据源

今天的文章稍微没有那么肝,但是还是拖到了晚上!!哈哈哈,其实昨天已经写好了,只不过一直想不到个人唠叨应该写什么,所以就拖了一下。

内置数据源初始化过程

在详细分析 UnpooledDataSource 和 PooledDataSource 两种数据源实现之前,我们先来了解一下数据源的配置与初始化过程。现在看数据源是如何配置的,如下:

<!--使用连接池-->
<dataSource type="POOLED">
    <!--这里会替换为local-mysql.properties中的对应字段的值-->
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

以UnpooledDataSource为例。UnpooledDataSource,从名称上即可知道,该种数据源不具有池化特性。该种数据源每次会返回一个新的数据库连接,而非复用旧的连接。由于 UnpooledDataSource 无需提供连接池功能,因此它的实现非常简单。核心的方法有三个,分别如下:

  1. initializeDriver - 初始化数据库驱动
  2. doGetConnection - 获取数据连接
  3. configureConnection - 配置数据库连接
初始化数据库驱动

​ 回顾我们一开始学习使用 JDBC 访问数据库时的情景,在执行 SQL 之前,通常都是先获取数据库连接。一般步骤都是加载数据库驱动,然后通过 DriverManager 获取数据库连接。UnpooledDataSource 也是使用 JDBC 访问数据库的,因此它获取数据库连接的过程也大致如此,只不过会稍有不同。先来看看这个调用栈吧,image-20200717121717435

// -☆- UnpooledDataSource
private synchronized void initializeDriver() throws SQLException {
    // 检测缓存中是否包含了与 driver 对应的驱动实例
    if (!registeredDrivers.containsKey(driver)) {
        Class<?> driverType;
        try {
            // 加载驱动类型
            if (driverClassLoader != null) {
                // 使用 driverClassLoader 加载驱动
                driverType = Class.forName(driver, true, driverClassLoader);
            } else {
                // 通过其他 ClassLoader 加载驱动
                driverType = Resources.classForName(driver);
            }

            // 通过反射创建驱动实例
            Driver driverInstance = (Driver) driverType.newInstance();
            /*
             * 注册驱动,注意这里是将 Driver 代理类 DriverProxy 对象注册到 DriverManager 中的,
             * 而非 Driver 对象本身。DriverProxy 中并没什么特别的逻辑,就不分析。
             */
            DriverManager.registerDriver(new DriverProxy(driverInstance));
            // 缓存驱动类名和实例
            registeredDrivers.put(driver, driverInstance);
        } catch (Exception e) {
            throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
        }
    }
}

// 之所以上面有缓存相关的逻辑,是用于便面重复注册驱动

获取数据库连接

这跟JDBC有点类似

// -☆- UnpooledDataSource
public Connection getConnection() throws SQLException {
	return doGetConnection(username, password);
}
    
private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
        props.putAll(driverProperties);
    }
    if (username != null) {
        // 存储 user 配置
        props.setProperty("user", username);
    }
    if (password != null) {
        // 存储 password 配置
        props.setProperty("password", password);
    }
    // 调用重载方法
    return doGetConnection(props);
}

private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化驱动
    initializeDriver();
    // 获取连接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置连接,包括自动提交以及事务等级
    configureConnection(connection);
    return connection;
}

private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
        // 设置自动提交
        conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
        // 设置事务隔离级别
        conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
}

上面方法将一些配置信息放入到 Properties 对象中,然后将数据库连接和 Properties 对象传给 DriverManager 的 getConnection 方法即可获取到数据库连接

PooledDataSource

内部实现了连接池技术

获取连接

先看看调用栈,入口方法和之前的UnPooled一样image-20200717143902996

public Connection getConnection() throws SQLException {
    // 返回 Connection 的代理对象
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}

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 (state) {
            // 检测空闲连接集合(idleConnections)是否为空
            if (!state.idleConnections.isEmpty()) {
                // idleConnections 不为空,表示有空闲连接可以使用
                conn = state.idleConnections.remove(0);
            } else {
                /*
                 * 暂无空闲连接可用,但如果活跃连接数还未超出限制
                 *(poolMaximumActiveConnections),则可创建新的连接
                 */
                if (state.activeConnections.size() < poolMaximumActiveConnections) {
                    // 创建新连接
                    conn = new PooledConnection(dataSource.getConnection(), this);
                    
                } else {    // 连接池已满,不能创建新连接
                    // 取出运行时间最长的连接
                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                    // 获取运行时长
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    // 检测运行时长是否超出限制,即超时
                    if (longestCheckoutTime > poolMaximumCheckoutTime) {
                        // 累加超时相关的统计字段
                        state.claimedOverdueConnectionCount++;
                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        state.accumulatedCheckoutTime += longestCheckoutTime;

                        // 从活跃连接集合中移除超时连接
                        state.activeConnections.remove(oldestActiveConnection);
                        // 若连接未设置自动提交,此处进行回滚操作
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            try {
                                oldestActiveConnection.getRealConnection().rollback();
                            } catch (SQLException e) {...}
                        }
                        /*
                         * 创建一个新的 PooledConnection,注意,
                         * 此处复用 oldestActiveConnection 的 realConnection 变量
                         */
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        /*
                         * 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的 
                         * createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection 
                         * 的创建时间。所以这里要复用原连接的时间信息。
                         */
                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

                        // 设置连接为无效状态
                        oldestActiveConnection.invalidate();
                        
                    } else {    // 运行时间最长的连接并未超时
                        try {
                            if (!countedWait) {
                                state.hadToWaitCount++;
                                countedWait = true;
                            }
                            long wt = System.currentTimeMillis();
                            // 当前线程进入等待状态
                            state.wait(poolTimeToWait);
                            state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                }
            }
            if (conn != null) {
                /*
                 * 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,
                 * 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,
                 * 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。
                 * 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的
                 * 属性,有三个:poolPingQuery,poolPingEnabled 和 
                 * poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档
                 */
                if (conn.isValid()) {
                    if (!conn.getRealConnection().getAutoCommit()) {
                        // 进行回滚操作
                        conn.getRealConnection().rollback();
                    }
                    conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                    // 设置统计字段
                    conn.setCheckoutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    state.activeConnections.add(conn);
                    state.requestCount++;
                    state.accumulatedRequestTime += System.currentTimeMillis() - t;
                } else {
                    // 连接无效,此时累加无效连接相关的统计字段
                    state.badConnectionCount++;
                    localBadConnectionCount++;
                    conn = null;
                    if (localBadConnectionCount > (poolMaximumIdleConnections
                        + poolMaximumLocalBadConnectionTolerance)) {
                        throw new SQLException(...);
                    }
                }
            }
        }

    }

    if (conn == null) {
        throw new SQLException(...);
    }

    return conn;
}

这里的代码有点复杂

稍微总结了一下:Mybatis-Pooled-获取连接

回收连接

相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。

protected void pushConnection(PooledConnection conn) throws SQLException {
    synchronized (state) {
        // 从活跃连接池中移除连接
        state.activeConnections.remove(conn);
        if (conn.isValid()) {
            // 空闲连接集合未满
            //  复用原连接的字段信息创建新的连接,并将其放入空闲集合中
            if (state.idleConnections.size() < poolMaximumIdleConnections
                && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
                
                state.accumulatedCheckoutTime += conn.getCheckoutTime();

                // 回滚未提交的事务
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }

                // 创建新的 PooledConnection
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                state.idleConnections.add(newConn);
                // 复用时间信息
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());

                // 将原连接置为无效状态
                conn.invalidate();

                // 通知等待的线程
                state.notifyAll();
                
            } else {    // 空闲连接集合已满
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                // 回滚未提交的事务
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }

                // 关闭数据库连接
                conn.getRealConnection().close();
                conn.invalidate();
            }
        } else {
            state.badConnectionCount++;
        }
    }
}

回收连接的入口:

// -☆- PooledConnection
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 检测 close 方法是否被调用,若被调用则拦截之
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        // 将回收连接中,而不是直接将连接关闭
        // 回收连接的入口方法
        dataSource.pushConnection(this);
        return null;
    } else {
        try {
            if (!Object.class.equals(method.getDeclaringClass())) {
                checkConnection();
            }

            // 调用真实连接的目标方法
            return method.invoke(realConnection, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

本篇文章大概就讲这么多,重点还是在对Pool和UnPool的理解,因为际场景下,我们基本不用 MyBatis 自带的数据库连接池的实现。所以,上面的流程了解即可!!!

个人唠叨

日常总结

今天下午已经把Mybatis系列全部写完!!!哈哈哈,后面会陆续发的。感觉现在的自己阅读源码越来越牛逼了(日常吹牛逼),本来计划是再阅读一遍Spring源码的,因为其实每一次阅读源码,你都会有不同的收获!!但是基于计划的原因,如果再阅读一遍的话,恐怕又要花一周的时间了。所以,今晚就特意提前进入算法的刷题了,不为啥,因为之前一直害怕算法,所以刷了一次之后,就停了一段时间了,然后又忘了,然后6月份又刷了一下链表,因为项目和期末考的原因就先放下了,现在又把算法捡起来了,这次是抱着刷刷刷的决心来的,这次一定要坚持刷,一定要坚持,坚持,坚持!!!

说说今晚刷算法的感受

今晚应该不算刷吧,算复习一下6月份刷的链表题目,8点开始复习,重新刷了一遍,才刷了8道题了,用了两个小时,而且都是在6月份做过的,仅仅用了一个月,就把做过的题目忘光光了,那平时的那些知识点更不用说了唉!所以,今晚是一个好的开始,之后一直到找到实习offer之前都要一直刷下去,先计划一下这个暑假刷算法的时间吧!每天晚上8点开始(最迟)开始刷,刷到10点左右吧,每天差不多这个时间段,题量具体而定,然后每周划出周日的晚上不刷新题目,只做复习,如果当天晚上复习不完,就留给明天晚上!!!在此立下Flag,之后的个人唠叨中,我都会稍微提一下这个Flag的情况,哈哈哈,希望大家和我一样能尽早克服对算法的恐惧!!(大佬勿理)

谈谈我大学遇到的那个她(这里先说结论,我们最后是没有在一起的)上篇(哈哈哈,留个悬念下篇再继续)

哈哈哈哈,怎么突然今晚会说这个呢?嗯,就是爸妈今晚吃饭的时候,我在跟我爸妈提到了,我的同学都准备要结婚了,哈哈哈哈哈,才20岁耶,就结婚了,卧槽,我。。。(语塞)!!我其实也一点都不羡慕啦,我跟我爸妈说,我喜欢在这段时间好好奋斗,因为这是我精力最旺盛的时候啊,真的不能留下遗憾哎,拍拖会不会有点浪费时间呢?这时候,我爸妈就说,你大学能找到合适的,当然最好,他怕我出来工作之后找不到女朋友,好像我三叔一样,虽然工资很高,也挺有钱的,但是现在30多岁还没找到老婆,所以我爸妈一直催我,在大学就赶紧找个女朋友啦!!我:我都想找啊,但我无人问津,失礼啊!!哈哈哈哈,开玩笑的

接下来就进入主题了,大学的上半阶段结束了,我在大二上确实遇到过这么一个女孩,她超可爱的,她是我部门的一个师姐!那我是什么时候开始喜欢她的呢?是在三下乡的时候,短短8天时间,回忆真的好多,先挑几张经典的图出来压压惊image-20200718225235025

哈哈哈 这个是跟大头的工作照,跟着大头哥学到了很多很多,虽然现在也不怎么拿起相机继续拍照了,但是偶尔也会拿起手机拍几张,然后修一下,留在相册里面吃灰image-20200718225416420

还有上面那位小姐姐,看看时间,哈哈哈哈,凌晨一点多的我还在工作,那时的我是兼任排版+拍照+剪视频,卧槽,现在想想都恐怖,天天排版排到凌晨2点多3点,然后早上7点多又起床了,真的太恐怖了,现在都没试过这种工作模式了!image-20200718225713999

正经合照就不发了,因为好像找不到,就随便找了一张那时候去吃东西拍的合照,嗯,本文的主角就是我(戴眼镜,男,在右边)前面那位带着白色帽子的女生!

今天就先说到这,哈哈哈,因为也快11点了,有点困了,要早点休息了!!!

欲知详情,请继续关注下篇文章(狗头。。)

晚安!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值