其他分享
首页 > 其他分享> > MyBatis(技术NeiMu):基础支持层(DataSource)

MyBatis(技术NeiMu):基础支持层(DataSource)

作者:互联网

回顾

上一篇,分析了类型转换、日志适配器、类加载模块和ResolverUtil(用于寻找指定包下符合要求的类),并且还看到了使用的一些设计模式,比如适配器模式、单例模式

下面就到基础支持层的一个核心,DataSource

DataSource

在数据持久层中,数据源是一个非常重要的组件,性能会直接关乎到整个持久层的性能。

大多数时候,我们都会去使用第三方的数据源,比如Apache Common DBCP、C3P0、Proxool等,MyBatis不仅支持可以自定义去集成第三方数据源组件,还提供了自己的数据源实现

首先我们要认识一下DataSource这个接口,这个接口是javax.sql.DataSource下的

在这里插入图片描述
常见的数据源组件都是实现了这个DataSource接口的,所以MyBatis自身定义的数据源也会去实现这个接口,我们就可以从中知道MyBatis实现了多少种数据源,并且在工厂模式中,生产出来的产品就是一个DataSource
在这里插入图片描述
可以看到,对于MyBatis,总共有两种实现

MyBati有多个数据源,那么MyBatis是如何进行管理的呢?

DataSourceFactory

对于多个数据源,MyBatis是使用DataSourceFactory来进行管理的,从这里就可以看到,大概就可以估计使用了工厂模式了

在这里插入图片描述
从上面可以看到,DataSourceFactory这个接口里面仅仅提供了两个方法

在这里插入图片描述
并且可以看到,MyBatis对于DataSourceFactory接口有三个实现类

UnpooledDataSourceFactory

下面我们先来看下UnpooledDataSource数据源的创建工厂

在这里插入图片描述
从构造方法上我们就可以看到,当实例化UnPooledDataSourceFactory时,就会去创建UnpooledDataSource了,并且注入进自己的DataSource成员属性中去

下面再来看看DataSourceFactory中的getDataSource方法是如何实现的

在这里插入图片描述
可以看到,十分朴实,直接返回构造出来的UnpooledDataSource

接下来看看是如何重写setProperty方法的,源码如下

 public void setProperties(Properties properties) {
     //存储对于DataSource的配置
    Properties driverProperties = new Properties();
     //使用SystemMetaObject去创建DataSource的MetaObject对象
     //前面我们已经提到过这个MetaObject对象了,是MyBatis用来管理实例对象的信息的!!!!
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
     //遍历要设置的属性
    for (Object key : properties.keySet()) {
        //获取当前要设置的属性的名称
      String propertyName = (String) key;
        //如果是driver.
        //代表是对DataSource的配置
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
          //添加进driverProperties集合中去
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } 
        //下面就是设置其他的相关属性了,必须要有setter方法才能够进行设置
        else if (metaDataSource.hasSetter(propertyName)) {
            //调用set方法进行设置属性
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
     //最后去设置DataSource的配置
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

可以看到,UnpooledDataSourceFactory对于setProperty方法是先将DataSource转换为MetaObject,然后再继续进行set注入的,所以这里就可以看到MyBatis对于MetaObject的运用了,将当前的实例DataSource转换为MetaObject(MetaObject可以用于管理实例的信息),然后进行对应的属性修改或注入

PooledDataSourceFactory

PooledDataSourceFactory则更简单,其是用来创建PooledDataSource的

在这里插入图片描述
可以看到PooledDataSourceFactory是继承了UnpooledDataSourceFactory的,仅仅只是构造方法上去创建数据源不同而已,PooledDataSourceFactory创建的是PooledDataSource,而UnpooledDataSourceFactory创建的是UnPooledDataSource

其他的方法,比如获取数据源,设置数据源属性都是跟UnpooledDataSourceFactory一样的

JndiDataSourceFactory

JndiDataSourceFactory是使用JNDI服务来去设置第三方数据源的,在之前的JVM学习双亲委派模型的时候,针对JNDI服务可以破坏过依次双亲委派模型的

JNDI

JNDI是全名为Java Naming and Directory Interface,是Sum公司提供的一套API规范,其功能是模仿Window的注册表

相当与把用户的配置存放进去了注册表中,然后JNDI可以从该注册表中取信息出来

而JndiDataSourceFactory就i是可以利用Jndi服务从容器中去获取用户配置的DataSource的

看完DataSourceFactory之后,下面就来看看DataSource

UnpooledDataSource

UnpooledDataSource的特点就是如名字一样,没有池子,也就是没有连接池,每次获取数据库连接时都会创建一个新连接

在这里插入图片描述
下面我们先来看看UnpooledDataSourced的成员属性

回忆一下之前使用原生的JDBC

第一步:注册驱动,DriverManager.registerDriver

注册驱动之后也是保存在DriverManger的一个容器里面,如下所示

在这里插入图片描述
在这里插入图片描述
下面再看一下静态代码块,因为当第一次访问UnpooledDataSource会执行静态代码块

在这里插入图片描述
源码如下

static {
    //获取注册进DriverManager的驱动
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    //遍历将注册的驱动都添加进缓存中
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

可以看到,注册驱动并不是在DataSource里面去完成的,并且DataSource里面还支持多种驱动!!!!!!说白了支持多个数据库

下面再看看是如何获取连接的

getConnection

在这里插入图片描述
getConnection对应的就是去获取Connection连接的,可以看到其调用了doGetConnection方法,怎么框架都喜欢将实质操作给到doxxx方法去做的呀,在Spring框架也是一样,明明已经是createBean了,还要推到doCreateBean去实现

下面来看一下doGetConnection做了什么事情

源码如下

private Connection doGetConnection(String username, String password) throws SQLException {
    //封装获取连接的配置参数
    //Properties本质上是一个HashTable
    //Properties还是java.util下的类,是Java原生的,适合存一些键值对配置信息
    Properties props = new Properties();
    //装入所有的数据源配置
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    //装入用户名
    if (username != null) {
      props.setProperty("user", username);
    }
    //装入密码
    if (password != null) {
      props.setProperty("password", password);
    }
    //调用doGetConnection的重载方法
    return doGetConnection(props);
  }

可以看到,在这一层仅仅做了封装参数而已,封装了数据源的配置、连接数据源的账号与密码,接下来就委托到doGetConnection方法去实现了

重载的doGetConnection源码如下

private Connection doGetConnection(Properties properties) throws SQLException {
    //初始化驱动,针对没有驱动
    initializeDriver();
    //使用DriverManager来进行获取连接。。。。。。
    Connection connection = DriverManager.getConnection(url, properties);
    //对获取的连接进行一些信息配置
    configureConnection(connection);
    return connection;
  }

步骤如下

下面对这几个步骤逐个分析

initializeDriver

源码如下

private synchronized void initializeDriver() throws SQLException {
    //判断驱动是否已经注册了
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
          //如果驱动没有注册
          //使用ClassLoader来创建驱动类
          //根据驱动的名称进行创建,所以这里驱动的名称应该是一个全限定类名
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
          //使用反射来创建驱动的实例
        Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
          //DriverManager来注册驱动
        DriverManager.registerDriver(new DriverProxy(driverInstance));
          //并且将注册的驱动添加到缓存中去
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

可以看到初始化驱动其实就是判断是否已经将UnpooledDataSource的驱动已经完成注册了(虽然有缓存,但只能用一个驱动!!!),如果没有注册,说白了就是缓存中没有当前驱动的名称,如果有就代表已经注册了,没有就代表没有注册,如果没有注册代表还没有去创建驱动,需要使用ClassLoader+反射来创建出驱动实例,然后交由DriverManager进行注册驱动,完成注册驱动后,将注册的驱动添加到缓存中去

DriverManager.getConnection

使用DriverManager.getConnection来获取连接就没什么好说的

configureConnection

接下来就是要配置一下Connection连接了,是否自动提交、超时时间的设置、默认的事务隔离等级,源码如下

private void configureConnection(Connection conn) throws SQLException {
    //判断定义的默认的超时时间是不是不为空
    if (defaultNetworkTimeout != null) {
        //如果超时时间存在定义,那就去重新定义Connection的超时时间
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    //判断是否有自定义自动提交属性
    //如果设置了自动提交属性并且自定义的提交属性与Connection原来默认的存在出入
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
        //进行设置
        //这里之所以判断复杂,个人认为应该是自动提交是个敏感的属性
      conn.setAutoCommit(autoCommit);
    }
    //如果自定义的默认事务隔离机制不为空
    if (defaultTransactionIsolationLevel != null) {
        //给Connection设置自定义的事务隔离机制
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

从代码中可以看到,对于创建出的Connection的配置其实就是针对以下三个方面

从整个获取连接的过程可以看到,getConnection每次都会调用DriverManager去创建Connection,创建出新连接,这也是unPooled的原因

PooledDataSource

创建数据库连接也就是Connection是一个 非常耗时的操作,而且数据库能够建立的连接数也是有限的,所以在绝大多数系统中,数据库连接是一个非常珍贵的资源,数据库连接池是很有必要性的,使用数据库连接池会带来一系列的好处,例如,可以实现数据库连接的重用、提高响应速度、防止数据库连接过多造成数据库假死、同时避免数据库连接泄漏

数据库连接池的工作流程是怎样的呢?

下面分析一下,总连接数和空闲连接数的上线设置过大过小会有什么问题

下面就来看下PooledDataSource是如何实现的

在这里插入图片描述
可以看到,PooledDataSource同样实现了DataSource接口,并且其还组装了UnpooledDataSource与PoolState

PooledConnection

PooledDataSource并不会直接管理Connection对象,而是管理经过了一层封装的PooledConnection对象,在PooledConnection中封装了真正的数据库连接对象,也就是Connection,还有其代理对象,该代理对象是由JDK动态代理产生的

在这里插入图片描述
可以看到,PooledConnection实现了InvocationHandler接口,说明了PooledConnection本身就是一个代理对象。。。。。。

下面来看一下PooledConnection的核心字段

前面提到过,PooledConnection本身实现了InvocationHandler接口,说明其本身就是一个代理对象,那么为什么在成员属性上会有一个代理对象呢?

我们来看一下该构造方法

源码如下

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;
    //可以看到,真实产生的代理对象是属性中的proxyConnection
    //属性中的代理对象是通过当前PooledConnection来创建出来的,个人感觉怎么像是变成了一个静态代理
    //相当于只要去new了代理对象,代理对象内部就会去实例出了一个被当前代理对象代理的Connection出来
    //编码能力确实牛逼。。。。。。。
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }

从代码上可以看到,MyBatis的确有点东西。。。。。。。

个人感觉这种方式跟静态代理差不多了,通过创建代理对象就完成了代理

对于JDK动态代理对象,重点就要在于invoke方法,下面就来看看invoke方法做了什么逻辑

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //获取执行的方法名
    String methodName = method.getName();
    //如果方法名是close
    if (CLOSE.equals(methodName)) {
        //让dataSource去回收缓存,而不是真正的关闭!!!
      dataSource.pushConnection(this);
        //不去执行真正的close逻辑
      return null;
    }
    try {
        //判断是不是执行Object里面的方法
        
      if (!Object.class.equals(method.getDeclaringClass())) {
          //如果执行不是Object里面的方法
          //要检查当前连接是不是失效了
          //里面的逻辑仅仅只是判断valid字段而已,要是为false就会抛错
        checkConnection();
      }
        //这里很关键,可以看到,这里是交由了realConnection去执行的
        //而不是proxyConnection,也就是被代理的Connection去执行逻辑
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

  }

可以看到,invoke方法的逻辑如下

PoolState

认识玩了PooledConnection之后,下面我们认识PoolState,PoolState就是PooledDataSource里面的连接池

在这里插入图片描述
在这里插入图片描述
下面就来分析一下PoolState的成员属性,除了连接池之外,也有着很多的统计数据

可以看到,PoolState维护了一系列的统计数据,而且PoolState仅仅只是拥有这些资源的一个封装,剩下的方法都是get方法而已

这个连接池是所有用户线程都会使用的,因此会产生一系列的并发问题,在PoolState里面,主要是使用Synchroniced来保证并发安全的

在这里插入图片描述

PooledDataSource的其他可选信息

在这里插入图片描述
可以看到,PooledDataSource还有着一系列的可选信息,比如之前我们提到的连接池的总连接数、最大的空闲连接数等。。。。。

UnpooledDataSource

从PooledDataSource的成员属性中,我们可以看到其还组装了UnpooledDataSource,我个人理解为这里采用了装饰器模式,给UnpooledDataSource加上了连接池的功能,从而形成了新的UnpooledDataSource

下面就来看看PooledDataSource的一个关键点,如何从连接池中获取连接

在这里插入图片描述

获取Connection连接

对应的方法是getConnection方法,而getConnection方法通过popConnection方法获取连接池中的PooledConnection,然后再获取代理的Connection的代理对象(ProxtConnection)去进行操作,所以关键点在于popConnection是如何从连接池中获取的

源码如下

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    //conn用来承载获取到的PooledConnection
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;
	//循环去进行获取PooledConnection
    //循环去获取PooledConnection,直到获取成功,获取获取失败超过一定次数抛错
    while (conn == null) {
        //对state进行上锁
        //保证了只有一个线程可以从state中获取连接
      synchronized (state) {
          //判断是否有空闲连接池中是否有空闲连接
        if (!state.idleConnections.isEmpty()) {
            //如果有空闲连接,弹出第一个空闲连接
            //注意这里!
            //使用的是remove方法,弹出了之后是要进行维护的
            //既将后面的空闲连接给移动上来
            //因此过大的空闲连接是否耗费性能的
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // 如果没有空闲的连接
          //判断当前正在激活的Connection有没有超过允许的最大激活Connecion数量
            //说白了就是判断当前正在进行的连接有没有超过设置的最大连接数阈值
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // 如果没超过,使用UnPooledDataSource去创建新的Connection
              //然后为该新的Connection创建代理对象
              //封装进PooledConnection里面
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // 如果超过了设置的最大连接阈值,就不能去新建了
              //获取最老的一个正在使用的连接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
              //判断最老的连接是不是已经执行超时了
              //这里的超时是指超过了定义的CheckoutTime
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
                //如果已经超时了就要进行移除
              // 维护poolState的统计信息
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
                //超时了,从连接池中进行移除
              state.activeConnections.remove(oldestActiveConnection);
                //判断该Connection有没有开启自动提交
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                    //如果没有开启自动提交,执行回滚
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  log.debug("Bad connection. Could not roll back");
                }
              }
                //重新创建PooledConnection
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                //设置PooledConnection的一些时间属性
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                //将最老的连接改为失效
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } 
              //如果最老的连接都没有超过定义的Checkout时间
              //那就必须要进行等待了
              else {
              // 进行等待
              try {
                  //等待一次
                if (!countedWait) {
                    //维护PoolState的一些属性
                  state.hadToWaitCount++;
                    //不等了
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                  //
                long wt = System.currentTimeMillis();
                  //PoolState执行wait方法进行等待,并且等待的时间为设置的等待时间
                  //这里关键点在于进入wait方法是会释放锁的!
                  //默认为20000毫秒,等待20S?????
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
          //如果经过上面的步骤可以拿到连接
        if (conn != null) {
          // 对连接进行校验,isValid方法里面会进行ping
          if (conn.isValid()) {
              //在使用连接之前,将里面的事务回滚掉!
              //回滚掉会比较稳健。。。。
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
              //对PooledConnection进行一些属性设置
            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;
          } 
            //如果校验失败,也就是ping不同,执行测试SQL失败
            else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
                //维护信息
            state.badConnectionCount++;
            localBadConnectionCount++;
                //让con = null,让外部循环重新去获取!
            conn = null;
                //判断ping测试失败次数,超过了指定的失败次数要进行抛错
            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.");
    }
	//返回得到得到Connection
    return conn;
  }

大概的流程如下

获取连接的大概过程看懂了,现在来看一下细节

校验连接

对应的方法就是isValid方法

在这里插入图片描述
可以看到,要valid为true、realConnection不为null,并且交由dataSource.pingConnection成功才能代表校验成功,关键在于pingConnection操作

源码如下

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;

    try {
        //判断真实的连接关闭了没有
        //代理对象都是使用realConnection去执行操作的
        //关闭了result就会为false
        //只要没关闭才会为true
      result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
      result = false;
    }
	//如果连接未关闭,并且开启了测试校验,并且超过了设定的进行校验的相隔时间
    //就需要进行SQL测试校验
    if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
        && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
      try {
        if (log.isDebugEnabled()) {
          log.debug("Testing connection " + conn.getRealHashCode() + " ...");
        }
          //使用当前连接去执行poolPingQuery,也就是测试SQL
        Connection realConn = conn.getRealConnection();
        try (Statement statement = realConn.createStatement()) {
          statement.executeQuery(poolPingQuery).close();
        }
          //如果是自动提交的,进行回滚
          //保证了测试SQL不会影响数据
        if (!realConn.getAutoCommit()) {
          realConn.rollback();
        }
          //测试SQL通过,让result为true
        result = true;
        if (log.isDebugEnabled()) {
          log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
        }
      } catch (Exception e) {
        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;
  }

可以看到,Ping校验并不是一定会开启的,必须要连接未关闭(如果关闭了还测试个鬼咩),并且开启了测试SQL功能,还要达到时间条件(也就是达到一定时间间隔才执行测试)

等待获取连接

还有一个关键点在于PoolState是如何进行等待操作的

在这里插入图片描述
在这里插入图片描述
可以看到,PoolState是直接调用了Object的wait方法,让当前线程进入了等待状态。。。。。。

dataSource.pushConnection

那问题来了,线程一定会等待到指定时间吗?此时如果空闲连接池有空闲连接了不能直接拿吗?

还记得代理Connection对象的PooledConnection吗?在它的invoke方法会拦截Close方法,并且会执行dataSouece.pushConnection方法,如下表示

在这里插入图片描述
下面就来看看这个pushConnection的逻辑,因为空闲连接是从这里产生的,因此很有可能在这里唤醒线程取进行争抢,源码如下

 protected void pushConnection(PooledConnection conn) throws SQLException {
	//对连接池进行上锁,因此释放连接也是只能一个线程一个线程去进行释放
    synchronized (state) {
        //首先从活跃连接池中进行释放该连接
      state.activeConnections.remove(conn);
        //判断其是否仍然可以通过校验了
        //前面以及看过isValid方法了,是有可能会进行Ping操作的
        //因此在释放前也会判断该连接是否可用
      if (conn.isValid()) {
          //如果此时的空闲连接数小于定义的最大空闲连接数
          //并且expectedConnectionTypeCode可以对应上
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
            //同意进行释放
            //更新CheckoutTime
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
            //如果不是自动提交,将里面的事务回滚
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
            //可以看到,原先的PooledConnection代理对象被抛弃掉了,转而去创建新的代理对象
            //但是!被代理的RealConnection却还是同一个
          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锁然后去操作了
          state.notifyAll();
        } else {
            //如果超过了允许的空闲连接最大数量,就需要抛弃掉这个连接了
            //更新连接池信息
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
            //不是自动提交就进行回滚
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
            //关闭旧的RealConnection
          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.");
        }
          //如果ping失败了,既归还的连接不能使用
          //然后什么都不做???单纯移出活跃连接池?
        state.badConnectionCount++;
      }
    }
  }

下面总结一下整个归还步骤

强制关闭所有连接

在PooledDataSource里面还有一个重要的操作,就是强制关闭所有连接,什么时候进行强制关闭所有连接呢?

当修改了PooledDatSource的字段时,比如数据库URL、用户名、密码、autoCommit等配置(执行其Set方法),都会调用forceCloseAll方法将所有的数据库连接关闭,同时也会将所有相应的PooledConnection对象都设置为无效,并且清空连接池,包括活跃连接池与空闲连接池

在这里插入图片描述
可以看到,set方法之后都会执行forceCloseAll方法

forceCloseAll

该方法源码如下

public void forceCloseAll() {
    //对PooledState进行上锁
    synchronized (state) {
        //生成新的expectedConnectionTypeCode
      expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
        //遍历活跃连接池
      for (int i = state.activeConnections.size(); i > 0; i--) {
        try {
            //将当前连接移出活跃连接池
          PooledConnection conn = state.activeConnections.remove(i - 1);
            //改为失效
          conn.invalidate();
			//进行回滚
          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
            realConn.rollback();
          }
            //关闭
          realConn.close();
        } catch (Exception e) {
          // ignore
        }
      }
        //遍历空闲连接池
      for (int i = state.idleConnections.size(); i > 0; i--) {
        try {
            //将当前连接移出空闲连接池
          PooledConnection conn = state.idleConnections.remove(i - 1);
            //改为失效
          conn.invalidate();
		//进行回滚
          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
            realConn.rollback();
          }
          realConn.close();
        } catch (Exception e) {
          // ignore
        }
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("PooledDataSource forcefully closed/removed all connections.");
    }
  }

可以看到,强制关闭所有连接就是对活跃连接池和空闲连接池进行处理

至此,PooledDataSource也认识完了

总结一下

标签:空闲,PooledConnection,连接池,Connection,NeiMu,DataSource,MyBatis,连接,conn
来源: https://blog.csdn.net/GDUT_Trim/article/details/121457045