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

来源:互联网 发布:淘宝店铺转让价格表 编辑:程序博客网 时间:2024/05/18 09:40

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

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

本文主要从上面代码中分析Mybatis的实现源码
首先是sqlSession的获取,时序图如下:
这里写图片描述
第一步首先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对象进行了代理。

1 0
原创粉丝点击