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对象进行了代理。
- Mybatis源码分析之SqlSessionFactory,SqlSession和连接池
- Mybatis源码分析(二)- SqlSessionFactory和SqlSession详解
- mybatis源码学习之执行过程分析(1)——SqlSessionFactory及SqlSession的创建
- Mybatis - 获取SqlSessionFactory和SqlSession
- mybatis的探索过程之SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession作用域和生命周期
- sqlsession和sqlsessionFactory区别
- SqlSessionFactory和Sqlsession
- MyBatis常用对象SqlSessionFactory和SqlSession介绍和运用
- MyBatis--SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession作用域和生命周期
- myBatis源码学习之SqlSessionFactory
- myBatis源码学习之SqlSession
- mybatis之入门到开发(三)之常用对象SqlSessionFactory和SqlSession,动态sql,输入映射和输出映射
- MyBatis 源码分析——SqlSession接口和Executor类
- SqlSessionFactory创建SqlSession测试mybatis的sql
- Mybatis学习笔记--SqlSessionFactory、SqlSession等
- MyBatis源码分析(三)-SqlSession理解
- mybatis源码阅读之SqlSessionFactory创建
- (四)MyBatis源码解析之SqlSession
- Android 传参需要List/Array,用json转换一下
- javascript高级程序设计读书笔记-第二章
- LIS 百练2757 LCS UVA10405
- 2017年蘑菇街暑期算法实习生一面+二面
- C++重载运算符详解
- Mybatis源码分析之SqlSessionFactory,SqlSession和连接池
- R语言扩展包dplyr
- ionic 中tab切换时出现一闪而过白屏
- 【javaWeb】sendRedirect和forward原理及区别总结
- opencv-package 'eigen3' not found
- javascript java switch
- C/C++ tips
- APP全栈工程师修炼之路(二)
- DSYMTool分析工具