10-Mybatis源码和设计模式-1(数据源模块和工厂模式,代理模式)

文章目录

  • Mybatis数据源模块和工厂模式、代理模式
    • 一、简介
    • 二、工厂模式引入
      • 2.1 来源
      • 2.2 优点
      • 2.3 缺点
      • 2.4 借鉴
      • 2.5 分类
        • 2.5.1 简单工厂模式
        • 2.5.1 工厂方法模式
        • 2.5.1 抽象工厂模式
    • 三、目录结构
    • 四、源码解析
      • 4.1 pooled
        • 4.1.1 PooledDataSource
          • 4.1.1.1 获取连接
          • 4.1.1.2 归还连接
          • 4.1.1.3 获取数据源状态
          • 4.1.1.4 pingConnection
          • 4.1.1.5 forceCloseAll
        • 4.1.2 PooledConnection
          • 4.1.2.1 invoke
          • 4.1.2.2 构造方法
          • 4.1.2.3 其他方法
        • 4.1.3 PoolState
        • 4.1.4 PooledDataSourceFactory
        • 4.1.5 PooledDataSource的连接获取/归还流程图
          • 4.1.5.1 获取流程图
          • 4.1.5.2 归还流程图
      • 4.2 unpooled
        • 4.2.1 UnpooledDataSource
          • 4.2.1.1 获取连接
          • 4.2.1.2 归还连接
          • 4.2.1.3 initializeDriver
        • 4.2.2 UnpooledDataSourceFactory
      • 4.3 JNDI
    • 五、小结
    • 六、参考

Mybatis数据源模块和工厂模式、代理模式

一、简介

  • Mybatis数据源组件和常见的数据源组件一样都实现了javax.sql.DataSource接口,除了自身包含的数据源组件,Mybatis也可以集成专业的第三方数据源组件,通常使用第三方的数据源组件,因为自身的实现比较简单。
  • Mybatis数据源组件包括3种:pooled代表使用连接池的数据源,unpooled代表不使用连接池的数据源,另外还有JNDI类型。
  • 数据源通常包含比较复杂的初始化参数,因此使用了工厂模式。
  • pooled使用了连接池,对原始连接进行了增强,因此使用了代理模式。

二、工厂模式引入

2.1 来源

  • 创建对象的三种方式:new关键字/反射/工厂模式
  • 前两种方式将创建对象和使用对象耦合在一起,扩展业务可能需要修改业务代码,不符合单一职责和开闭原则

2.2 优点

  • 创建对象和对象的使用分离,负责单一职责原则
  • 将创建对象的过程抽离出来,便于维护,提高代码复用性,
  • 业务扩展时,只需要增加工厂子类,不需要修改原有代码,符合开闭原则

2.3 缺点

  • 对于简单的对象,没有必要使用工厂模式

2.4 借鉴

  • Spring的IOC容器可以理解为一个工厂模式,将对象的创建和维护交由容器管理,使用的时候只需要注入即可,不关心对象的创建。

2.5 分类

2.5.1 简单工厂模式

  • 在类中使用类似于if判断或者switch的语句来根据不同的条件创建不同的对象,适用于比较简单的场景,但是不便于扩展,不同类型产品的创建耦合在一起。

2.5.1 工厂方法模式

  • 工厂和商品都是抽象类,若干个子类工厂,每个子类负责一个具体类型产品的创建,当有新的商品需求时,只需要增加工厂和商品的实现类即可

2.5.1 抽象工厂模式

  • 可参考设计模式文章02-创建型模式(下)

三、目录结构

  • 包:org.apache.ibatis.datasource包

10-Mybatis源码和设计模式-1(数据源模块和工厂模式,代理模式)_第1张图片

  • 主要分为pooled和unpooled和jndi三种类型的数据源

四、源码解析

4.1 pooled

  • pooled类型是最常用的,也是使用了连接池的数据源,我们先看这个,我们先通过一个表格梳理几个关键类
类名 描述 功能
PooledDataSource 使用连接池的数据源,且线程安全 提供连接的获取,归还,和连接池内部状态维护的功能
PooledConnection 从PooledDataSource中获取的数据库连接对象(不是真正的连接,只是一个代理) 提供数据库连接功能
PooledDataSourceFactory 数据源工厂 根据配置生产数据源
PoolState 用于描述PooledConnection连接对象状态的组件 通过两个list分别管理连接池中空闲连接和活跃连接,另外还包含很多状态计数变量

4.1.1 PooledDataSource

  • PooledDataSource是具备连接池的数据源,是简单,同步且线程安全的数据库连接池,内部维护一个连接池避免频繁创建数据库连接,
  • PooledDataSource的实现基于两个重要的类,一个是PooledConnection,它代表一个数据库连接,还有一个是PoolState,代表了一个连
    接池的状态,内部封装了很多连接池的信息。
  • PooledDataSource源码比较多,我们主要看数据库连接的获取,归还,和几个比较核心的方法
4.1.1.1 获取连接
  • PooledDataSource#popConnection是获取连接的核心方法
@Override
  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }
  
/**
     * 数据源获取连接的方法
     * 根据账号密码获取连接,获取的是PooledConnection对象,这个对象是一个代理对象,他代理了真正的连接对象,
     * 并对真正的连接对象做了一定的增强,具体可以参考PooledConnection#invoke方法的注释
     */
    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) {
            //1.加锁,PooledDataSource是线程安全的数据库连接池
            synchronized (state) {
                //2.如果有空闲连接队列中有连接,直接拿队列的首个连接对象
                if (!state.idleConnections.isEmpty()) {
                    // Pool has available connection
                    conn = state.idleConnections.remove(0);
                    if (log.isDebugEnabled()) {
                        log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } else {
                    //3.没有空闲连接,就走这一步
                    // Pool does not have available connection
                    //4.没有空闲连接,并且活跃的连接小于允许活跃的连接数上限,那么就可以创建新的连接
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        // Can create new connection
                        //5.创建新的连接,创建后直接跳到commentA处的代码逻辑
                        conn = new PooledConnection(dataSource.getConnection(), this);
                        if (log.isDebugEnabled()) {
                            log.debug("Created connection " + conn.getRealHashCode() + ".");
                        }
                    } else {
                        //6.没有空闲连接,并且活跃的连接处已经大于允许活跃的连接数上限,就会走到这里,此时不允许创建新的连接了
                        // Cannot create new connection
                        //7.不允许创建,那么就从活跃连接队列中拿一个连接(等第一个超时了就可以拿出来使用,第一个没超时后面的更加不会超时了)
                        PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                        //8.获取这个拿到的连接上一次检查到现在所经过的时间
                        long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                        //9.检查连接是否过期,如果checkoutTimestamp到现在的时间已经大于poolMaximumCheckoutTime就过期了
                        if (longestCheckoutTime > poolMaximumCheckoutTime) {
                            // Can claim overdue connection  
                            //10.连接已经过期,那么把这个连接从活跃队列中移除
                            //11.修改相关的状态变量
                            state.claimedOverdueConnectionCount++;
                            //累计超时时间增加
                            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                            //累计使用时间增加
                            state.accumulatedCheckoutTime += longestCheckoutTime;
                            //12.活跃队列移除对应的连接
                            state.activeConnections.remove(oldestActiveConnection);
                            //13.如果连接还有事物尚未提交,则回滚,这里条件的判断意思是连接没有开启自动提交,说明是手动提交模式,则帮他回滚这个事物
                            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                                try {
                                    oldestActiveConnection.getRealConnection().rollback();
                                } catch (SQLException e) {
                                    log.debug("Bad connection. Could not roll back");
                                }
                            }
                            //14.从拿到的连接里面获取到里面的真实的连接对象,再把这个真实的对象包装为一个PooledConnection
                            //(每个PooledConnection里面都包装了一个真正的连接对象)
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            //15.销毁之前的PooledConnection
                            oldestActiveConnection.invalidate();
                            if (log.isDebugEnabled()) {
                                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                            }
                        } else {
                            //16.如果活跃队列没有过期(第一个没有过期,说明后面的也不会过期),那么只能等待了
                            // (注意活跃连接不是可用连接,空闲连接才是可用连接,活跃连接是没有回收的连接,比如其他线程A在使用的连接,这个连接就是活跃连接,
                            // 获取到之后要判断这个连接是否超时了(比如网络原因),超时了就移除并重新设置状态,没有超时那么说明线程A还在使用,就只能等待了)
                            // Must wait
                            try {
                                if (!countedWait) {
                                    //连接池等待次数加1
                                    state.hadToWaitCount++;
                                    countedWait = true;
                                }
                                if (log.isDebugEnabled()) {
                                    log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                                }
                                long wt = System.currentTimeMillis();
                                //阻塞等待被唤醒,默认最长阻塞2秒
                                state.wait(poolTimeToWait);
                                //17.累加等待时间
                                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }
                //18.到这一步,理论上是拿到了一个连接了 --- commentA
                if (conn != null) {
                    if (conn.isValid()) {
                        //19.检查事物
                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }
                        conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                        conn.setCheckoutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        //20.新创建的这个连接需要加到活跃队列里面,这个连接可能是空闲队列拿到的,也可能是创建新的连接,也可能是从活跃队列里面移除了一个过期的,
                        //并把过期的PooledConnection里面的真实连接拿到包装成一个新的PooledConnection,不管是3中情况哪一种创建的,这里都需要把它加到活跃队
                        //列,这里也说明了注释16处的说明,活跃连接不是可用连接(如果没超时那就不可用,只能等,超时的话,做一些工作之后创建一个新的是可用的)
                        state.activeConnections.add(conn);
                        state.requestCount++;
                        state.accumulatedRequestTime += System.currentTimeMillis() - t;
                    } else {
                        //21.拿到的连接不合法,抛出异常
                        if (log.isDebugEnabled()) {
                            log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                        }
                        //相关状态修改
                        state.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
                            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.");
                        }
                    }
                }
            }

        }

        //22.返回前进行判断,如果为null就抛出异常,按理到这一步应该是拿到连接了,为了健壮性
        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.");
        }
        //23.返回
        return conn;
    } 
4.1.1.2 归还连接
  • PooledDataSource#pushConnection是归还连接的核心方法。
/**
     * 数据源归还连接的方法
     */
    protected void pushConnection(PooledConnection conn) throws SQLException {

        synchronized (state) {
            //1.加锁,PooledDataSource是线程安全的数据库连接池
            //2.如果活跃的连接池包含这个连接,则先移除
            state.activeConnections.remove(conn);
            //3.连接合法才有继续操作的必要
            if (conn.isValid()) {
                //4.空闲连接队列未满,并且connectionTypeCode和预期的一致,那么就尝试将连接归还(expectedConnectionTypeCode标识了一个连接池的连接)
                if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
                    state.accumulatedCheckoutTime += conn.getCheckoutTime();
                    //5.手动回滚未完成的事物
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    //6.将连接加到空闲连接里面去,其实并没有创建新的连接,而是基于这个连接里面的realConnection创建了一个PooledConnection对象
                    PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                    state.idleConnections.add(newConn);
                    //7.修改这个连接里面的状态变量
                    newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                    newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                    //8.销毁旧的PooledConnection
                    conn.invalidate();
                    if (log.isDebugEnabled()) {
                        log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                    }
                    //9.唤醒其他的等待获取连接的线程
                    state.notifyAll();
                } else {
                    //10.不满足4的条件,那么这个连接就会被销毁(比如空闲队列满了或者状态不对)
                    state.accumulatedCheckoutTime += conn.getCheckoutTime();
                    //11.回滚事务
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    //12.关闭连接
                    conn.getRealConnection().close();
                    if (log.isDebugEnabled()) {
                        log.debug("Closed connection " + conn.getRealHashCode() + ".");
                    }
                    //13.将连接置为不合法
                    conn.invalidate();
                }
            } else {
                //14.不合法连接打印一下日志
                if (log.isDebugEnabled()) {
                    log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
                }
                //15.状态变量加一
                state.badConnectionCount++;
            }
        }
    }
  • 查看源码之后发现,其实源码不难,这两个方法都使用synchronized同步代码块来保证线程安全,另外还用到了线程之间的同步唤醒机制(wait和notifyAll)
4.1.1.3 获取数据源状态
  • 数据源的状态实际上就是整个连接池的状态,实际上就是通过内部的PoolState对象来表示的,这里细节我们在PoolState部分再分析
  private final PoolState state = new PoolState(this);

  public PoolState getPoolState() {
    return state;
  }
4.1.1.4 pingConnection
  • pingConnection用于检查连接是否可用
 /**
     * Method to check to see if a connection is still usable
     * 检查连接是否可用
     *
     * @param conn - the connection to check
     * @return True if the connection is still usable
     */
    protected boolean pingConnection(PooledConnection conn) {
        boolean result = true;

        try {
            //1.如果连接是关闭的或者有异常,返回false
            result = !conn.getRealConnection().isClosed();
        } catch (SQLException e) {
            if (log.isDebugEnabled()) {
                log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
            }
            result = false;
        }

        //2.上面的没有异常,则进一步判断
        if (result) {
            //3.允许ping才进行判断
            if (poolPingEnabled) {
                //4.poolPingConnectionsNotUsedFor表示多久没有使用了才去ping,getTimeElapsedSinceLastUse返回上一次使用之后到现在过去了多久,
                //如果上一次使用到现在时间很短,那么就不要ping,为了提高效率,因为这样的情况下大概率连接是ok的,
                //如果上一次使用到现在已经过去很久了,大于poolPingConnectionsNotUsedFor这个阈值,那么就很有必要检查下这个连接了
                //这里也能看出,如果poolPingConnectionsNotUsedFor小于0,说明任何时候都不会去真正的ping服务器
                if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
                    try {
                        if (log.isDebugEnabled()) {
                            log.debug("Testing connection " + conn.getRealHashCode() + " ...");
                        }
                        //5.这里可以看出,其实就是执行了poolPingQuery这个sql语句,如果有异常,则方法返回false,默认是没有设置这个语句
                        //的-->"NO PING QUERY SET"
                        Connection realConn = conn.getRealConnection();
                        Statement statement = realConn.createStatement();
                        ResultSet rs = statement.executeQuery(poolPingQuery);
                        rs.close();
                        statement.close();
                        if (!realConn.getAutoCommit()) {
                            realConn.rollback();
                        }
                        result = true;
                        if (log.isDebugEnabled()) {
                            log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
                        }
                    } catch (Exception e) {
                        //6.如果没有设置poolPingQuery,这里就报异常,内部进行了异常捕获处理
                        log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
                        try {
                            conn.getRealConnection().close();
                        } catch (Exception e2) {
                            //ignore
                        }
                        result = false;
                        if (log.isDebugEnabled()) {
                            log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
                        }
                    }
                }
            }
        }
        return result;
    }
4.1.1.5 forceCloseAll
  • forceCloseAll用于强制关闭所有的活跃队列连接和空闲队列连接
    /*
     * Closes all active and idle connections in the pool
     * 强制关闭所有的活跃队列连接和空闲队列连接
     */
    public void forceCloseAll() {
        //1.同步加锁
        synchronized (state) {
            expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
            //2.关闭活跃队列内的连接
            for (int i = state.activeConnections.size(); i > 0; i--) {
                try {
                    PooledConnection conn = state.activeConnections.remove(i - 1);
                    //2.1销毁PooledConnection
                    conn.invalidate();
                    //2.2回滚事物
                    Connection realConn = conn.getRealConnection();
                    if (!realConn.getAutoCommit()) {
                        realConn.rollback();
                    }
                    //2.3关闭realConnection
                    realConn.close();
                } catch (Exception e) {
                    // ignore
                }
            }
            //3.关闭空闲队列内的连接
            for (int i = state.idleConnections.size(); i > 0; i--) {
                try {
                    PooledConnection conn = state.idleConnections.remove(i - 1);
                    //3.1销毁PooledConnection
                    conn.invalidate();
                    //3.2回滚事物
                    Connection realConn = conn.getRealConnection();
                    if (!realConn.getAutoCommit()) {
                        realConn.rollback();
                    }
                    //3.3关闭realConnection
                    realConn.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("PooledDataSource forcefully closed/removed all connections.");
        }
    }

4.1.2 PooledConnection

  • PooledConnection具备连接池能力的连接对象,PooledDataSource里面获取和归还的连接就是PooledConnection对象。但是PooledConnection不是
    真正的数据库连接对象,它封装了真正的连接对象Connection,采用代理模式拦截了Connection的方法,当客户端调用获取方法的时候,如果是close
    方法,那么实际上只是在内部将连接回收,并不会销毁,如果是object的方法,那么就直接由内部的Connect对象调用,如果是其他的方法,那么就检
    查内部的连接是否合法,再调用。因为使用了动态代理,因此PooledConnection实现了InvocationHandler接口
class PooledConnection implements InvocationHandler {

  //记录当前连接所在的数据源对象,本次连接是由这个数据源创建的,关闭后也是回到这个数据源;
  private final PooledDataSource dataSource;
  //真正的连接对象
  private final Connection realConnection;
  //连接的代理对象
  private final Connection proxyConnection;
  //从数据源取出来连接的时间戳
  private long checkoutTimestamp;
  //连接创建的的时间戳
  private long createdTimestamp;
  //连接最后一次使用的时间戳
  private long lastUsedTimestamp;
  //根据数据库url、用户名、密码生成一个hash值,唯一标识一个连接池
  private int connectionTypeCode;
  //连接是否有效
  private boolean valid;
}
4.1.2.1 invoke
  • 动态代理模式中最重要的方法就是handler类的invoke方法;
/**
     * Required for InvocationHandler implementation.
     *
     * @param proxy  - not used
     * @param method - the method to be executed
     * @param args   - the parameters to be passed to the method
     * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
     * 总结代理模式对真正的连接对象所做的增强:
     * 1.如果是调用close方法,那么就把连接回收
     * 2.如果调用的不是close,并且是自己定义的方法(不是Object类定义的方法),那么就在调用这个方法之前检查下连接是否合法,合法再调用这个方法,不合法就抛异常
     * 3.如果方法是Object定义的,那么就不检查是否合法,直接调用
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        //1.对于PooledDataSource,如果realConnection调用了close方法,那么就回收该连接,并不是直接关闭
        if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
            dataSource.pushConnection(this);
            return null;
        } else {
            try {
                //2.如果不是close方法,也不是Object类定义的方法,那么在执行这个方法之前检查下连接还是否合法,只通过valid检查,
                //如果不合法的话,就跑出异常了,
                if (!Object.class.equals(method.getDeclaringClass())) {
                    // issue #579 toString() should never fail
                    // throw an SQLException instead of a Runtime
                    checkConnection();
                }
                //3.如果是Object类定义的方法,那么就直接执行,比如toString方法,不会检查valid,总会成功的。
                return method.invoke(realConnection, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
    }
4.1.2.2 构造方法
//在构造方法中将真正的连接对象connection保存到内部的realConnection属性
public PooledConnection(Connection connection, PooledDataSource dataSource) {
    this.hashCode = connection.hashCode();
    this.realConnection = connection;
    this.dataSource = dataSource;
    this.createdTimestamp = System.currentTimeMillis();
    this.lastUsedTimestamp = System.currentTimeMillis();
    this.valid = true;
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }
4.1.2.3 其他方法
  • 其他大部分都是一些状态相关的方法,比如获取创建时间,获取最近一次的检查时间,判断状态是否合法,获取realConnection等
    public boolean isValid() {
        return valid && realConnection != null && dataSource.pingConnection(this);
    }

4.1.3 PoolState

  • PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别管理空闲状态的连接资源和活跃状态的连接资源,
    另外还有相关的计数状态变量,在获取和回收连接的过程中都会更新这些计数状态
/**
 * PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别
 * 管理空闲状态的连接资源和活跃状态的连接资源
 */
public class PoolState {

  protected PooledDataSource dataSource;
  //空闲的连接池资源集合
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  //活跃的连接池资源集合
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  //请求的次数
  protected long requestCount = 0;
  //累计的获得连接的时间
  protected long accumulatedRequestTime = 0;
  //累计的使用连接的时间。从连接取出到归还,算一次使用的时间;
  protected long accumulatedCheckoutTime = 0;
  //使用连接超时的次数
  protected long claimedOverdueConnectionCount = 0;
  //累计超时时间
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  //累计等待时间
  protected long accumulatedWaitTime = 0;
  //等待次数 
  protected long hadToWaitCount = 0;
  //无效的连接次数 
  protected long badConnectionCount = 0;

//构造方法
  public PoolState(PooledDataSource dataSource) {
    this.dataSource = dataSource;
  }

    //下面都是前面的状态类数据的获取方法,都是synchronized方法,保证线程安全,
  
  
  public synchronized long getRequestCount() {
    return requestCount;
  }

   //省略其他的get属性的方法
   //toString方法
}

4.1.4 PooledDataSourceFactory

  • PooledDataSourceFactory是获取PooledDataSource的工厂,使用了工厂模式,而且看到PooledDataSourceFactory是继承了UnpooledDataSourceFactory,这里主要继承了
    成员变量和设置属性,获取getDataSource的方法,因为这一套逻辑完全是复用的,具体可以参考后面的UnpooledDataSourceFactory。
  • 关于这里为什么直接继承有几点疑问,UnpooledDataSourceFactory是用于创建UnpooledDataSource的,现在继承了他,在构造的时候传入了PooledDataSource,那么等下创建
    出来的就是PooledDataSource,而数据库连接池的获取,归还和状态维护等是PooledDataSource维护的,PooledDataSource和UnpooledDataSource都是DataSource,因此这里使用
    返回DataSource的方式,将2种DataSource的创建共用一套逻辑。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

    /**
     * 继承了UnpooledDataSourceFactory的DataSource成员变量和 setProperties、getDataSource();这两个方法的实现
     */
    public PooledDataSourceFactory() {
        this.dataSource = new PooledDataSource();
    }
}
  • 示意图(从下图可以看出,对于UnpooledDataSource和DataSource来说,对于DataSourceFctory来说逻辑是一样的,因此2中Factory共用了1套逻辑,只是构造方法传入的DataSource类型不同):

10-Mybatis源码和设计模式-1(数据源模块和工厂模式,代理模式)_第2张图片

4.1.5 PooledDataSource的连接获取/归还流程图

4.1.5.1 获取流程图
  • 结合获取连接的方法,流程图如下

10-Mybatis源码和设计模式-1(数据源模块和工厂模式,代理模式)_第3张图片

4.1.5.2 归还流程图
  • 结合归还连接的方法,流程图如下:

10-Mybatis源码和设计模式-1(数据源模块和工厂模式,代理模式)_第4张图片

4.2 unpooled

4.2.1 UnpooledDataSource

  • UnpooledDataSource是不使用连接池的数据源,每次获取连接都是创建一个新的连接。
4.2.1.1 获取连接
  • UnpooledDataSource#doGetConnection是获取连接的核心方法,因为不使用连接池,每次获取的都是一个新的连接,因此代码其实比较简单,没有太多需要维护的状态信息,
    3个关键方法如下:
  //重写的DataSource接口的获取连接的方法
  @Override
  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) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    //根据属性对象获取连接
    return doGetConnection(props);
  }
  
    private Boolean autoCommit;
  private Integer defaultTransactionIsolationLevel;
  
  
    //根据属性对象获取连接
  private Connection doGetConnection(Properties properties) throws SQLException {
    //1.初始化驱动
    initializeDriver();
    //2.获取一个连接
    Connection connection = DriverManager.getConnection(url, properties);
    //3.配置连接;里面主要是根据设置来配置事物是否自动提交和事物隔离级别,这2个配置是UnpooledDataSource的2个属性,
    //可以set设置,这些设置应该是在UnpooledDataSourceFactory的setProperties中设置的
    configureConnection(connection);
    return connection;
  }
4.2.1.2 归还连接
  • UnpooledDataSource每次创建的都是一个新的连接,不提供连接的归还方法,相当于一次性使用,使用后就销毁了。
4.2.1.3 initializeDriver
  • 我们这里看看前面获取连接的初始化驱动方法,和JDBC类似
    //初始化驱动的方法
  private synchronized void initializeDriver() throws SQLException {
        //1.已经注册过的就不需要再注册了
      if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
            //2.这里和JDBC的代码是一样的
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver)driverType.newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        //3.放到注册中心,下一次就不需要再注册了
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

4.2.2 UnpooledDataSourceFactory

  • UnpooledDataSourceFactory的这套逻辑和PooledDataSourceFactory是一致的,前面我们没有分析,在这里分析,它实现了DataSourceFactory接口,
    代码还是比较清晰简单的
/**
 * @author Clinton Begin
 * 数据源工厂,定义了数据源具备的接口,设置属性接口和获取数据源的接口
 */
public interface DataSourceFactory {

  void setProperties(Properties props);
  DataSource getDataSource();
}

/**
 * @author Clinton Begin
 * 工厂模式,创建UnpooledDataSource的工厂,工厂继承DataSourceFactory
 * 整体逻辑和创建PooledDataSource是一样的,因此PooledDataSource直接继承复用
 */
public class UnpooledDataSourceFactory implements DataSourceFactory {

    //配置前缀
    private static final String DRIVER_PROPERTY_PREFIX = "driver.";
    //配置前缀长度
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();

    //Factory内部包含的数据源,可以是UnpooledDataSource或者PooledDataSource
    protected DataSource dataSource;

    /**
     * 将UnpooledDataSource传到工厂里面来
     */
    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }

    /**
     * 设置属性的方法
     */
    @Override
    public void setProperties(Properties properties) {
        Properties driverProperties = new Properties();
        //1.获取dataSource的MetaObject,便于修改属性,这里可以参考反射模块
        MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
        //2.遍历所有属性
        for (Object key : properties.keySet()) {
            String propertyName = (String) key;
            //3.如果属性以driver.开头,那么就设置到driverProperties属性对象里面去
            if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
                String value = properties.getProperty(propertyName);
                driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
            } else if (metaDataSource.hasSetter(propertyName)) {
                //4.如果不是以driver.开头,就检查是否有这个属性的setter方法,如果有就进一步去设置,没有就需要抛异常,底层由反射模块封装实现 
                String value = (String) properties.get(propertyName);
                //5.进行必要的属性值类型转换,比如转换成整型或者Long类型等
                Object convertedValue = convertValue(metaDataSource, propertyName, value);
                //6.设置值,key不变,但是value是转换后的
                metaDataSource.setValue(propertyName, convertedValue);
            } else {
                //3.前缀不对也没有set方法,属于未知属性,抛异常
                throw new DataSourceException("Unknown DataSource property: " + propertyName);
            }
        }
        //4.将处理后得到的属性对象设置到UnpooledDataSource的driverProperties属性 
        if (driverProperties.size() > 0) {
            metaDataSource.setValue("driverProperties", driverProperties);
        }
    }

    //获取数据源
    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 将需要设置的key对应的value的属性进行转换
     */
    private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
        Object convertedValue = value;
        Class<?> targetType = metaDataSource.getSetterType(propertyName);
        if (targetType == Integer.class || targetType == int.class) {
            convertedValue = Integer.valueOf(value);
        } else if (targetType == Long.class || targetType == long.class) {
            convertedValue = Long.valueOf(value);
        } else if (targetType == Boolean.class || targetType == boolean.class) {
            convertedValue = Boolean.valueOf(value);
        }
        return convertedValue;
    }
}

4.3 JNDI

  • JNDI数据源的实现是为了能在如EJB或tomcat应用服务器这类容器中使用,容器可以集中或在外部配置数据源,,因为使用不多,
    代码也不复杂,只有一个实现了DataSourceFactory的JndiDataSourceFactory工厂,就不解析了,可以阅读参考文献[1];

五、小结

  • 本文的主体是"数据源和工厂模式,代理模式",主要是解析了Mybatis中Pooled和Unpooled这两种主要的数据源类型,在创建数据源的时候,
    使用了UnpooledDataSourceFactory和PooledDataSourceFactory分别去创建UnpooledDataSource和PooledDataSource,符合工厂方法模式。
  • 在PooledDataSource中并没有直接使用数据库连接,而是使用PooledConnection这个包装了realConnection的加强版数据库连接,它加强的功能
    我们在invoke方法中可以比较清楚的看到,主要是对回收做了一定的增强,会收拾放到连接池队列,并且会维护连接池的状态。
  • 对于PooledDataSource,其最核心的职责是维护一个数据库连接池,内部通过2个队列(一个活跃队列,一个空闲队列)来维护的,在获取连接和回收
    连接的时候,都和这2个队列息息相关,具体可以参照4.1.4中的流程图和代码解析

六、参考

  • [1] MyBatis Tomcat JNDI原理及源码分析

你可能感兴趣的:(Mybatis)