Mybatis源码分析----Mybatis数据源与连接池(1)_drivermanager.getconnection如何返回datasource-程序员宅基地

技术标签: mybatis原理和源码解析  java  

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

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

以下是本章的组织结构:

 

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

 

一、MyBatis数据源DataSource分类

MyBatis数据源实现是在以下四个包中:

MyBatis把数据源DataSource分为三种:

        ž UNPOOLED    不使用连接池的数据源

        ž POOLED      使用连接池的数据源

        ž JNDI            使用JNDI实现的数据源

即:

相应地,MyBatis内部分别定义了实现了Java.sql.DataSource接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。 如下图所示:

对于JNDI类型的数据源DataSource,则是通过JNDI上下文中取值。

 

二、数据源DataSource的创建过程

 MyBatis数据源DataSource对象的创建发生在MyBatis初始化的过程中。下面让我们一步步地了解MyBatis是如何创建数据源DataSource的。

在mybatis的XML配置文件中,使用<dataSource>元素来配置数据源:

1.  MyBatis在初始化时,解析此文件,根据<dataSource>的type属性来创建相应类型的的数据源DataSource,即:

  • type=”POOLED”  :MyBatis会创建PooledDataSource实例
  • type=”UNPOOLED” :MyBatis会创建UnpooledDataSource实例
  • type=”JNDI”     :MyBatis会从JNDI服务上查找DataSource实例,然后返回使用

2.  顺便说一下,MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其getDataSource()方法返回数据源DataSource:

定义如下:

[java] view plain copy print?

派生到我的代码片

  1. public interface DataSourceFactory {  
  2.   
  3.   void setProperties(Properties props);  
  4.   //生产DataSource  
  5.   DataSource getDataSource();  
  6. }  

 

上述三种不同类型的type,则有对应的以下dataSource工厂:

  • POOLED        PooledDataSourceFactory
  • UNPOOLED     UnpooledDataSourceFactory
  • JNDI          JndiDataSourceFactory

   其类图如下所示:

 

3.  MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中, 供以后使用。

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

当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。

比如,我们有如下方法执行一个简单的SQL语句:

[java] view plain copy print?

派生到我的代码片

  1. String resource = "mybatis-config.xml";  
  2. InputStream inputStream = Resources.getResourceAsStream(resource);  
  3. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
  4. SqlSession sqlSession = sqlSessionFactory.openSession();  
  5. sqlSession.selectList("SELECT * FROM STUDENTS");  

前4句都不会导致java.sql.Connection对象的创建,只有当第5句sqlSession.selectList("SELECT * FROM STUDENTS"),才会触发MyBatis在底层执行下面这个方法来创建java.sql.Connection对象:

[java] view plain copy print?

派生到我的代码片

  1. protected void openConnection() throws SQLException {  
  2.     if (log.isDebugEnabled()) {  
  3.       log.debug("Opening JDBC Connection");  
  4.     }  
  5.     connection = dataSource.getConnection();  
  6.     if (level != null) {  
  7.       connection.setTransactionIsolation(level.getLevel());  
  8.     }  
  9.     setDesiredAutoCommit(autoCommmit);  
  10.   }  

 

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

四、不使用连接池的UnpooledDataSource

 

当 <dataSource>的type属性被配置成了”UNPOOLED”,MyBatis首先会实例化一个UnpooledDataSourceFactory工厂实例,然后通过.getDataSource()方法返回一个UnpooledDataSource实例对象引用,我们假定为dataSource。

使用UnpooledDataSource的getConnection(),每调用一次就会产生一个新的Connection实例对象。

UnPooledDataSource的getConnection()方法实现如下:

[java] view plain copy print?

派生到我的代码片

  1. /* 
  2. UnpooledDataSource的getConnection()实现 
  3. */  
  4. public Connection getConnection() throws SQLException  
  5. {  
  6.     return doGetConnection(username, password);  
  7. }  
  8.   
  9. private Connection doGetConnection(String username, String password) throws SQLException  
  10. {  
  11.     //封装username和password成properties  
  12.     Properties props = new Properties();  
  13.     if (driverProperties != null)  
  14.     {  
  15.         props.putAll(driverProperties);  
  16.     }  
  17.     if (username != null)  
  18.     {  
  19.         props.setProperty("user", username);  
  20.     }  
  21.     if (password != null)  
  22.     {  
  23.         props.setProperty("password", password);  
  24.     }  
  25.     return doGetConnection(props);  
  26. }  
  27.   
  28. /* 
  29.  *  获取数据连接 
  30.  */  
  31. private Connection doGetConnection(Properties properties) throws SQLException  
  32. {  
  33.     //1.初始化驱动  
  34.     initializeDriver();  
  35.     //2.从DriverManager中获取连接,获取新的Connection对象  
  36.     Connection connection = DriverManager.getConnection(url, properties);  
  37.     //3.配置connection属性  
  38.     configureConnection(connection);  
  39.     return connection;  
  40. }  

 

如上代码所示,UnpooledDataSource会做以下事情:

1.  初始化驱动:    判断driver驱动是否已经加载到内存中,如果还没有加载,则会动态地加载driver类,并实例化一个Driver对象,使用DriverManager.registerDriver()方法将其注册到内存中,以供后续使用。

2.  创建Connection对象:    使用DriverManager.getConnection()方法创建连接。

3.  配置Connection对象:    设置是否自动提交autoCommit和隔离级别isolationLevel。

4.  返回Connection对象。

 

上述的序列图如下所示:

 

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

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

 

1. 创建一个java.sql.Connection实例对象的代价

首先让我们来看一下创建一个java.sql.Connection对象的资源消耗。我们通过连接Oracle数据库,创建创建Connection对象,来看创建一个Connection对象、执行SQL语句各消耗多长时间。代码如下:

[java] view plain copy print?

派生到我的代码片

  1. public static void main(String[] args) throws Exception  
  2. {  
  3.   
  4.     String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";  
  5.     PreparedStatement st = null;  
  6.     ResultSet rs = null;  
  7.   
  8.     long beforeTimeOffset = -1L; //创建Connection对象前时间  
  9.     long afterTimeOffset = -1L; //创建Connection对象后时间  
  10.     long executeTimeOffset = -1L; //创建Connection对象后时间  
  11.   
  12.     Connection con = null;  
  13.     Class.forName("oracle.jdbc.driver.OracleDriver");  
  14.   
  15.     beforeTimeOffset = new Date().getTime();  
  16.     System.out.println("before:\t" + beforeTimeOffset);  
  17.   
  18.     con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "louluan", "123456");  
  19.   
  20.     afterTimeOffset = new Date().getTime();  
  21.     System.out.println("after:\t\t" + afterTimeOffset);  
  22.     System.out.println("Create Costs:\t\t" + (afterTimeOffset - beforeTimeOffset) + " ms");  
  23.   
  24.     st = con.prepareStatement(sql);  
  25.     //设置参数  
  26.     st.setInt(1, 101);  
  27.     st.setInt(2, 0);  
  28.     //查询,得出结果集  
  29.     rs = st.executeQuery();  
  30.     executeTimeOffset = new Date().getTime();  
  31.     System.out.println("Exec Costs:\t\t" + (executeTimeOffset - afterTimeOffset) + " ms");  
  32.   
  33. }  

 

上述程序在我笔记本上的执行结果为:

从此结果可以清楚地看出,创建一个Connection对象,用了250 毫秒;而执行SQL的时间用了170毫秒

创建一个Connection对象用了250毫秒!这个时间对计算机来说可以说是一个非常奢侈的!

这仅仅是一个Connection对象就有这么大的代价,设想一下另外一种情况:如果我们在Web应用程序中,为用户的每一个请求就操作一次数据库,当有10000个在线用户并发操作的话,对计算机而言,仅仅创建Connection对象不包括做业务的时间就要损耗10000×250ms= 250 0000 ms = 2500 s = 41.6667 min,竟然要41分钟!!!如果对高用户群体使用这样的系统,简直就是开玩笑!

2. 问题分析:

 

创建一个java.sql.Connection对象的代价是如此巨大,是因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接,在建立通信连接的过程,消耗了这么多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!

 

3.解决方案:

对于需要频繁地跟数据库交互的应用程序,可以在创建了Connection对象,并操作完数据库后,可以不释放掉资源,而是将它放到内存中,当下次需要操作数据库时,可以直接从内存中取出Connection对象,不需要再创建了,这样就极大地节省了创建Connection对象的资源消耗。由于内存也是有限和宝贵的,这又对我们对内存中的Connection对象怎么有效地维护提出了很高的要求。我们将在内存中存放Connection对象的容器称之为 连接池(Connection Pool)。下面让我们来看一下MyBatis的线程池是怎样实现的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/asdasdasd123123123/article/details/81034131

智能推荐

Jupyter Notebook - 正常显示中文和负号_jupyter notebook绘图怎样写负数-程序员宅基地

文章浏览阅读2.8k次。请先安装SimHei字体到系统字体库import matplotlib mplmpl.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签mpl.rcParams['axes.unicode_minus']=False #用来正常显示负号..._jupyter notebook绘图怎样写负数

论傻瓜交换机接到有vlan的网管交换机为什么可以通信_access上vlan接傻瓜交换机-程序员宅基地

文章浏览阅读1w次,点赞7次,收藏32次。实验目的网工的日常可能有这样的场景,一个傻瓜交换机接入了一个网管交换机的vlan端口,依然可以正常通信,我们通过实验验证结果,并且总结原因,加深对交换机的三个链路端口的理解。实验思路其实验证这样的场景非常简单,我们只需在核心交换机上配置vlan10(例如),并且开启DHCP,下面连了交换机的端口可以正常获取vlan10地址池的IP地址,就说明两台PC可以通信实验拓补分析过程1:..._access上vlan接傻瓜交换机

spring-cloud微服务 feign调用 本地调试问题_springcloud接口调试bug-程序员宅基地

文章浏览阅读2.1w次,点赞7次,收藏19次。现象反馈,公司微服务间相互调用时通过feignClient来实现,feignClient自带负载均衡(轮询), 但是本地调试时候,大家都用同一个注册中心Eureka Server 有几率分发至其他机器,而不是本地自己调试机器 解决办法:1、目前已经有部门同事(包括本人)解决办法是 自己修改端口号,修改@FeignClient(value = "11501-pica-cloud-h..._springcloud接口调试bug

【微信小程序】小程序获取元素的宽度高度_微信开发工具获取元素长度-程序员宅基地

文章浏览阅读4.4k次,点赞2次,收藏6次。微信小程序不能进行DOM操作,所以获取元素的信息就不能按照常规手法来做了。首先需要借助微信给我们开发者提供的一个API来实现返回一个SelectorQuery 对象实例。 接下来就好办多了,话不多说,上代码!API:wx.createSelectorQuery()具体实现代码如下:const query = wx.createSelectorQuery()..._微信开发工具获取元素长度

python 运用requests库 以及 正则表达式 爬取小说实例以及心得_正则函数爬虫小说-程序员宅基地

文章浏览阅读5.8k次,点赞7次,收藏22次。笔者标注部分:笔者技艺不精,若文章中有错误之处,还望各位大佬海涵,笔者希望与大家共同进步此外笔者推荐python3以上的编辑器,因为过程中会出现一些编码问题,python2系列可能会将问题复杂化,笔者采用python3.6进行演示爬取网站内容:某网站小说(至于哪个网站,自行看代码)robots协议:人工查看,无robots协议,可合法的对网站的爬取(若您对robots协议不熟悉,可询问小杜..._正则函数爬虫小说

unsigned int 和 unsigned long-程序员宅基地

文章浏览阅读1.8k次。(1)unsigned long int unsigned long int在C语言中是无符号长整形变量,是整形变量的一种。unsigned long int 与unsigned long是等价的,即定义的时候int可以不写。  C语言字节数为4,与long型数据一样。  unsigned long int的取值范围: 0~4294967295 即 0~(2的32次..._unsigned int&unsigned long int

随便推点

聊天系统的服务器端类图,聊天系统服务器端类图-程序员宅基地

文章浏览阅读549次。聊天系统服务器端类图 内容精选换一换当创建文件系统后,您需要使用云服务器来挂载该文件系统,以实现多个云服务器共享使用文件系统的目的。本章节以Windows 2012版本操作系统为例进行NFS文件系统挂载,其他版本请参考以下主要步骤根据实际界面进行配置。同一SFS容量型文件系统不能同时支持NFS协议和CIFS协议。裸金属服务器、容器(CCE&CCI)操作与云服务器一致。已完成迁移前,您需要设..._即时聊天系统类图

Unity VideoPlayer播放视频 Slider控制播放进度_unity videoplayer slider控制视频前进-程序员宅基地

文章浏览阅读1.1w次,点赞7次,收藏52次。using UnityEngine;using UnityEngine.UI;using UnityEngine.Video;public class ToPlayVideo : MonoBehaviour{ public VideoClip[] videoClips; // 视频的文件 参数 public Text videoTimeText; ..._unity videoplayer slider控制视频前进

android mvvm官方文档,Android中使用mvvm-程序员宅基地

文章浏览阅读370次。使用mvvm1、让android项目启用dataBinding使用mvvm,必然会用到android为我们提供的dataBinding支持包需要在我们正在编写的module中的build.gradle中添加(添加到android {} 内部)dataBinding {enabled = true}构建一下,就可以使用2、修改布局文件使用databinding框架,需要布局文件 按照 「固定的写法」..._android官方关于mvvm的最佳使用

C语言嵌入式Linux编程第3期:程序的编译、链接和运行-王利涛-专题视频课程-程序员宅基地

文章浏览阅读386次。本课程为《C语言嵌入式Linux编程》第3期,主要对程序的编译、链接及运行机制进行分析。同时对静态库链接、动态链接的过程、插件原理、内核模块运行机进行探讨,后对嵌入式系统比较难理解的u-boot重定位、u-boot加载内核、内核解压缩、重定位过程进行分析,加深对程序的编译链接原理的理解。..._程序编译链接重定位 宅学俱乐部

Mysql 优化之EXPLAIN执行计划 const eq_ref ref range index all_mysql explan eq_ref ref index-程序员宅基地

文章浏览阅读2.6k次。explain:查询查询性能或者需要查看使用索引状态一、type:连接类型 最关键的一列 效率(const>eq_ref>ref>range>index>all)1、const:查询索引字段,并且表中最多只有一行匹配(好像只有主键查询只匹配一行才会是const,有些情况唯一索引匹配一行会是ref)2、eq_ref 主键或者唯一索引3、ref 非唯一索引(主键也是唯一索引)4、range 索引的范围查询5、index (type=index ext_mysql explan eq_ref ref index

(转载)虚幻引擎3--UnrealScript语言参考-程序员宅基地

文章浏览阅读423次。档概要: UnrealScript 介绍和简要参考。最初作者是Tim Sweeney(EpicGames)内容UnrealScript语言参考介绍快速链接本文档的目的UnrealScript的设计目标虚幻引擎3中UnrealScript的新功能代码结构示例Unreal虚拟机对象层次类变量变量类型内..._epic虚幻引擎怎么用unreal 用的什么语言

推荐文章

热门文章

相关标签