开发者

MyBatis基础支持DataSource实现源码解析

目录
  • DataSource
  • UnpooledDataSource
  • PooledDataSource
    • PooledConnection
    • PoolState
    • 获取连接
    • 关闭连接
    • 小结
  • DataSourceFactory
    • UnpooledDataSourceFactory
    • PooledDataSourceFactory
  • 结语

    DataSource

    在数据库应用中,客户端与数据库服务端建立的连接对象(Connection)是宝贵的资源,每次请求数据库都创建连接,使用完毕后会销毁连接,这是一种很浪费资源的操作。因此Java提出了DataSource接口。可以把它当作一个连接池。程序初始化时,创建一批连接放入到连接池中,如果需要请求数据库就从连接池中取出连接对象(Connection)使用完毕后把连接归还给连接池。这样就减少了每次请求都创建、销毁连接的步骤,从而提高数据库性能。

    package javax.sql;
    public interface DataSource  extends CommonDataSource, Wrapper {
      // 最重要的方法
      Connection getConnection() throws SQLExce编程ption;
      // 其他方法不再列出
    }
    

    Java只是在JDK1.4版本发布了该接口规范。具体实现需要用户自己实现。MyBATis中提供了3种DataSource接口的实现。

    • UnpooledDataSource
    • PooledDataSource
    • JNDI方式的接口(不在本文讨论范围)

    下面着重分析1和2这两种DataSource的实现。

    UnpooledDataSource

    UnpooledDataSource顾名思义,他是非池化的DataSource,说白了和普通的Connection没什么区别。通过UnpooledDataSource过去连接每次都需要重新创建一个Connection。我们来看下它的getConnection实现方法。

    public Connection getConnection() throws SQLException {
      return doGetConnection(username, password);
    }
    private Connection doGetConnection(Properties properties) throws SQLException {
      initializeDriver();
      Connection connection = DriverManager.getConnection(url, properties);
      configureConnection(connection);
      return connection;
    }
    

    在UnpooledDataSource#getConnection方法中,调用了doGetConnection方法,参数是username和password,该方法也就是通过用户名和密码获取数据库连接的意思。doGetConnection具体实现就使用了DriverManager来获取连接对象。这是JDBC原生获取连接对象的方式。

    值得一说的是:UnpooledDataSource的其他方法都是基于DriverManager实现的。也就是说,使用UnpooledDataSource作为连接池的话等价于没有使用连接池。

    PooledDataSource

    PooledDataSource才是真正意义上的连接池,它提供了连接池的大小(默认10)、最大活跃连接数量、空闲连接数量等蚕食设置。并且对Connection对象进行了JDK动态代理,重写了Connection的close方法。使得Connection对象在调用close方法是不是真正的关闭连接,而是把自定义关闭行为,MyBatis的关闭逻辑就是把Connection对象归还连接池。

    我们先看下www.devze.comPooledDataSource的几个重要字段信息

    public class PooledDataSource implements DataSource {
      // PooledDataSource真正管理连接状态的是PoolState,后面会详细说明
      private final PoolState state = new PoolState(this);
      // UnpooledphpDataSource上面说过和普通的Connection无异
      private final UnpooledDataSource dataSource;
      //正在使用连接的数量
      protected int poolMaxi开发者_开发培训muMACtiveConnections = 10;
      //空闲连接数
      protected int poolMaximumIdleConnections = 5;
      //在被强制返回之前,池中连接被检查的时间
      protected int poolMaximumCheckoutTime = 20000;
      //这是给连接池一个打印日志状态机会的低层次设置,还有重新 尝试获得连接, 这些情况下往往需要很长时间 为了避免连接池没有配置时静默失 败)。
      protected int poolTimeToWait = 20000;
      //发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 接受请求。默认是“NO PING QUERY SET” ,这会引起许多数据库驱动连接由一 个错误信息而导致失败
      protected String poolPingQuery = "NO PING QUERY SET";
      //开启或禁用侦测查询
      protected boolean poolPingEnabled = false;
      //用来配置 poolPingQuery 多次时间被用一次
      protected int poolPingConnectionsNotUsedFor = 0;
      private int expectedConnectionTypeCode;
    }
    

    这些字段主要记录了连接池的重要信息:连接池大小、空闲时最大连接数、最大活跃连接数、超时时间等。而整整揭开PooledDataSource获取连接对象的神秘面纱还需要介绍两个类。PooledConnection和PoolState

    PooledConnection

    PooledConnection实现了InvocationHandler接口,他是用来做JDK动态代理的。前文提到过,mybatis使用JDK动态代理重写了Connection对象的close方法,就是在该类中实现的逻辑。该类有几个重要属性。

    • private PooledDataSource dataSource; // dataSource的副本
    • private Connection realConnection; // 真实连接对象
    • private Connection proxyConnection; // 实际返回的代理对象

    接下来来看下代理对象的invoke方法是如何重写close方法的。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      //如果调用close的话,忽略它,反而将这个connection加入到池中
      if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        dataSource.pushConnection(this);
        return null;
      } 
      return method.invoke(realConnection, args);
      // 其他逻辑省略....
    }
    

    在invoke方法中判断下执行的方法名称是否是Close,如果是,就不再执行原来的close方法了,而是执行PooledDataSource 的pushConnection方法!从方法名可以看出方法的作用是:把连接push到连接池PooledDataSource 中。pushConnection的逻辑后文详细说明

    PoolState

    上文提到PooledDataSource并不管理连接对象。那么程序初始化的时候创建的一批连接存放到哪里了呢?答案是存在PoolState对象中,而PooledDataSource有一个属性就是PoolState。也就是说PooledDataSource是通过PoolState来管理连接池的。

    一批连接在Java中就是一个List集合嘛。那么我们想一下PoolState都需要怎么管理连接呢?首先根据连接的状态,可以把连接分为2种

    • 空闲连接protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
    • 活跃连接protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

    PoolState中两个List属性分别存储空闲连接和活跃连接。需要连接的时候就从idleConnections 列表中取,关联连接时就把连接从activeConnections 中移到idleConnections 中。

    PoolState中还有一些其他的统计信息字段,比如 请求次数、请求的总时间、总连接数等这些属性比较简单就不再列出了

    获取连接

    介绍完PooledConnection和PoolState这两个类后,我们来看下PooledDataSource是怎么获取连接的。获取连接的逻辑在PooledDataSource#getConnection方法中,getConnection方法只是一个壳子,具体调用逻辑在popConnection方法。我们来看一下(我只列出了重要逻辑)

    public Connection getConnection() throws SQLException {
      return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
    }
    private PooledConnection popConnection(String username, String passwwww.devze.comord) throws SQLException {
      //最外面是while死循环,如果一直拿不到connection,则不断尝试
      while (conn == null) {
        synchronized (state) {
          if (!state.idleConnections.isEmpty()) {
            //如果有空闲的连接的话,返回第一个空闲连接
            conn = state.idleConnections.remove(0);
          } else {
            //如果没有空闲的连接
            if (state.activeConnections.size() &lt; poolMaximumActiveConnections) {
              //如果activeConnections太少,那就new一个PooledConnection
              conn = new PooledConnection(dataSource.getConnection(), this);
            } else {
              //如果activeConnections已经很多了,那不能再new了
              //取得activeConnections列表的第一个(最老的)
              PooledConnection oldestActiveConnection = state.activeConnections.get(0);
              long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
              if (longestCheckoutTime &gt; poolMaximumCheckoutTime) {
                //如果checkout时间过长,则这个connection标记为overdue(过期)
                //删掉最老的连接,然后再new一个新连接
                conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                oldestActiveConnection.invalidate();
              } else {
                //如果checkout时间不够长,没办法,只能等待,在此分支会记录一些统计信息
              }
            }
          }
          if (conn != null) {
            if (conn.isValid()) {
              //如果已经拿到connection,则记录一些统计信息
            } else {
              //如果没拿到,统计信息:坏连接+1
              state.badConnectionCount++;
              localBadConnectionCount++;
              conn = null;
              //如果好几次都拿不到,就放弃了,抛出异常
            }
          }
        }
      }
      return conn;
    }
    

    在popConnection中

    • 从PoolState对象的空闲连接列表中获取连接,如果有空闲连接就返回。
    • 从PoolState对象的活跃连接列表中获取连接,如果连接数小于最大活跃数,则new一个连接返回。如果没有只能等待其他线程释放连接再进行获取
    • 无论是否获取到连接,对连接进行一些信息统计并记录到PoolState对象中。一旦尝试获取连接的时间超过了阈值,就会放弃获取连接抛出异常

    关闭连接

    在PooledConnection小节中见到,PooledConnection重写了Connection的close方法。当调用Connection的close方法时真正执行的逻辑是PooledDataSource的pushConnection方法。该代码逻辑很简单,大体上说,就是把连接从活跃列表中删除,加入到空闲列表中。具体实现如下

    protected void pushConnection(PooledConnection conn) throws SQLException {
      synchronized (state) {
        //先从activeConnections中删除此connection
        state.activeConnections.remove(conn);
        if (conn.isValid()) {
          if (state.idleConnections.size() &lt; poolMaximumIdleConnections &amp;&amp; conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
            //如果空闲的连接太少,
            state.accumulatedCheckoutTime += conn.getCheckoutTime();
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            //new一个新的Connection,加入到idle列表
            PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
            state.idleConnections.add(newConn);
            //通知其他线程可以来抢connection了
            state.notifyAll();
          } else {
           //否则,即空闲的连接已经足够了
            state.accumulatedCheckoutTime += conn.getCheckoutTime();
            //那就将connection关闭就可以了,获取真正的connection对象并且关闭
            conn.getRealConnection().close();
            conn.invalidate();
          }
        } 
      }
    }
    

    关闭过程:

    • 空闲连接数<最大空闲连接数 则新建一个连接存放到PoolState的空闲列表中并通知其他线程可以来抢Connection对象
    • 如果PoolState的空闲列表是满的,那只能获取真正编程客栈的connection对象并将其关闭了。

    小结

    • PooledDataSource真正意义上实现了DataSource接口。具有连接池的意义
    • PooledDataSource通过PooledConnection和PoolState来管理连接池中的连接
    • PooledConnection重写了Connection对象的close方法。调用Connection的close方法时并不会真正的关闭连接,而是先要进行归还连接的操作。
    • PoolState是对连接列表状态的管理。它有两个List属性,分别存储了活跃连接列表空闲连接列表

    DataSourceFactory

    获取MyBatis提供的DataSource实现,需要通过工厂DataSourceFactory接口来获取。在这里MyBatis使用了工厂方法模式。DataSourceFactory有两个实现类。分别是

    • UnpooledDataSourceFactory
    • PooledDataSourceFactory

    我们首先来看下工厂接口定义

    public interface DataSourceFactory {
      //设置属性,被XMLConfigBuilder所调用
      void setProperties(Properties props);
      //生产数据源,直接得到javax.sql.DataSource
      DataSource getDataSource();
    }
    

    其中最重要的方法就是getDataSource,它很直观,通过工厂对象的该方法可以获取DataSource实现。

    UnpooledDataSourceFactory

    UnpooledDataSourceFactory获取dataSource的方法非常简单直观。

    首先,构造方法里里new了一个UnpooledDataSource对象存放到工厂的属性中

    然后,getDataSource直接返回该对象即可。具体实现如下

    public class UnpooledDataSourceFactory implements DataSourceFactory {
      protected DataSource dataSource;
      public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
      }
      public DataSource getDataSource() {
        return dataSource;
      }
    }
    

    PooledDataSourceFactory

    PooledDataSourceFactory就有意思了,想偷懒,直接继承自UnpooledDataSourceFactory。只需要在构造方法中new一个PooledDataSource对象,再通过getDataSource方法获取即可。

    public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
      //数据源换成了PooledDataSource
      public PooledDataSourceFactory() {
        this.dataSource = new PooledDataSource();
      }
    }
    

    结语

    个人感觉mybatis提供的DataSourceFactory的实现类有点鸡肋。可以说还是new对象。我们知道工厂模式创建的一般都是比较复杂的对象,是用来帮助开发者屏蔽复杂的细节。而mybatis的这两个实现都只是new对象而已。

    以上就是MyBatis基础支持DataSource实现源码解析的详细内容,更多关于MyBatis基础支持DataSource的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜