Question:
. mybatis 各种 datasource Factory类型是如何实现的?
. PooledDataSource 是如何实现的? 为什么需要 idleConnections 和 activeConnections 两个 容器 来管理?
.UnpooledDataSource 和 PooledDataSource 之间的关系是什么?
. PooledDataSource 包含哪些属性, 是如何 影响 连接池的行为的?
Mybatis 针对不同情况对不同的datasource做了封装,利用 JndiDataSourceFactory, UnpooledDataSourceFactory,PooledDataSourceFactory 3个datasource 分别 生成 不同的datasource, JndiDataSourceFactory 是依赖jndi服务从容器中获取用户配置的datasource, UnpooledDataSourceFactory 则是生成 不使用连接池的数据源,用户每次其每次通过datasouce获取connection,都会直接调用jdbc获取connection,而 PooledDataSourceFactory 则依赖自己的实现,实现了 连接池的管理功能
至于 JndiDataSourceFactory生成数据源的实现,可以自己查看,很容易理解,下面先讲下 UnpooledDataSourceFactory 是 如何实现:
查看DataSourceFactory 接口,有两个抽象方法,分别是 setPropertiesgetDataSource, setProperties可以对datasource的属性进行额外的设置,而getDataSource则是提供给用户获取数据源的方法, 下面是 UnpooledDataSourceFactory 的 setProperties实现:
public void setProperties(Propertiesproperties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);// 此处的datasource 引用为UnpooledDataSourceFactory构造函数实例化UnpooledDataSource 类,同时 SystemMetaObject 工具类不熟悉的可以查看源码或者上一篇的 《mybatis 源码系列 组件之 reflection》
for (Object key :properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH),value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource,propertyName, value);
metaDataSource.setValue(propertyName,convertedValue); // 设置 datasource的属性值
} else {
throw new DataSourceException("Unknown DataSource property: " +propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties",driverProperties);
}
}
顺着这条路走看看 UnpooledDataSource 是如何实现,着重看其 getConnection 方法:
public Connection getConnection() throws SQLException {
return doGetConnection(username,password);
}
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username,password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties !=null) {
props.putAll(driverProperties);
}
if (username !=null) {
props.setProperty("user",username);
}
if (password !=null) {
props.setProperty("password",password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Propertiesproperties) throws SQLException {
initializeDriver(); // 检查是否已经注册当前用户配置的 driver类型,如果没有,则为用户注册当前配置的driver类型
Connection connection = DriverManager.getConnection(url,properties); // 是不是很熟悉,直接通过原始jdbc获取connection
configureConnection(connection);// 根据用户配置数据源的属性,对 connection进行配置
returnconnection;// 直接返回实例化的 connection,未做任何缓存,没有连接池的概念
}
private void configureConnection(Connectionconn) throws SQLException {
if (autoCommit !=null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit); // 设置 是否自动提交
}
if (defaultTransactionIsolationLevel !=null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);//设置事务隔离级别
}
}
这就是 UnpooledDataSource 的主要实现了,是不是很容易!!
下面进入正题,看 PooledDataSource 是如何实现:
首先需要了解下 PoolState 这个类,其主要功能有:
. 利用 idleConnections 和 activeConnections 管理 连接池下 空闲和活跃的 数据库连接
. 一系列 监控 连接池状态的属性,包括:
. requestCount 用户请求 数据库连接的次数
. accumulatedRequestTime 对用户所有的数据库连接池的获取请求 的一个 时间总计
. accumulatedCheckoutTime, checkTime 为 这个 数据库连接从DriverManager获取后 的时间,而这个累计值值表示所有数据库连接池的 checkout 和
. claimedOverdueConnectionCount, 在mybatis针对每个 poolConnection 都有个 最大 checkedout(当前时间 减去 checkout 的时间) 时间,如果 某一个 connection的 checkedout 大于 poolMaximumCheckoutTime(默认为20000),则会累加当前值,并将超时的poolconnection从 activeConnections中去除
. hadToWaitCount 记录了用户获取连接池暂时无法获取的情况下,需要等待的数目
. hadToWaitCount 则记录了用户获取连接池暂时无法获取的情况下,需要等待的的时间累计值
. badConnectionCount 记录了 无效 connection的数目,可能是手动设置无效,或者 真实的 connection已经被设置为了 nulll, 或者 是 已经与数据库失去了连接
接着我们看看 PooledDataSource, 首先看其属性列表和构造函数,如下:
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource; // 看到了吗? 在这 引用了 一个 unpooledDataSource的实例,利用组合关系,在从数据库中获取真实连接的时候就是利用这层关系,用unpooledDataSource代理生成真实数据库连接
// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled = false;
protected int poolPingConnectionsNotUsedFor = 0;
private int expectedConnectionTypeCode; // 根据 <span style="font-family: Arial, Helvetica, sans-serif;">数据库配置 </span><span style="font-family: Arial, Helvetica, sans-serif;">url, username, password 生成的一个hashcode, 用于标志着当前的数据库池,如果url或者 username,或者password有变化,需要关闭现有连接</span>
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
public PooledDataSource(String driver, String url, String username, String password) {
dataSource = new UnpooledDataSource(driver, url, username, password);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(String driver, String url, Properties driverProperties) {
dataSource = new UnpooledDataSource(driver, url, driverProperties);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
下面进入正轨,看其是如何获取数据库连接的:
在这之前,需要了解下 PooledConnection 这个类,首先看其属性列表和构造函数:
private int hashCode = 0;
private PooledDataSource dataSource;
private Connection realConnection; // 真实的数据库连接
private Connection proxyConnection; // 代理的 数据库连接
private long checkoutTimestamp;
private long createdTimestamp;
private long lastUsedTimestamp;
private int connectionTypeCode;
private boolean valid;
/*
* Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in
*
* @param connection - the connection that is to be presented as a pooled connection
* @param dataSource - the dataSource that the connection is from
*/
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); // 在这 直接生成 真实数据库连接的代理
}
下面就看看这个代理做了什么:
/*
* Required for InvocationHandler implementation.
*
* @param proxy - not used
* @param method - the method to be executed
* @param args - the parameters to be passed to the method
* @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 如果方法名称为 close, 则在用户关闭当前代理数据库连接时,其并不会真正的关闭数据库连接,而是 调用 pooledDataSource的 pushConnection方法,尽可能的 重用
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) { //如果此方法不是从Object里继承过来的,也就是数据库连接的方法,则在调用之前需要检查其是否还有效
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
接着,看 PooledDataSource的 getConnection方法:
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); // 返回给用户的其实是代理类,并不是真实的数据库连接
}
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();<span style="font-family: Arial, Helvetica, sans-serif;"> // 返回给用户的其实是代理类,并不是真实的数据库连接</span>
}
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false; // 标志 <span style="font-family: Arial, Helvetica, sans-serif;">本次获取connection 是否进入等待</span>
PooledConnection conn = null;
long t = System.currentTimeMillis(); // 记录 获取connection的开始时间
int localBadConnectionCount = 0; // 记录本次因获取connection,但获取无效的connection次数
while (conn == null) { // 轮询,直到 不为空
synchronized (state) {
if (state.idleConnections.size() > 0) { // 首先检查 是否有 闲置的 连接,如果有,则直接返回闲置连接下的第一个
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) {// 如果没有闲置连接,则检查 活跃连接数目是否已经达到用户配置的上限,如果没有,则直接生成
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this); //直接利用 datasource (注意,其是 unpooledDataSource引用) 代理生成 真实数据库的连接
@SuppressWarnings("unused")
//used in logging, if enabled
Connection realConn = conn.getRealConnection();
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {// 如果已经达到用户配置活跃连接数目的上限,则 检查 活跃连接的第一个连接的checked out time是否已经 超时,如果已经超时,则将其从活跃连接容器中删除,同时生成pooledConnection
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
oldestActiveConnection.getRealConnection().rollback();
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {// 如果活跃连接中的第一个连接没有超时,则必须等待 轮询 获取
// Must wait
try {
if (!countedWait) {// 利用这个标志,可以 使 <span style="font-family: Arial, Helvetica, sans-serif;">state.hadToWaitCount 只会累加一次</span>
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) {
if (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); // 将当前获取的connection加入到 获取连接容器中
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 + 3)) {
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;
}
有数据库连接的获取,就有数据库连接的返还,在用户调用 proxyConnection 的close方法后,会调用 pooledDataSource的pushConnection方法,代码如下:
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn); //首先将当前connection从活跃连接中删除
if (conn.isValid()) { //检查是否有效
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {//检查 限制连接的数目是否已经达到上限,同时查看当前数据库连接是否是当前连接池生成的,如果满足,则将其放入 闲置连接容器中, 同时,通知 popConnection中等待线程
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();
} else {// 如果不满足,则 获取真实连接,并关闭连接
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {// 如果无效,则记录 badConnectionCount
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}