Mybatis连接池及事务

一:Mybatis连接池

我们在学习WEB技术的时候肯定接触过许多连接池,比如C3P0、dbcp、druid,但是我们今天说的mybatis中也有连接池技术,可是它采用的是自己内部实现了一个连接池技术,我们可以在mybatis的SqlMapConfig.xml配置文件中,通过设置<dataSource type="POOLED">标签来实现mybatis中的连接池配置。

1:Mybatis连接池种类

我们可以把Mybatis连接池分为三类:

UNPOOLED  :不使用连接池技术

POOLED    :使用连接池技术

JNDI      :使用JNDI实现数据源 

Mybatis内部实现了java.sql.DataSource接口的UnpooledDataSource和PooledDataSource两个类分别来表示UNPOOLED、POOLED类型的数据源

从上图可以看出UnpooledDataSource和PooledDataSource两个类都实现了java.sql.DataSource接口,但注意的是PooledDataSource类里面会聚合一个UnpooledDataSource引用对象,因为PooledDataSource需要创建java.sql.Connection的连接数据库对象的时候,它是调用非连接池对象UnpooledDataSource来创建,说白了创建是UnpooledDataSource,而PooledDataSource只是提供了一个缓存连接池机制

所以我们在这三种数据源中,一般采用POOLED连接池技术,因为这个可以更好的管理数据源连接,防止来回创建浪费时间,而JNDI技术我会后期会单独抽时间写一篇博文,它主要是针对web架构的工程使用的连接池技术

2:Mybatis中的SqlMapConfig.xml配置数据源

<dataSource type="POOLED">
     <!--四大配置 driver驱动 url数据库地址 username/password账号密码-->
     <property name="driver" value="${driver}"/>
     <property name="url" value="${url}"/>
     <property name="username" value="${username}"/>
     <property name="password" value="${password}"/>
</dataSource>
<!--
mybatis在初始化的时候会根据<dataSource>中的type属性来创建相应的数据源DataSource
type="POOLED":MyBatis 会创建 PooledDataSource 实例
type="UNPOOLED" : MyBatis 会创建 UnpooledDataSource 实例
type="JNDI":MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
我上面配置的是POOLED使用连接池数据源技术
-->

3:项目搭建

快速搭建一个Mybatis小框架

二:UNPOOLED无连接池原理分析

我们首先来把配置文件下的DataSource标签的type属性改为UNPOOLED不使用连接池数据源,然后运行一下测试类的update方法测试

 可是现在好奇它的内部是什么样的呢?首先我说一下,其实Mybatis是通过工厂模式来创建数据源DataSource对象的,(注:这个工厂模式只实现UNPOOLED和JNDI两种)那个抽象工厂是:org.apache.ibatis.datasource.DataSourceFactory,它里面有2个抽象方法

#### org.apache.ibatis.datasource.DataSourceFactory 源码
import
java.util.Properties; import javax.sql.DataSource; public interface DataSourceFactory { void setProperties(Properties props); //获取指定的数据源创建对象 DataSource getDataSource(); }

那我们这一节讲的是 不使用连接池UnpooledDataSource,其实DataSourceFactory下面有UnpooledDataSourceFactory实现子类

 让我们进入它的实现类看看吧,然后就可以知道它是怎么返回数据源创建对象的

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();
  //聚合的DataSource
  protected DataSource dataSource;
  //构造函数
  public UnpooledDataSourceFactory() {
  //这不正是使用UnpooledDataSource的实例吗?没错就是   那应该也有返回这个实例的方法
    this.dataSource = new UnpooledDataSource(); 
  }
  ......此处省略一部分源码

  //看啦  在这耶  这个就是返回去一个实例  可以看成是DataSource ds=new UnpooledDataSource()
  @Override
  public DataSource getDataSource() {
    return dataSource;
  }
 ...... 此处省略一部分代码
}
UnpooledDataSourceFactory源码分析

可是话说回来,创建后返回到哪了呢?还是直接就使用了? 带着疑问往下走....其实MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment 对中, 供以后使用,,大家可以先进入到org.apache.ibatis.builder.xml.XMLConfigBuilder类中,然后在return configuration ;处打一个断点 ,,然后分析源码

public class XMLConfigBuilder extends BaseBuilder {
 private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

.... 此处省略一部分代码

 //对信息的设置
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration()); //创建Configuration对象
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;  设置数据源信息
    this.parser = parser;
  }

 //返回
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

.... 此处省略一部分代码
}
org.apache.ibatis.builder.xml.XMLConfigBuilder源码分析

 从上面源码可以分析出它返回了一个Configuration对象,里面存放一个Environment对象属性,但这里面是啥呢?

   分析到这里后DataSourceFactory也就结束了,顺便说一下,这个是在mybatis的SqlMapConfig.xml一加载就会创建具体的对象Configuraction对象,然后把读取配置文件的<environments>标签封装一个environment对象存放到Configuraction里面,具体的org.apache.ibatis.session.Configuration类可以大家自己查看

已经分析到这了,一些配置也封装到了对象里面,而且DataSourceFactory工厂类的子类UnpooledDataSourceFactory也创建了UnPooledDataSource无连接池对象(根据UNPOOLED来解析创建此对象的),那什么时候调用这个datasource对象呢?

当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候,(如执行到:sqlSession.selectList("xxxxxxxxx",xx),才开始执行)接下来让我们来解析一下UnpooledDataSource类源码吧!
###### org.apache.ibatis.datasource.unpooled.UnpooledDataSource源码分析

public class UnpooledDataSource implements DataSource {
  
  private ClassLoader driverClassLoader;
  private Properties driverProperties;
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();

  //连接数据库的4的配置属性
  private String driver;
  private String url;
  private String username;
  private String password;

  //自动提交模式
  private Boolean autoCommit;
  //事务隔离级别
  private Integer defaultTransactionIsolationLevel;

  ...... 此处省略一部分源码

  //①:返回Connection对象
  @Override
  public Connection getConnection() throws SQLException {
    return doGetConnection(username, password); //调用此方法设置用户及密码
  }

  ...... 此处省略一部分源码

  //②:先对Connection对象进行用户密码设置
  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); //调用此方法对Connection完善
  }

  //③:对Connection对象完善  然后直接返回Connection对象
  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver(); //调用此方法完成注册驱动
    Connection connection = DriverManager.getConnection(url, properties); //获取Connection连接啦
    configureConnection(connection); //调用方法来设置事务隔离级别和自动提交模式
    return connection; //最终返回一个Connection完成了
  }

  //④:这不就是我们平常注册驱动吗  什么Class.forName(....)
  private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          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));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

  //⑤:设置事务隔离级别和自动提交模式
  private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      //将此连接的自动提交模式设置为给定状态。
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      //试图将此 Connection 对象的事务隔离级别更改为给定的级别。
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }
  
  ...... 此处省略一部分源码
}
org.apache.ibatis.datasource.unpooled.UnpooledDataSource源码分析

 到这里我就把UnpooledDataSource的源码解释了一下,也就结束了不使用连接池的配置,要深入剖析代码请看我后期更新

三:POOLED带有连接池配置的源码分析

我们首先来把配置文件下的DataSource标签的type属性改为UNPOOLED不使用连接池数据源,然后运行一下测试类的update方法测试

  在学完UnpooledDataSource无连接池类后,我们就要开始谈谈带连接池的PooledDataSource类了,它们这2个类的getConnection都是返回Connection连接对象,现在我们就看一下它的源码。

在说PooledDataSource类之前呢,还得先解释一下两个辅助类,协同辅助PooledDataSource类,分别是PooledConnection和PoolState

①:辅助类 PoolState

此类用来记录连接池运行时的状态,如连接获取次数,无效连接数量等,同时里面还定义了两个ArrayList<PooledConnection>集合,用来存储空闲连接和活跃连接

#### org.apache.ibatis.datasource.pooled.PoolState 源码分析
/**
 * 其实这个类就可以说是一个javaBean对象,对一些数据进行了封装
 * 对外界提供get/set/toString 等等方法
 */

public class PoolState {

  //聚合对象 PooledDataSource
  protected PooledDataSource dataSource;

  // 空闲池连接列表
  protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  // 活跃池连接列表
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
  // 从连接池中获取连接的次数
  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;

  ...... 后面代码省略
}

②:辅助类  PooledConnection

此类的内部定义定义了两个Connection类型的变量,有用于指向真实数据库连接(realConnection)的,还有一个Connection代理类的数据库连接(proxyConnection),用于对部分方法调用进行拦截。其它的内部字段也就是用于记录数据库连接的一些运行时状态,注意的是Connection连接被封装到此类里面了!!!

#### org.apache.ibatis.datasource.pooled.PoolState 源码分析

/**
 * InvocationHandler接口的实现  说明这个类是一个代理类对象
 */
class PooledConnection implements InvocationHandler {

  private static final String CLOSE = "close";
  // 这个参数是用来传入代理模式的参数 及抽象类的接口方法数组
  private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

  private final int hashCode;
  // 聚合 PooledDataSource对象
  private final PooledDataSource dataSource;

  // 真实的数据库连接
  private final Connection realConnection;
  // 数据库连接代理
  private final Connection proxyConnection;
  // 从连接池中取出连接时的时间戳
  private long checkoutTimestamp;
  // 数据库连接创建时间
  private long createdTimestamp;
  // 数据库连接最后使用时间
  private long lastUsedTimestamp;
  // connectionTypeCode = (url + username + password).hashCode()
  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;
    // 创建 Connection 的代理类对象
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }

  ...... 省略部分代码

  //代理执行的方法 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      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);
      }
    }
  }
  ...... 省略部分代码
}
org.apache.ibatis.datasource.pooled.PoolState 源码分析

 在说完上面的2个辅助类之后(上面2个类有很多字段用来记录运行状态的,知道每个字段意思即可)终于可以来解剖PooledDataSource,这个类是一个带有连接池的思想的类,它会将用过的连接进行回收,反复利用。正常情况下去PooledDataSouce获取连接会给我们返回一个空闲的连接对象,那假设,默认的5个最大空闲连接都在活动状态(就是没用空闲的连接的),那我们该如何去获取呢?这个时候就去和我看一下源码吧!

##### org.apache.ibatis.datasource.pooled.PooledDataSource源码分析

public class PooledDataSource implements DataSource {

  private static final Log log = LogFactory.getLog(PooledDataSource.class);

  // 组合一个PoolState    这就是存放连接的  也可理解为池子
  private final PoolState state = new PoolState(this);

  // 聚合了UnpooledDataSource 就是之前介绍的类  直接创建对象,然后返回对象,没有池思想
  private final UnpooledDataSource dataSource;

  // OPTIONAL CONFIGURATION FIELDS
  // 最大活动数量 10
  protected int poolMaximumActiveConnections = 10;
  // 最大空闲数量 5
  protected int poolMaximumIdleConnections = 5;
  // 最大池清理时长 20000毫秒
  protected int poolMaximumCheckoutTime = 20000;
  // 等待池时间 20000毫秒
  protected int poolTimeToWait = 20000;
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  protected String poolPingQuery = "NO PING QUERY SET";
  protected boolean poolPingEnabled;
  protected int poolPingConnectionsNotUsedFor;

  private int expectedConnectionTypeCode;

  ...... 此处省略一部分源码

  @Override
  public Connection getConnection() throws SQLException {
    // 返回Connection的代理对象
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

  ...... 此处省略一部分源码
 
  //重要部分 获取池连接
  private PooledConnection popConnection(String username, String password) throws SQLException {
    // 初始化数据
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    //判断PooledConnection 为空
    while (conn == null) {
      // 同步代码 锁为那个存放连接池的对象实例
      synchronized (state) {
        //判断那个空闲连接池集合是否为空
        if (!state.idleConnections.isEmpty()) {
          // 不为空  直接获取PooledConnection就ok了
          conn = state.idleConnections.remove(0);
          // 打印日志
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          /**
           *  可是 运行到这里 如果没有空闲连接可用,但是呢,
           *  那个活跃连接还未超出限制 那就会重写创建一个新的连接
           */
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            
            // 创建一个新的连接  这个创建是交给UnpooledDataSource的getConnection方法完成的
            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();
            // 判断运行时常是否超出限制  及超时 poolMaximumCheckoutTime默认是20000毫秒
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // 累超时相关的统计字段
              state.claimedOverdueConnectionCount++;
              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");
                }  
              }

              /**
               * 通过上面的一个else 运行到这里后,活跃连接池的数量就被减去了一个
               * 到这里我们就要创建一个新的PooledConnection对象了,可是得睁大眼睛
               * 创建这个对象正是上一个else被移除的运行时间最长的对象,
               * 我们不能丢弃这个oldestActiveConnection这个对象,所以再次复用
               */
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              /**
               * 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的createdTimestamp 
               * 用于记录 Connection 的创建时间,而非 PooledConnection 的创建时间。
               * 所以这里要复用原连接的时间信息。
               */
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

              // 设置连接为无效状态
              oldestActiveConnection.invalidate();
              // 打印日志
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              /**
               * 运行到这一步的都是王哈哈 这时候活跃连接池的运行时间最长的连接并未超出20000毫秒
               */
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  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;
              }
            }
          }
        }
        /**
         * 如果PooledDataSource不为空 ,
         * 那么就检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,
         * 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,检测连接是否可用。
         */
        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++;
            localBadConnectionCount++;
            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;
  }

  // 检查PooledConnection是否可用 用来测试SQL语句执行
  protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;

    try {
      result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
      result = false;
    }

    if (result) {
      if (poolPingEnabled) {
        if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
          try {
            if (log.isDebugEnabled()) {
              log.debug("Testing connection " + conn.getRealHashCode() + " ...");
            }
            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) {
            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;
  }

}
##### org.apache.ibatis.datasource.pooled.PooledDataSource源码分析
 1 if(连接池中有空闲连接){
 2   直接获取空闲连接返回
 3 }else{
 4 
 5   if(活跃连接池还没有超出限制){
 6     创建一个新的连接返回
 7   }else{
 8     从活跃的连接池中获取第一个元素
 9     获取其连接的运行时长
10 
11     if(连接时长超时默认20000){
12       将连接从活跃集合中移除
13       创建新的PooledConnection对象,并设置原连接成员变量
14     }else{
15       线程进入等待状态
16       线程被唤醒后,重写执行以上逻辑
17     }
18   }
19 }

 其实在解析完PooledDataSource源码后,还有一个回收连接在等着我们,它相比那个popConnection源码要简单的多,回收连接的成功与否,主要是取决空闲连接的状态

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
          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.");
          }
          //通知等待的线程 在popConnection如果都不满足线程就会等待 这里正好解除
          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++;
      }
    }
  }
pushConnection回收连接方法解析

  上面代码首先将连接从活跃连接集合中移除,然后再根据空闲集合是否有空闲空间进行后续处理。如果空闲集合未满,此时复用原连接的字段信息创建新的连接,并将其放入空闲集合中即可。若空闲集合已满,此时无需回收连接,直接关闭即可。

  在进行了popConnection获取和pushConnection回收的两个方法解析完之后,可以知道popConnection方法是由getConnection方法调用的,那么问题来啦,这个pushConnection方法是谁调用呢?先来和我分析一段代码

/**
 * 大家还记得这个方法吗 invoke方法,这个只有实现InvocationHandler接口才
 * 可以成为代理对象  所以PooledConnection里面有个invoke方法 这里面调用了
 * pushConnection方法用来回收连接
 */
@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);
      return null;
    } else {
      try {
        if (!Object.class.equals(method.getDeclaringClass())) {
          checkConnection();
        }
        // 调用真实连接的目标方法
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

前面我们说过getConnection 方法返回的是 Connection 代理对象,不知道大家有没有注意到。代理对象中的方法被调用时,会被上面的代理逻辑所拦截。如果代理对象的 close 方法被调用,MyBatis 并不会直接调用真实连接的 close 方法关闭连接,而是调用 pushConnection 方法回收连接。同时会唤醒处于睡眠中的线程,使其恢复运行。

1:总结

到这里已经解析了UNPOOLED和POOLED的2个配置上的源码分析,但是还有一个JNDI数据源还未解剖源码,我会通过之后的时间会出一篇关于JNDI数据源解析

四:Mybatis事务控制

1:回顾JDBC事务

在当初懵懂的时候,大家是否用到过JDBC为我们提供的setAutoCommit(boolean autoCommit)方法呢?这个方法是可以将自动提交方式改为手动方式

setAutoCommit

void setAutoCommit(boolean autoCommit)throws SQLException
将此连接的自动提交模式设置为给定状态。如果连接处于自动提交模式下,则它的所有 SQL 语句将被执行并作为单个事务提交。否则,它的 SQL 语句将聚集到事务中,直到调用 commit 方法或 rollback 方法为止。默认情况下,新连接处于自动提交模式。

提交发生在语句完成时。语句完成的时间取决于 SQL 语句的类型:

  1. 对于 DML 语句(比如 Insert、Update 或 Delete)和 DDL 语句,语句在执行完毕时完成。
  2. 对于 Select 语句,语句在关联结果集关闭时完成。
  3. 对于 CallableStatement 对象或者返回多个结果的语句,语句在所有关联结果集关闭并且已获得所有更新计数和输出参数时完成。

注:如果在事务和自动提交模式更改期间调用此方法,则提交该事务。

  如果调用 setAutoCommit 而自动提交模式未更改,则该调用无操作(no-op)。

参数:
autoCommit - 为 true 表示启用自动提交模式;为 false 表示禁用自动提交模式
抛出:
SQLException - 如果发生数据库访问错误,在参与分布式事务的同时调用 setAutoCommit(true),或者在关闭的连接上调用此方法

 那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的setAutoCommit()方法来设置事务提交方式的。

2:Mybatis中的事务提交方式

 其实mybatis中的事务提交方式,本质上就是调用JDBC的setAutoCommit(boolean autoCommit)来实现事务控制,我们现在回到自己的之前搭建的项目,在测试类里面找到update的测试方法,把sqlSession.commit();这个给删除,然后运行update方法

 

 但是当我们把sqlSession.commit();方法加上就会又变另一种效果,它不在回滚jdbc事务

 那有人就会问,那sqlSession.commit();底层是怎么实现的呢?说到这我就给大家来分析一下它的执行过程吧

3:Mybatis中commit方法源码详解

 

 分析到这里也就结束了,后期针对这些难点的技术会详细出单篇文章

posted @ 2020-04-25 11:11  蚂蚁小哥  阅读(697)  评论(0编辑  收藏  举报