对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题。本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池。

本文首先会讲述MyBatis的数据源的分类,然后会介绍数据源是如何加载和使用的。紧接着将分类介绍UNPOOLED、POOLED和JNDI类型的数据源组织;期间我们会重点讲解POOLED类型的数据源和其实现的连接池原理。

本文结构如下:

  • 一、MyBatis数据源DataSource分类
  • 二、数据源DataSource的创建过程
  • 三、 DataSource什么时候创建Connection对象
  • 四、不使用连接池的UnpooledDataSource
  • 五、为什么要使用连接池?
  • 六、使用了连接池的PooledDataSource

一、Mybatis数据源分类

mybatis数据源实现类在mybatis的dataSource包中:

Mybatis将数据源分为三种:

JNDI 数据源 : 使用JNDI方式数据源

POOLED数据眼:  使用连接池数据源

UNPOOLED 数据源 : 不使用连接池数据源

即:

相应的Mybatis内部分别以实现  javax.sql.DataSource 接口的 PooledDataSource和 UnPooledDataSource 实现 POOLED和UNPOOLED数据源。(关于数据源创建细节请看下面章节)

JNDI数据源则通过 javax.naming.Context 上下文生成数据源。

二、数据源DataSource创建过程

数据源配置如下:

 <dataSource type="UNPOOLED"><property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/pmdb"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource>  

Mybatis数据源的创建过程:

Mybatis初始化阶段 就会创建好数据源,具体创建数据源的时机发生在解析mybatis XML配置文件<environments></environments>  节点下的<dataSource></dataSouorce>节点:

 //context :dataSource节点数据private DataSourceFactory dataSourceElement(XNode context) throws Exception {if (context != null) {//获取dataSource配置的类型 (POOLED、UNPOOLED、JNDI)String type = context.getStringAttribute("type");//将dataSource下的username、password等信息解析为PropertiesProperties props = context.getChildrenAsProperties();//根据dataSource的type类型(别名机制)获取到对应的DateSource实现类,并实例化该类DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a DataSourceFactory.");}

创建DataSource最关键的一步在:  DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();

打开该方法resolveClass方法实现,看它到底做了什么:

public <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;}String key = string.toLowerCase(Locale.ENGLISH);Class<T> value;if (TYPE_ALIASES.containsKey(key)) {value = (Class<T>) TYPE_ALIASES.get(key);} else {value = (Class<T>) Resources.classForName(string);}return value;} catch (ClassNotFoundException e) {throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);}}

resolveClass方法核心功能就是根据XML dataSource节点配置的type属性找到对应的实现类:

如上图所示:

根据配置的type别名找到Factory,然后创建出对应的DataSource

  • JNDI : JndiDataSourceFactory
  • POOLED: PooledDataSourceFactory
  • UNPOOLED: UnpooledDataSourceFactory

Mybatis创建DataSource之后会将其放在Configuration的Environment中,供以后使用。

三、DataSource什么时候创建Connection对象

  InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();User user = (User) sqlSession.selectOne("selectByPrimaryKey", 1);

如上图所示,前三行代码都不会去创建javax.sql.Connection,当执行到 selectOne("selectByPrimaryKey", 1);时,才会去真正创建Connection对象:

 @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException {Statement stmt = null;try {flushStatements();Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);Connection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

那对于UNPOOLED类型DataSource的实现UnpooledDataSource是怎么样实现getConnection方法呢?请看一下节。

四、不使用连接池的UnpooledDataSource

  //UnpoolDataSource 创建Connection对象
private Connection doGetConnection(Properties properties) throws SQLException {//1. 初始化驱动initializeDriver();//2. 创建Connection对象Connection connection = DriverManager.getConnection(url, properties);//3. 配置ConnectionconfigureConnection(connection);return connection;}

如上代码所示,流程如下:

  • 1. 初始化驱动 : 判断驱动是否加载到内存中,若有则直接取出,否则创建驱动
  • 2. 创建Connecion : 调用DriverManager创建相应的Connection对象
  • 3. 配置Connection: 配置Connection对象一些默认配置项
  • 4. 返回DataSource对象: 返回创建好的Connection对象供以使用

总结:从上述的代码中可以看到,我们每调用一次getConnection()方法,都会通过DriverManager.getConnection()返回新的java.sql.Connection实例。

五、为什么要使用连接池?

public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {long start = System.currentTimeMillis();Class.forName("com.mysql.jdbc.Driver");Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mall", "root", "123456");System.out.println("创建Connection对象耗时 : " + String.valueOf(System.currentTimeMillis() - start));String sql = "select *  from mmall_user where id = 1";start = System.currentTimeMillis();Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql);System.out.println("查询语句耗时:" + String.valueOf(System.currentTimeMillis() - start));while(resultSet.next()){System.out.println("id = " + resultSet.getInt(1) + "  username = " + resultSet.getString(2));}resultSet.close();statement.close();connection.close();}

创建Connection对象耗时 789ms, 而查询语句才耗时7ms.(不排除数据库数据少的原因,但是查询耗时一般不会超过789ms)

一次查询请求创建Connection对象耗时789ms。要知道100ms对于Java来说都是很奢侈的。(一个Connection对象耗时 700ms,10000 * 700 =  116分钟,10000次请求只创建对象就耗时116分钟,这是根本不能接受的)

所以使用连接池是非常有必要的。

六、使用了连接池的PooledDataSource

了解连接池之前,先了解两个参数概念:

idleConnections : 空闲Connection对象,当其他请求需要创建Connection时,直接到ideaConnection取出一个连接,可以减少资源、耗时。

activeConnections : 活动Connection对象,记录当前正在被请求所使用的Connection对象,当一次请求使用完一个Connection时,不将其立即销毁,而是放到idleConnection缓存池里面。

对于UnpooledDataSource每次请求都会创建一个新的Connection对象,当请求结束后会执行Connection.cloes()方法关闭该Connection.

PooledDataSource是如何创建的Connection的呢?

@Overridepublic Connection getConnection() throws SQLException {return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return popConnection(username, password).getProxyConnection();}

数据源type设置为 POOLED,当实例化DataSource时会根据别名实例化出 PooledDataSource对象。

当调用getConnection方法创建Connection时,最终会调用 popConnecion方法并返回一个代理对象。

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) {//判断连接池中是否还有空闲Connection对象,若有则直接返回一个Connectionif (!state.idleConnections.isEmpty()) {// Pool has available connectionconn = state.idleConnections.remove(0);if (log.isDebugEnabled()) {log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");}} else {//没有空闲Connection对象//当前活动对象个数是小于最大活动数量 则会生成一个新的Connection对象if (state.activeConnections.size() < poolMaximumActiveConnections) {// Can create new connectionconn = new PooledConnection(dataSource.getConnection(), this);if (log.isDebugEnabled()) {log.debug("Created connection " + conn.getRealHashCode() + ".");}} else {//判断老的活动对象是否超过poolMaximumCheckoutTime时间PooledConnection oldestActiveConnection = state.activeConnections.get(0);long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();//超过poolMaximumCheckoutTime时间,则尝试结束该Connection对象线程,并返回重用Connectionif (longestCheckoutTime > poolMaximumCheckoutTime) {// Can claim overdue connectionstate.claimedOverdueConnectionCount++;state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;state.accumulatedCheckoutTime += longestCheckoutTime;state.activeConnections.remove(oldestActiveConnection);if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {try {oldestActiveConnection.getRealConnection().rollback();} catch (SQLException e) {/*Just log a message for debug and continue to execute the followingstatement like nothing happend.Wrap the bad connection with a new PooledConnection, this will helpto not intterupt current executing thread and give current thread achance to join the next competion for another valid/good databaseconnection. At the end of this loop, bad {@link @conn} will be set as null.*/log.debug("Bad connection. Could not roll back");}  }conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());oldestActiveConnection.invalidate();if (log.isDebugEnabled()) {log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");}} else {//没有超时,则等待该Connection线程结束// Must waittry {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) {// ping to server and check the connection is valid or notif (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;}

综上所述,大致流程如下:

  • 1. 若IdleConnection中是否有空闲的连接对象,则直接返回Connection
  • 2. 判断当前activeConnection数量是否小于poolMaximumActiveConnections(活动连接最大数量),若小于,则和Unpooled方式一样创建新的Connection对象并返回。若大于,则会判断当前所有活动连接的占用时间是否超时,若超时则停止该Connection,并直接返回供其他请求使用。若没有超时,则等待Connection使用完毕后再返回。

连接池对Connection.close()的处理:

PooledDataSource中除了popConnection方法,还有一个pushConnection方法pushConnection方法会将使用完毕的Connection放入idleConnections缓存池中,供其他请求继续使用。传统的jdbc连接使用完Connection之后,会手动执行Connection.cloes()方法关闭连接。Pooled连接池为了重复利用Connection减少不必要的开销,对Connection.cloes做了动态代理。也就是说,在Pooled模式下,若我们手动执行connecion.cloes(),实际上并不会执行原生Connection.close方法。而是通过PooledConnection对原生Connection做动态代理,把close方法映射到 pushConnection方法上:@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();//若执行close方法,实际上会代理执行pushConnection方法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 RuntimecheckConnection();}return method.invoke(realConnection, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}}

以上就是本文 《深入理解Mybatis原理》 02-Mybatis数据源与连接池 的全部内容,

上述内容如有不妥之处,还请读者指出,共同探讨,共同进步!

@author : jackcheng1117@163.com

《深入理解Mybatis原理》 02-Mybatis数据源与连接池相关推荐

  1. mybatis 原理_深入理解MyBatis原理 MyBatis数据源与连接池

    点击上方"程序开发者社区"关注,选择"设为星标" 第一时间送达实用干货 对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文 ...

  2. 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...

  3. 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

    MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...

  4. 《深入理解mybatis原理》 MyBatis缓存机制的设计与实现

    本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论. MyBatis将数据缓存设计成两级结构,分为一级缓存 ...

  5. 深入理解Spring Boot数据源与连接池原理

    ​ Create by yster@foxmail.com 2018-8-2 一:开始 在使用Spring Boot数据源之前,我们一般会导入相关依赖.其中数据源核心依赖就是spring‐boot‐s ...

  6. 《深入理解mybatis原理三》 Mybatis数据源与连接池

    对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池. 本文首先会讲述MyB ...

  7. 《深入理解mybatis原理》 MyBatis的一级缓存实现详解 及使用注意事项

    MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本 ...

  8. Mybatis | Mybatis-plus配置多数据源,连接多数据库

    文章目录 前言 业务逻辑 使用Mybatis实现 使用Mybatis-plus实现 前言 ​ 工作的时候,遇到了需要将一个数据库的一些数据插入或更新到另一个数据库.一开始使用insert into T ...

  9. SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件

    我们这一一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池的好处我 ...

最新文章

  1. layui遍历json数组_shell脚本:json格式化与字段抓取(下)
  2. oracle 序列的使用
  3. SpringCloud系列之服务消费Ribbon和Feign区别
  4. linux连接教程视频,[原创]linux视频教程之连接
  5. 会话技术Cookie
  6. matlab保存colormap失败
  7. (BookxNote Pro)Windows版Marginnote 3 阅读神器 自动生成脑图/思维导图
  8. 《剑指offer》面试题27——二叉搜索树与双向链表(C++)
  9. 通达信指标加密DLL加密解密三个公式源码准确率90%以上超级指标精准买卖绝世指标
  10. 成本360元的迷你物联网服务器有多香?
  11. 面向对象编程中的 诡异事件
  12. 基于Tomcat的MQ学习月记
  13. 在微信开发者工具导入整个weui的实例,查看weui的用法
  14. 在Windows 7下删除注册表项时,权限不足
  15. IP-guard功能详解——安全U盘
  16. 微信小程序的悬浮按钮
  17. php手机座机验证,JS校验手机号 座机 邮箱 微信号
  18. 【在线工具】在线视频压缩工具
  19. 2021年全国职业技能大赛:网络系统管理项目
  20. 多叉树的python实现

热门文章

  1. 2023秋招—大数据开发面经—多益网络
  2. 在100ask stm32mp157板子上运行超级玛丽
  3. 谷歌浏览器解决临时跨域问题
  4. 腾讯:暑假只能周五六日打游戏,最多玩21小时
  5. 【贴图、OCR】snipaste、天若OCR-win软件
  6. 【制作数字人】零门槛通过三维重建技术生成个人三维模型
  7. 【JVM】详解类加载机制
  8. Mac远程桌面连接Windows
  9. 计算机设备停用代码22,Win7电脑提示由于该设备有问题Windows已将其停止(代码43)怎么办?...
  10. 人力资源管理专业必读书目(基础部分)