一起认识Mybatis-数据源

Mybatis(二) —数据源DataSource

使用过数据库连接的读者对数据源都不会陌生,我们是通过数据源去拿到数据库连接的,本文主要从两个方面去介绍Mybatis中数据源这个模块:

1.数据源的创建。

2.数据库连接池技术。

数据源的创建

数据源对象是比较复杂的对象,其创建过程相对比较复杂,对于 MyBatis 创建一个数据源,主要有一下三个难点:

1.我们常见的数据源组件(C3P0,druid,DBCP等)都实现了javax.sql.DataSource 接口。

2.Mybatis要能集成第三方的数据源组件,自身也提供了数据源的实现。

3.一般情况下,数据源的初始化过程参数较多,比较复杂。

对于以上的问题,我们可以发现数据源的创建是一个典型使用工厂模式的场景,那么我们先来介绍下工厂模式:

其实工厂模式还有两种类似的设计模式,简单工厂模式和抽象工厂模式,这两个不是今天的主角,但他们都是对类实例化过程封装的一种设计模式。存在各自的优缺点,之后在设计模式专题中会将这几种设计模式一一介绍。今天主要讲解工厂模式,我们用一个牛奶的例子来讲解。假设我们不用工厂设计模式,我们在使用一个比较复杂的对象时,我们会new这个对象,给对象初始化值等过程,都需要手动去完成,就好比如我们如果要喝一瓶奶,需要自己去生产这一瓶奶,其实过程是十分复杂的,而且我们现实生活中也不会说自己去生产奶,我们都是通过购买工厂生产的牛奶来饮用的,比如我们今天想喝蒙牛的牛奶,我们只需要购买蒙牛的奶就能喝到,明天想喝伊利的牛奶,我们只需要购买伊利的奶就能喝到,不需要关注不同厂商的奶的生产过程,我们只管喝就好了。这也是我们在实例化对象时使用工厂模式的原因,对于比较复杂的对象的实例化过程,我们希望能够避免,我们并不关注这个过程,我们只是想拿到一个对象来使用就好了。工厂模式中有四个元素是比较重要的:

产品接口(Product):产品接口用于定义产品类的功能,具体工厂类产生的所有产品都必须实现这个接口。调用者与产品接口直接交互,这是调用者最关心的接口;

具体产品类(ConcreteProduct):实现产品接口的实现类,具体产品类中定义了具体的 业务逻辑;

工厂接口(Factory):工厂接口是工厂方法模式的核心接口,调用者会直接和工厂接口 交互用于获取具体的产品实现类;

具体工厂类(ConcreteFactory):是工厂接口的实现类,用于实例化产品对象,不同的具体工厂类会根据需求实例化不同的产品实现类;

上面介绍完了工厂模式,大家也都有所了解了,那么工厂模式有哪些优点呢?

1.把对象的创建和使用的过程分开,对象创建和对象使用使用的职责解耦;

2.如果创建对象的过程很复杂,创建过程统一到工厂里管理,既减少了重复代码,也方便以后对创建过程的修改维护;

3.当业务扩展时,只需要增加工厂子类,符合开闭原则;

数据源的创建是一个典型使用工厂模式的场景,我们在Mybatis数据源模块中看看他是怎么使用工厂模式的。

一起认识Mybatis-数据源_第1张图片

DataSource:数据源接口,JDBC 标准规范之一,定义了获取获取 Connection 的方法;

UnPooledDataSource:不带连接池的数据源,获取连接的方式和手动通过 JDBC 获取连接的方式是一样的;

PooledDataSource:带连接池的数据源,提高连接资源的复用性,避免频繁创建、关闭连接资源带来的开销;

DataSourceFactory:工厂接口,定义了创建 Datasource 的方法;

UnpooledDataSourceFactory:工厂接口的实现类之一,用于创建 UnpooledDataSource(不连接池的数据源);

PooledDataSourceFactory:工厂接口的实现类之一,用于创建 PooledDataSource(带连接池的数据源);

我们只需要获得相应的工厂实例,然后调用getDataSource方法,就可以获取到相应的数据源了。在UnpooledDataSource这种不带连接池的数据源中,每次调用getConnection获取数据库连接时都是重新创建一个连接。而在pooledDataSource这种带数据库连接池的数据源中,会有一个专门的连接池来管理线程(准确的说是在PoolState中有两个ArrayList,一个是管理空闲连接,一个是管理工作中的连接)。接下来我们就来看看关于连接池技术。

数据库连接池技术

数据库连接池技术是提升数据库访问效率常用的手段,使用连接池可以提高连接资源的复用性,避免频繁创建、关闭连接资源带来的开销,接下来重点解析连接池技术的数据结构和算法。

先重点分析下跟连接池相关的关键类:

PooledDataSource:一个简单、同步、线程安全的数据库连接池

PooledConnection:使用动态代理封装了真正的数据库连接对象,在连接使用之前和关闭时进行增强;

PoolState:用于管理 PooledConnection 对象状态的组件,通过两个 list 分别管理空闲状态的连接资源和活跃状态的连接资源,如下图,需要注意的是这两个List 使用 ArrayList实现,存在并发安全的问题,因此在使用时,注意加上同步控制。

在这个连接池中使用的并不是原生的Connection连接,而是进行增强后的代理对象,先来看看PooledConnection中是如何进行增强的:(增强逻辑主要在invoke()方法中)

class PooledConnection implements InvocationHandler {
     
    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class[]{
     Connection.class};
    private final int hashCode;
    private final PooledDataSource dataSource;
    private final Connection realConnection;//真实对象
    private final Connection proxyConnection;//代理对象
    private long checkoutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valid;

    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);
        //初始化时将代理对象创建好
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
        String methodName = method.getName();
        if ("close".equals(methodName)) {
     
            this.dataSource.pushConnection(this);
            return null;
        } else {
     
            try {
     
                if (!Object.class.equals(method.getDeclaringClass())) {
     
                    this.checkConnection();
                }

                return method.invoke(this.realConnection, args);
            } catch (Throwable var6) {
     
                throw ExceptionUtil.unwrapThrowable(var6);
            }
        }
    }
}

一起认识Mybatis-数据源_第2张图片

可以看出获取线程前线判断线程的有效性,而释放线程时并不是直接关闭连接,是将线程放入到线程池的空闲线程池中。

来看看PoolState:这个类主要是提供了两个容器管理空闲和工作连接,并且记录了连接池的相关信息

一起认识Mybatis-数据源_第3张图片

进入到PooledDataSource这个类中,我们看看里面的一些细节:获取连接的过程比较复杂,这里会一步步分析

public class PooledDataSource implements DataSource {
     
  private static final Log log = LogFactory.getLog(PooledDataSource.class);
  //线程池的状态信息
  private final PoolState state = new PoolState(this);
  //真正用于创建连接的数据源
  private final UnpooledDataSource dataSource;
  // OPTIONAL CONFIGURATION FIELDS
  //最大活跃连接数
  protected int poolMaximumActiveConnections = 10;
  //最大闲置连接数
  protected int poolMaximumIdleConnections = 5;
  //最大checkout时长(最长使用时间)
  protected int poolMaximumCheckoutTime = 20000;
  //无法取得连接是最大的等待时间
  protected int poolTimeToWait = 20000;
  //最多允许几次无效连接
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  //测试连接是否有效的sql语句
  protected String poolPingQuery = "NO PING QUERY SET";
  //是否允许测试连接
  protected boolean poolPingEnabled;
  //配置一段时间,当连接在这段时间内没有被使用,才允许测试连接是否有效
  protected int poolPingConnectionsNotUsedFor;
  //根据数据库url、用户名、密码生成一个hash值,唯一标识一个连接池,由这个连接池生成的连接都会带上这个值
  private int expectedConnectionTypeCode;
    
//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 (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) {
     //发生异常仅仅记录日志
                  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) {
     //获取连接成功的,要判检查接是否有效,同时更新统计的数据
          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;
  }
    
//pushConnection回收连接,相对于获取连接会简单许多
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++;
      }
    }
  }
}

整个获取连接和释放连接其实都是围绕着PoolState开展的,PoolState统计连接的相关信息并拥有两个连接的管理容器。由于这两个容器是ArrayList,线程不安全的,所以每次操作这两个容器时都需要获取同步状态(Synchronized控制同步状态)。

以上就是本节内容,谢谢大家的阅读,如有错漏,欢迎评论区指正提出!

你可能感兴趣的:(java,spring,boot,mysql,mybatis)