您的位置:首页 > 其它

Mybatis学习笔记-数据源与连接池

2015-08-06 15:32 260 查看
参考:/article/1373172.html

对于应用程序来说,与数据库的交互是必不可少的。但对于大多数应用来说,数据访问对象(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=”POOLED”,创建PooledDataSource实例

type=”UNPOOLED”,创建UnpooledDataSource实例

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()方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: