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

智能推荐

《ActBERT》百度&悉尼科技大学提出ActBERT,学习全局局部视频文本表示,在五个视频-文本任务中有效!...-程序员宅基地

文章浏览阅读155次。关注公众号,发现CV技术之美本文分享论文『ActBERT: Learning Global-Local Video-Text Representations』,百度&悉尼科技大学提出《ActBERT》,学习全局局部视频文本表示,在五个视频-文本任务中有效!详细信息如下:论文链接:https://arxiv.org/abs/2011.07231 01..._actbert

smartivr 5.0 接口说明 (智能电话机器人话术开发接口)_ivr流程中接口有哪些-程序员宅基地

文章浏览阅读478次。FreeSWITCH 电话机器人 smartivr 接口文档_ivr流程中接口有哪些

AAAI 2024 | 腾讯优图实验室27篇论文入选,含表格结构识别、异常图像生成、医学图像分割等研究方向...-程序员宅基地

文章浏览阅读289次。关注公众号,发现CV技术之美AAAI 2024 (AAAI Conference on Artificial Intelligence)人工智能国际会议于近日公布论文录用结果,本届会议共收到9862篇份论文投稿,最终录用2342篇论文,录用率23.75%。AAAI 是美国人工智能协会主办的年会,同时也是人工智能领域中历史最悠久、涵盖内容最广泛的的国际顶级学术会议之一。今年,腾讯优图实验室共有27..._腾讯优图实验室

神经网络在电商中的应用,神搜电商科技_电商投流 神经网路-程序员宅基地

文章浏览阅读1.3k次。根据《电子商务概论》(李琪 主编,高等教育出版社,2004年9月)第6章(电子商务链分析)中的相关内容,“商务链与交易链是将商务和交易活动进行联系与划分,并使之有序化的逻辑链条,它们高度抽象地将商务/交易活动表现为不同的节点,每个节点分别代表一定的经济事务,通过将这些节点有效地串连起来,共同形成了一个商务链或交易链。电子商务链是用来描述电子商务的交易(商务)流程的综合框架,这一分析框架从商务活动的基本过程对电子商务进行研究,揭示了电子商务的一般框架,并对电子商务链中的主要环节提供了指导性的分析方法。_电商投流 神经网路

Dubbo性能优化_dubbo接口发送1m数据需要多久-程序员宅基地

文章浏览阅读995次。https://my.oschina.net/u/1378920/blog/739399Dubbo性能优化背景dubbo作为一款分布式服务框架,除了提供远程调用的细节封装,还提供了基本的服务治理功能,能够粗略地监控系统性能。上图展示的是dubbo执行流程的原理图,在客户端和服务端都有一个程序去统计调用信息,其中有价值的信息有延迟时间、并发数、调用次数等,完成记录_dubbo接口发送1m数据需要多久

集群时钟同步问题_grid中集群时间同步问题-程序员宅基地

文章浏览阅读82次。问题举⼀个例⼦,电商⽹站业务中,新增⼀条订单,那么势必会在订单表中增加了⼀条记录,该条记录中应该会有“下单时间”这样的字段,往往我们会在程序中获取当前系统时间插⼊到数据库或者直接从数据库服务器获取时间。那我们的订单⼦系统是集群化部署,或者我们的数据库也是分库分表的集群化部署,然⽽他们的系统时钟缺不⼀致,⽐如有⼀台服务器的时间是昨天,那么这个时候下单时间就成了昨天,那我们的数据将会混乱!集群时钟同步配置集群时钟同步思路:分布式集群中各个服务器节点都可以连接互联⽹操作⽅式:#使⽤ ntpdat_grid中集群时间同步问题

随便推点

【BLDC理论篇】无刷直流电机与永磁同步电机比较_为什么bldc转矩波动大-程序员宅基地

文章浏览阅读1.6w次,点赞23次,收藏111次。现代电机与控制技术以电流驱动模式的不同将永磁无刷直流电动机分为两大类:1)方波驱动电机,也即无刷直流电机(BLDC);2)正弦波驱动电机:也即永磁同步电机(PMSM)。 表面来看,BLDC和PMSM的基本结构是相同的:1)它们的电动机都是永磁电动机,转子由永磁体组成基本结构,定子安放有多相交流绕组;2)都是由永久磁铁转子和定子的交流电流相互作用产生电机的转矩;3)在绕组中的..._为什么bldc转矩波动大

深入理解jvm-java内存模型(结合volatile)_volatile jvm-程序员宅基地

文章浏览阅读413次。1. 结构所有的变量存储在主内存每条线程都有自己的工作内存线程的工作内存保存了该线程所使用变量的主内存副本线程对变量的所有操作必须在工作内存中进行,不得直接操作主内存线程间变量的传递必须由主内存来交互完成2.内存间交互原子操作·lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。·unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释..._volatile jvm

蔡司全球新品发布会暨新品技术分享会上线-程序员宅基地

文章浏览阅读268次。顺应今年特殊环境下企业的迫切需求,蔡司通过线上发布形式,突破目前线下交流所受到的约束,以数字化方式迎接“新常态”,让用户能够同以往一样及时、便捷地了解蔡司最新创新方案。10月20日,蔡司..._蔡司秋季发布会

实时显示iOS编写UI代码效果_[self.reloadbutton mas_makeconstraints:^(masconstr-程序员宅基地

文章浏览阅读564次。实时显示iOS编写UI代码效果分类: iOS 2015-05-08 18:48 255人阅读 评论(0)收藏 举报IOS实时显示UI目录(?)[+]编写iOS应用UI的方式大概有两种,一种是Storyboard/Xib,另一种是手写代码。采用Storyboard/Xib方式组织UI,由于提供可视化的特性,只要从UI库中拖动UI控件,便可以_[self.reloadbutton mas_makeconstraints:^(masconstraintmaker *make) {

python爬虫运行的整体流程_Python 网络爬虫程序架构及运行流程-程序员宅基地

文章浏览阅读460次。1 前言Python开发网络爬虫获取网页数据的基本流程为:发起请求通过URL向服务器发起request请求,请求可以包含额外的header信息。获取响应内容服务器正常响应,将会收到一个response,即为所请求的网页内容,或许包含HTML,Json字符串或者二进制的数据(视频、图片)等。解析内容如果是HTML代码,则可以使用网页解析器进行解析,如果是Json数据,则可以转换成Json对象进行解析..._python正在爬数据的时候可以运行程序吗

平衡二叉树详解及C++实现_c++ 二叉搜索树平衡二叉树-程序员宅基地

文章浏览阅读583次,点赞2次,收藏7次。平衡二叉搜索树_c++ 二叉搜索树平衡二叉树

推荐文章

热门文章

相关标签