《深入理解Mybatis原理》 02-Mybatis数据源与连接池
对于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数据源与连接池相关推荐
- mybatis 原理_深入理解MyBatis原理 MyBatis数据源与连接池
点击上方"程序开发者社区"关注,选择"设为星标" 第一时间送达实用干货 对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文 ...
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)
文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...
- 《深入理解mybatis原理》 MyBatis缓存机制的设计与实现
本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论. MyBatis将数据缓存设计成两级结构,分为一级缓存 ...
- 深入理解Spring Boot数据源与连接池原理
Create by yster@foxmail.com 2018-8-2 一:开始 在使用Spring Boot数据源之前,我们一般会导入相关依赖.其中数据源核心依赖就是spring‐boot‐s ...
- 《深入理解mybatis原理三》 Mybatis数据源与连接池
对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池. 本文首先会讲述MyB ...
- 《深入理解mybatis原理》 MyBatis的一级缓存实现详解 及使用注意事项
MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上.MyBatis提供了一级缓存.二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能.本 ...
- Mybatis | Mybatis-plus配置多数据源,连接多数据库
文章目录 前言 业务逻辑 使用Mybatis实现 使用Mybatis-plus实现 前言 工作的时候,遇到了需要将一个数据库的一些数据插入或更新到另一个数据库.一开始使用insert into T ...
- SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件
我们这一一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池的好处我 ...
最新文章
- layui遍历json数组_shell脚本:json格式化与字段抓取(下)
- oracle 序列的使用
- SpringCloud系列之服务消费Ribbon和Feign区别
- linux连接教程视频,[原创]linux视频教程之连接
- 会话技术Cookie
- matlab保存colormap失败
- (BookxNote Pro)Windows版Marginnote 3 阅读神器 自动生成脑图/思维导图
- 《剑指offer》面试题27——二叉搜索树与双向链表(C++)
- 通达信指标加密DLL加密解密三个公式源码准确率90%以上超级指标精准买卖绝世指标
- 成本360元的迷你物联网服务器有多香?
- 面向对象编程中的 诡异事件
- 基于Tomcat的MQ学习月记
- 在微信开发者工具导入整个weui的实例,查看weui的用法
- 在Windows 7下删除注册表项时,权限不足
- IP-guard功能详解——安全U盘
- 微信小程序的悬浮按钮
- php手机座机验证,JS校验手机号 座机 邮箱 微信号
- 【在线工具】在线视频压缩工具
- 2021年全国职业技能大赛:网络系统管理项目
- 多叉树的python实现