参考:http://blog.csdn.net/luanlouis/article/details/37671851
对于应用程序来说,与数据库的交互是必不可少的。但对于大多数应用来说,数据访问对象(Dao)的性能是整个应用的一个瓶颈点,目前比较成熟的解决方案是利用数据库连接池对数据库连接(Connection)进行本地缓存,避免频繁的创建数据库连接。
Mybatis作为当前最流行的数据访问层ORM框架之一,对连接池技术做了很好的集成,下面就来探究一下Mybatis的数据源与连接池的实现。
Mybatis数据源的分类
通过观察Mybatis核心包或查看帮助文档可知,有三种数据源:
1)UNPOOLED:每次请求时简单的打开数据库连接。
2)POOLED:每次请求时从连接池中取得连接。
3)JNDI:从容器上下文的数据源中(JNDI)获取连接。
数据源的创建过程
Mybatis中数据源的创建是由对应的数据源工程来实现的,数据源工厂的顶级接口为DataSourceFactory。下面是数据源工厂体系的类图:
1)读取mybatis核心配置文件<environments/>
标签
<environments default="mysql_environment">
<environment id="mysql_environment">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="zhangdd" />
<property name="password" value="zd1991.." />
</dataSource>
</environment>
</environments>
根据<dataSource/>
的type类型来选择用不同的工厂类创建对应的数据源。
type=”JNDI”,从容器上下文中查找并获取数据源实例
注:mybatis的在执行具体的sql语句的时候才调用DataSource的getConnection()的。
为什么要使用连接池技术
我们来看一下下面的例子
public class CreateConnectionTest {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/test";
String user = "zhangdd";
String password = "zd1991..";
long beforeTimeOffset = -1L; //创建Connection对象前时间
long afterTimeOffset = -1L; //创建Connection对象后时间
long executeTimeOffset = -1L; //执行sql的时间
PreparedStatement pstmt;
ResultSet rs;
String sql = "select * from employee where id = ?";
Class.forName("com.mysql.jdbc.Driver");
beforeTimeOffset = new Date().getTime();
System.out.println("before create:" + beforeTimeOffset);
Connection connection = DriverManager.getConnection(url, user, password);
afterTimeOffset = new Date().getTime();
System.out.println("after create:" + afterTimeOffset);
System.out.println("create cost:" + (afterTimeOffset - beforeTimeOffset) + "ms");
pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 1);
rs = pstmt.executeQuery();
executeTimeOffset = new Date().getTime();
System.out.println("execute time:" + executeTimeOffset);
System.out.println("execute cost:" + (executeTimeOffset - afterTimeOffset) + "ms");
connection.close();
}
}
结果为:
before create:1438840632462
after create:1438840632776
create cost:314ms
execute time:1438840632789
execute cost:13ms
由此可见,创建数据库连接是及其耗时的,如果一个应用需要频繁的操作数据库,那绝大多数时间会浪费在创建数据库连接而不是处理业务数据上。
解决以上问题的办法是事先创建好一部分连接放到内存中,当程序需要数据库连接时,直接从内存中获取,而不需要与数据库重新建立socket连接。数据库连接池是上述方案的非常好的实现。
Mybatis数据库连接池的实现
基本原理
首先mybatis将创建好的Connection对象装饰城PooledConnection,
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;
proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
然后将其放入到PoolState(存储连接的容器)中,在PooledState中有两个用于存放PooledConnection的容器,其中一个是空闲连接容器(idleConnections),用于存放空闲的PooledConnection,另一个是活动连接容器(activeConnections),用于存放正在使用的PooledConnection。
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
重点来了…
从mybatis连接池中获取连接时:
1)首先判断idleConnections 中是否有空闲的连接,若有,则从idleConnections 中直接返回一个空闲的PooledConnection,若没有,则进行 2)
2)查看活动池activeConnections 是否已满,如果未满,则创建一个新的PooledConnection对象,并放入到activeConnections 中,并返回该对象,若activeConnections 已满,则进行 3)
3)查看最先加入到activeConnections 中的PooledConnection对象是否已经过期,若已过期,则将次对象从activeConnections 中移除,创建一个新的PooledConnection对象并将其加入到activeConnections 中。
若没有过期的PooledConnection对象,则进行 4)
4)进入等待状态,重复2)
下一个重点…
在传统JDBC中的Connection对象上调用close()方法时执行的操作的断开本次连接,但现在应用的连接池技术,就不能将该连接直接断开,而是将连接返回到连接池PoolState中。
那么,mybatis是怎么实现的呢?前面已经说过,mybatis将Connection对象封装成PooledConnection,同时PooledConnection是一个InvocationHandler,这是重点
class PooledConnection implements InvocationHandler
这样一来,PooledConnection对象就是Connection的代理对象,当调用Connection接口的方法时,代理对象PooledConnection对象就要利用invoke()
方法进行过滤,当过滤到close()
方法时,执行了一些特别的动作
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (method.getDeclaringClass() != Object.class) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
从上面的代码中可以看到,close()对应的动作是
dataSource.pushConnection(this);
综上所述,mybatis利用动态代理改写了Connection接口中的close()方法。