转自
https://blog.csdn.net/luanlouis/article/details/37671851
详细内容看源博客,这里只是总结性的整理知识。
1 MyBatis数据源DataSource分类
MyBatis把数据源DataSource分为三种:
UNPOOLED
不使用连接池的数据源POOLED
使用连接池的数据源JNDI
使用JNDI实现的数据源
相应地,MyBatis内部分别定义了实现了java.sql.DataSource接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。 如下图所示:
对于
JNDI类型的数据源
DataSource,则是通过JNDI上下文中取值
。
2 数据源DataSource的创建过程
MyBatis数据源DataSource对象的创建
发生在MyBatis初始化的过程
中。下面让我们一步步地了解MyBatis是如何创建数据源DataSource的。
在mybatis的XML配置文件中,使用< dataSource>元素来配置数据源:
MyBatis在初始化时
,解析此文件,根据< dataSource>的type属性
,通过工厂模式
来创建相应类型的的数据源DataSource,MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中, 供以后使用。
type=”POOLED” :MyBatis会创建PooledDataSource实例
type=”UNPOOLED” :MyBatis会创建UnpooledDataSource实例
type=”JNDI” :MyBatis会从JNDI服务上查找DataSource实例,然后返回使用
3 什么时候创建Connection对象
3.1 使用连接池的PooledDataSource
当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候
。
3.2 不使用连接池的UnpooledDataSource
MyBatis首先会实例化
一个UnpooledDataSourceFactory工厂实例
,然后通过.getDataSource()方法返回一个UnpooledDataSource实例对象引用,我们假定为dataSource。 使用UnpooledDataSource的getConnection(),每次调用就会产生一个新的Connection实例对象。
4 连接池
4.1 为什么要使用连接池?
创建一个java.sql.Connection实例对象的代价超级大。
是因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接
,在建立通信连接的过程,消耗了这么多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!
4.2 解决方案
使用PooledDataSource的getConnection()方法来返回Connection对象。现在让我们看一下它的基本原理
:
PooledDataSource将java.sql.Connection对象包裹成PooledConnection对象放到了PoolState类型的容器中维护。 MyBatis将连接池中的PooledConnection分为两种状态: 空闲状态(idle)和活动状态(active)
,这两种状态的PooledConnection对象分别被存储到PoolState容器内的idleConnections和activeConnections两个List集合中:
idleConnections:空闲(idle)
状态PooledConnection对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection集合。调用PooledDataSource的getConnection()方法时,会优先从
此集合中取PooledConnection对象。当用完
一个java.sql.Connection对象时,MyBatis会将其包裹成PooledConnection对象放到此集合中。
activeConnections:活动(active)
状态的PooledConnection对象被放置到名为activeConnections的ArrayList中,表示当前正在被使用的PooledConnection集合。调用PooledDataSource的getConnection()方法时,会优先从idleConnections集合中取PooledConnection对象,如果没有,则看此集合是否已满,如果未满,PooledDataSource会创建出一个PooledConnection,添加到此集合中,并返回。如果满了,线程等待,再次查看空闲集合中是否有,
先看是否有空闲(idle)状态下
的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。- 查看活动状态的PooledConnection池activeConnections
是否已满
;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步; - 看最先进入activeConnections池中的PooledConnection对象
是否已经过期
:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。 - 线程等待,循环2步
4.3 java.sql.Connection对象的回收
- 不使用数据库连接池:调用 connection.close()方法,关闭connection连接,释放所持有的资源,Connection对象也就不能再使用。
- 使用了连接池:使用
代理模式
,为真正的Connection对象创建一个代理对象,代理对象所有的方法都是调用相应的真正Connection对象的方法实现。当代理对象执行close()方法时,要特殊处理,不调用真正Connection对象的close()方法,而是将Connection对象添加到连接池中
。PooledConnection则是对真正的数据库连接java.sql.Connection实例对象的包裹器。
class PooledConnection implements InvocationHandler {
//......
//所创建它的datasource引用
private PooledDataSource dataSource;
//真正的Connection对象
private Connection realConnection;
//代理自己的代理Connection
private Connection proxyConnection;
//......
}
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
实际上,我们调用PooledDataSource的getConnection()方法返回的就是这个proxyConnection对象。
当我们调用此proxyConnection对象上的任何方法时,都会调用PooledConnection对象内invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//当调用关闭的时候,回收此Connection到PooledDataSource中
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
从上述代码可以看到,当我们使用了pooledDataSource.getConnection()返回的Connection对象的close()方法时,不会调用真正Connection的close()方法,而是将此Connection对象放到连接池中。
5 JNDI类型的数据源DataSource
MyBatis定义了一个JndiDataSourceFactory工厂来创建通过JNDI形式生成的DataSource。从JNDI上下文中找到DataSource并返回。