Mybatis数据源

时间:2023-03-09 08:57:13
Mybatis数据源

在描述mybatis数据源之前,先抛出几个问题,这几个问题都能在本文得到解答

1.mybatis是如何获取到mysql连接的?

2.mybatis的Connection是怎么被创建的?

  

1.Datasource的分类


我们已一段mybatis的配置文件为例

<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

datasource的type共有三个选项

UNPOOLED    不使用连接池的数据源

POOLED      使用连接池的数据源

JNDI        使用JNDI实现的数据源

2.Datasource的配置加载与创建


mybatis在项目启动阶段会加载配置文件,读取xml中的配置信息到Configuration中。我们看下datasource是怎么加载进来的。这段代码在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

Mybatis数据源

点进去,找到这个方法。

Mybatis数据源

Mybatis数据源

这里通过工厂模式,反射生成一个DatasourceFactory。

这里DataSourceFactory有三个子类,也就是上述三个datasource的类型。

Mybatis数据源

3.Connection的创建


当Datasource的配置加载到configuration后。每一次执行sql都需要Datasource对象创建Connection去连接数据库。

我们以一次查询为例,看下这个connection到底是怎么生成的。

Mybatis数据源

Mybatis数据源

Mybatis数据源

transaction对象持有connection和datasource,我们再点进去

Mybatis数据源

Mybatis数据源

Mybatis数据源

终于看到了真正获取connection是datasource,那datasource是怎么获取connection的呢?

4.连接池


datasource有三种类型,相对应的每种datasource创建connection各不相同。常见的是POOLEDA和UNPOOLED两种类型。

UNPOOLED顾名思义就是不用连接池的方式,每次用到一个就生产一个

Mybatis数据源

POOLEDA类型采用了连接池的方式,内部通过Poolstate对象来维护连接池对象

Mybatis数据源

Poolstate内部有两个数组,idleConnections用来存放空闲的connection,activeConnections用来存放连接中的connection。

Mybatis数据源

具体的获取一个Connection连接如下

private final PoolState state = new PoolState(this);
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) {
//这个对象锁锁的范围有点大,不过这也是因为PoolState内部的两个集合是ArrayList会产生并发问题
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);
@SuppressWarnings("unused")
//used in logging, if enabled
Connection realConn = conn.getRealConnection();
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// 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) {
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);
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;
}

1.   先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。

2.  查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;

3.  看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。

4.  线程等待,循环2步

以上描述的是通过连接池获取connection,那在关闭connection后,参照获取连接的步骤,首先从activeConnections中移除,再判断idle数组是否已经满了,如果满了再判断下数组中第一个连接是否已经任然可用,如果可用再把这个这个连接销毁。

上文我们提到了PoolState

Mybatis数据源

两个集合里面存放着PooledConnection对象,PooledConnection对象是Connection的代理类

Mybatis数据源

PooledConnection相比Connection多了什么呢,答案就在invoke方法中,当connection对象调用close方法时,会调用datasource的pushConnection方法,pushConnection的方法大致和上面的猜想一致

Mybatis数据源