Mybatis数据源的创建过程与销毁过程与工厂模式的理解

mybatis数据源的组件类图与简单的理解

在这里插入图片描述
数据源对象是比较复杂的对象,其创建过程相对比较复杂,对于 MyBatis 创建一个数据源
具体来讲有如下难点:

  1. 常见的数据源组件都实现了 javax.sql.DataSource 接口;
  2. MyBatis 不但要能集成第三方的数据源组件,自身也提供了数据源的实现;
  3. 一般情况下,数据源的初始化过程参数较多,比较复杂;
    综上所述,数据源的创建是一个典型使用工厂模式的场景,实现类图如下所示:

在这里插入图片描述
DataSource:数据源接口,JDBC 标准规范之一,定义了获取获取Connection 的方法;
 UnPooledDataSource:不带连接池的数据源,获取连接的方式和手动通过 JDBC 获取连
接的方式是一样的;
 PooledDataSource:带连接池的数据源,提高连接资源的复用性,避免频繁创建、关闭
连接资源带来的开销;
 DataSourceFactory:工厂接口,定义了创建 Datasource 的方法;
 UnpooledDataSourceFactory:工厂接口的实现类之一,用于创建UnpooledDataSource(不
带连接池的数据源);
 PooledDataSourceFactory:工厂接口的实现类之一,用于创建 PooledDataSource(带连
接池的数据源);
源码查看起始类 ,因为是实现了JDBC的初始类,所以我们查看的是DataSource的接口里面有两个方法:

getConnection()
 Connection getConnection(String username, String password)
    throws SQLException;

工厂模式的引用
那么mybatis相关的实现就一定有这两个实现:
org.apache.ibatis.datasource.unpooled.UnpooledDataSource#doGetConnection(java.lang.String, java.lang.String)
在这里插入图片描述

3.4 数据库连接池技术解析

数据库连接池技术是提升数据库访问效率常用的手段,使用连接池可以提高连接资源的复用
性,避免频繁创建、关闭连接资源带来的开销,池化技术也是大厂高频面试题。MyBatis 内
部就带了一个连接池的实现,接下来重点解析连接池技术的数据结构和算法;先重点分析下
跟连接池相关的关键类:
 PooledDataSource:一个简单,同步的、线程安全的数据库连接池
 PooledConnection:使用动态代理封装了真正的数据库连接对象,在连接使用之前和关
闭时进行增强;
 PoolState:用于管理 PooledConnection 对象状态的组件,通过两个 list 分别管理空闲状
态的连接资源和活跃状态的连接资源,如下图,需要注意的是这两个List 使用ArrayList
实现,存在并发安全的问题,因此在使用时,注意加上同步控制;
重点解析获取资源和回收资源的流程,获取连接资源的过程如下图:

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) {
          //检测是否有空闲连接
        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 {
            // 没有空闲连接
            //判断活跃连接池中的数量是否大于最大连接数
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // 没有则可创建新的连接
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }// 如果已经等于最大连接数,则不能创建新连接
          } else {
            //获取最早创建的连接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {//检测是否已经以及超过最长使用时间
              // 如果超时,对超时连接的信息进行统计
              state.claimedOverdueConnectionCount++;//超时连接次数+1
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;//累计超时时间增加
              state.accumulatedCheckoutTime += longestCheckoutTime;//累计的使用连接的时间增加
              state.activeConnections.remove(oldestActiveConnection);//从活跃队列中删除
              // 如果超时连接未提交,则手动回滚
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {//发生异常仅仅记录日志
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happend.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not intterupt current executing thread and give current thread a
                     chance to join the next competion for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  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) {
                  state.hadToWaitCount++;//连接池累计等待次数加1
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);//阻塞等待指定时间
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;//累计等待时间增加
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
          //获取连接成功的,要测试连接是否有效,同时更新统计数据
        if (conn != null) {
          // ping to server and check the connection is valid or not
          //检测连接是否有效
          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 {//如果连接无效
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;//累计的获取无效连接次数+1
            localBadConnectionCount++;//当前获取无效连接次数+1
            conn = null;
            //拿到无效连接,但如果没有超过重试的次数,允许再次尝试获取连接,否则抛出异常
            if (localBadConnectionCount > (poolMaximumIdleConnections + 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.");
    }

    return conn;
  }

在这里插入图片描述
在这里插入图片描述

根据图示我们可以很好的理解了连接池创建的过程与获取的过程

回收过程如下

 //回收连接资源
  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 newConn = new PooledConnection(conn.getRealConnection(), 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.");
          }
          //唤醒其他被阻塞的线程
          state.notifyAll();
         //如果闲置连接池已经达到上限了,将连接真实关闭
        } else {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          //关闭真的数据库连接
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          //将连接对象设置为无效
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    }
  }

对应着图
在这里插入图片描述
需要注意的是在PooledConnection这个类中,是封装了JDBC里面的连接池的而这个类是实现了InvocationHandler
可见Mybatis对它进行了增强
增强方法:


  /*
   * Required for InvocationHandler implementation.
   * 此方法专门用来增强数据库connect对象,使用前检查连接是否有效,关闭时对连接进行回收
   *
   * @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[])
   */
  @Override
  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);//通过pooled数据源来进行回收
      return null;
    } else {
      try {
    	  //使用前要检查当前连接是否有效
        if (!Object.class.equals(method.getDeclaringClass())) {
          // issue #579 toString() should never fail
          // throw an SQLException instead of a Runtime
          checkConnection();//
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

一个类的封装在我们看来就是数据结构,而各种各样的接口方法对我们来说就是算法,程序其实就是数据结构与算法的组合罢了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值