Mybatis源码分析之SqlSessionFactory,SqlSession和连接池

简单介绍下mybatis获取SqlSession和进行sql操作的例子

InputStream inputStream = Resources.getResourceAsStream(resource);  
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
SqlSession sqlSession = sqlSessionFactory.openSession();  
sqlSession.selectList("SQL");  

本文主要从上面代码中分析Mybatis的实现源码
首先是sqlSession的获取,时序图如下:
Mybatis源码分析之SqlSessionFactory,SqlSession和连接池_第1张图片
第一步首先SqlSessionFactoryBuilder去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory
关键代码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //然后返回一个DefaultSqlSessionFactory
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

第二步,获取到SqlSessionFactory之后,就可以利用SqlSessionFactory方法的openSession来获取SqlSession对象了。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
    // execType为执行器类型,配置文件中定义
    // SimpleExecutor -- SIMPLE 就是普通的执行器。
    //ReuseExecutor -执行器会重用预处理语句(prepared statements)
    //BatchExecutor --它是批量执行器
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //定义执行器,是对statement的封装
    final Executor executor = configuration.newExecutor(tx, execType);
    //最后返回一个SqlSession
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

得到SqlSession对象之后就可以利用SqlSession内部的方法进行CRUD操作了。

注意一点,Connection对象是在SqlSession对象创建之后进行CURD操作中创建的。深入查找之后找到在ManagedTransaction类中找到获取Connection对象的关键代码如下:

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //dataSource 来源有三种,JndiDatasource,PooledDataSource,UnpooledDataSource,配置文件中定义
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }

PooledDataSource和UnPooledDataSource的区别是PooledDataSource使用了连接池。为什么使用连接池呢?因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接,在建立通信连接的过程,消耗了非常多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!mybatis针对这一个问题提出的PooledDataSource使用了连接池。

本文主要介绍下使用连接池的PooledDataSource

简单介绍下几个概念:IdelConnections,ActiveConnections
PooledDataSource把java.sql.connection对象封装成PooledConnection对象放到PoolState类型的容器中进行维护,Mybatis把PooledConnection对象分为两种状态,空闲状态(idle)和活动状态(active),这两种状态的PooledConnection对象分别放在PoolState容器的idleConnections和activeConnections两个list中。
IdleConnections:空闲状态的PooledConnection放在次集合中,调用getConnection方法时,优先从该集合中获取PooledConnection对象,当连接结束时,Mybatis会把该对象放到该集合中。

ActiveConnections:激活状态的PooledConnection对象放在次集合中,表示当前正在使用的PooledConnection对象,调用getConnection方法时,首先看idleConnections集合是否不为空,如果没有对象,然后看此集合是否已满,不满的话,会新生成一个对象放到此集合中。

首先介绍下PooledConnection对象的获取过程,关键代码是PooledDataSource中的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.size() > 0) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // Pool does not have available connection
          // 空闲列表中不为空,需要重新创建一个PooledConnection对象,先判断激活列表是否已满,不满可以创建
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            conn = new PooledConnection(dataSource.getConnection(), this);
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // 激活列表已满,需要查看最新进入激活列表的PooledConnection对象是否已过期
            // Cannot create new connection
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            // 如果已经过期,移除掉,并创建新的PooledConnection对象
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // 如果没有过期,只能继续等待
              // Must wait
              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;
              }
            }
          }
        }
        //成功获取连接,然后更新信息
        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++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
              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对象, 共经历了以下几个步骤
1:查看IdleConnections集合是否为空
合中是否有pooledConnection对象,如果有,直接返回一个可用的PooledConnection对象。没有进行第二步
2:查看activeConnections集合是否已满,如果没有满,则PooledDataSource新建一个PooledConnection对象,然后放到activeConnections集合中,然后返回该对象。如果已满则跳到第三步。
3:查看最先进入activeConnection集合的pooledConnection对象是否已经过期,如果已经过期,删除该对象,然后新建一个新的PooledConnection对象,然后放到该集合中,如果没有过期,则进行第四步
4:线程等待,然后跳转第二步。

连接过程如上,然后需要查看下数据库连接断开过程中Connection对象的操作:
一般情况,Connection对象调用close方法后会释放全部资源,然后Connection对象也就不能用了,使用连接池断开连接,不是直接释放资源,而是把Connection对象放到连接池中。
PooledDataSource使用动态代理模式,为真正的Connection对象进行代理,具体代理代码如下:

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())) {
        // 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);
    }
  }
}

从上面可以看出,调用close方法时,会调用pushConnection方法
pushConnection方法主要功能就是把PooledConenction从ActiveConnections列表中移除放到IdelConnections列表中,并修改一些状态信息。

以上就是连接池的一些源码信息。实际上MyBatis的PooledDataSource中的PoolState容器内部维护的对象是PooledConnection对象,而PooledConnection则是对真正的数据库连接java.sql.Connection实例对象的包裹器,对Conenction对象进行了代理。

你可能感兴趣的:(mybatis,mybatis,SqlSession,连接池)