MyBatis基礎(chǔ)支持DataSource實(shí)現(xiàn)源碼解析
DataSource
在數(shù)據(jù)庫應(yīng)用中,客戶端與數(shù)據(jù)庫服務(wù)端建立的連接對象(Connection)是寶貴的資源,每次請求數(shù)據(jù)庫都創(chuàng)建連接,使用完畢后會銷毀連接,這是一種很浪費(fèi)資源的操作。因此Java提出了DataSource接口??梢园阉?dāng)作一個連接池。程序初始化時,創(chuàng)建一批連接放入到連接池中,如果需要請求數(shù)據(jù)庫就從連接池中取出連接對象(Connection)使用完畢后把連接歸還給連接池。這樣就減少了每次請求都創(chuàng)建、銷毀連接的步驟,從而提高數(shù)據(jù)庫性能。
package javax.sql;
public interface DataSource extends CommonDataSource, Wrapper {
// 最重要的方法
Connection getConnection() throws SQLException;
// 其他方法不再列出
}
Java只是在JDK1.4版本發(fā)布了該接口規(guī)范。具體實(shí)現(xiàn)需要用戶自己實(shí)現(xiàn)。MyBatis中提供了3種DataSource接口的實(shí)現(xiàn)。
- UnpooledDataSource
- PooledDataSource
- JNDI方式的接口(不在本文討論范圍)
下面著重分析1和2這兩種DataSource的實(shí)現(xiàn)。
UnpooledDataSource
UnpooledDataSource顧名思義,他是非池化的DataSource,說白了和普通的Connection沒什么區(qū)別。通過UnpooledDataSource過去連接每次都需要重新創(chuàng)建一個Connection。我們來看下它的getConnection實(shí)現(xiàn)方法。
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
在UnpooledDataSource#getConnection方法中,調(diào)用了doGetConnection方法,參數(shù)是username和password,該方法也就是通過用戶名和密碼獲取數(shù)據(jù)庫連接的意思。doGetConnection具體實(shí)現(xiàn)就使用了DriverManager來獲取連接對象。這是JDBC原生獲取連接對象的方式。
值得一說的是:UnpooledDataSource的其他方法都是基于DriverManager實(shí)現(xiàn)的。也就是說,使用UnpooledDataSource作為連接池的話等價于沒有使用連接池。
PooledDataSource
PooledDataSource才是真正意義上的連接池,它提供了連接池的大?。J(rèn)10)、最大活躍連接數(shù)量、空閑連接數(shù)量等蠶食設(shè)置。并且對Connection對象進(jìn)行了JDK動態(tài)代理,重寫了Connection的close方法。使得Connection對象在調(diào)用close方法是不是真正的關(guān)閉連接,而是把自定義關(guān)閉行為,MyBatis的關(guān)閉邏輯就是把Connection對象歸還連接池。
我們先看下PooledDataSource的幾個重要字段信息
public class PooledDataSource implements DataSource {
// PooledDataSource真正管理連接狀態(tài)的是PoolState,后面會詳細(xì)說明
private final PoolState state = new PoolState(this);
// UnpooledDataSource上面說過和普通的Connection無異
private final UnpooledDataSource dataSource;
//正在使用連接的數(shù)量
protected int poolMaximumActiveConnections = 10;
//空閑連接數(shù)
protected int poolMaximumIdleConnections = 5;
//在被強(qiáng)制返回之前,池中連接被檢查的時間
protected int poolMaximumCheckoutTime = 20000;
//這是給連接池一個打印日志狀態(tài)機(jī)會的低層次設(shè)置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時間 為了避免連接池沒有配置時靜默失 敗)。
protected int poolTimeToWait = 20000;
//發(fā)送到數(shù)據(jù)的偵測查詢,用來驗(yàn)證連接是否正常工作,并且準(zhǔn)備 接受請求。默認(rèn)是“NO PING QUERY SET” ,這會引起許多數(shù)據(jù)庫驅(qū)動連接由一 個錯誤信息而導(dǎo)致失敗
protected String poolPingQuery = "NO PING QUERY SET";
//開啟或禁用偵測查詢
protected boolean poolPingEnabled = false;
//用來配置 poolPingQuery 多次時間被用一次
protected int poolPingConnectionsNotUsedFor = 0;
private int expectedConnectionTypeCode;
}
這些字段主要記錄了連接池的重要信息:連接池大小、空閑時最大連接數(shù)、最大活躍連接數(shù)、超時時間等。而整整揭開PooledDataSource獲取連接對象的神秘面紗還需要介紹兩個類。PooledConnection和PoolState
PooledConnection
PooledConnection實(shí)現(xiàn)了InvocationHandler接口,他是用來做JDK動態(tài)代理的。前文提到過,mybatis使用JDK動態(tài)代理重寫了Connection對象的close方法,就是在該類中實(shí)現(xiàn)的邏輯。該類有幾個重要屬性。
- private PooledDataSource dataSource; // dataSource的副本
- private Connection realConnection; // 真實(shí)連接對象
- private Connection proxyConnection; // 實(shí)際返回的代理對象
接下來來看下代理對象的invoke方法是如何重寫close方法的。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//如果調(diào)用close的話,忽略它,反而將這個connection加入到池中
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
return method.invoke(realConnection, args);
// 其他邏輯省略....
}
在invoke方法中判斷下執(zhí)行的方法名稱是否是Close,如果是,就不再執(zhí)行原來的close方法了,而是執(zhí)行PooledDataSource 的pushConnection方法!從方法名可以看出方法的作用是:把連接push到連接池PooledDataSource 中。pushConnection的邏輯后文詳細(xì)說明
PoolState
上文提到PooledDataSource并不管理連接對象。那么程序初始化的時候創(chuàng)建的一批連接存放到哪里了呢?答案是存在PoolState對象中,而PooledDataSource有一個屬性就是PoolState。也就是說PooledDataSource是通過PoolState來管理連接池的。
一批連接在Java中就是一個List集合嘛。那么我們想一下PoolState都需要怎么管理連接呢?首先根據(jù)連接的狀態(tài),可以把連接分為2種
- 空閑連接
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); - 活躍連接
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
PoolState中兩個List屬性分別存儲空閑連接和活躍連接。需要連接的時候就從idleConnections 列表中取,關(guān)聯(lián)連接時就把連接從activeConnections 中移到idleConnections 中。
PoolState中還有一些其他的統(tǒng)計信息字段,比如 請求次數(shù)、請求的總時間、總連接數(shù)等這些屬性比較簡單就不再列出了
獲取連接
介紹完P(guān)ooledConnection和PoolState這兩個類后,我們來看下PooledDataSource是怎么獲取連接的。獲取連接的邏輯在PooledDataSource#getConnection方法中,getConnection方法只是一個殼子,具體調(diào)用邏輯在popConnection方法。我們來看一下(我只列出了重要邏輯)
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
//最外面是while死循環(huán),如果一直拿不到connection,則不斷嘗試
while (conn == null) {
synchronized (state) {
if (!state.idleConnections.isEmpty()) {
//如果有空閑的連接的話,返回第一個空閑連接
conn = state.idleConnections.remove(0);
} else {
//如果沒有空閑的連接
if (state.activeConnections.size() < poolMaximumActiveConnections) {
//如果activeConnections太少,那就new一個PooledConnection
conn = new PooledConnection(dataSource.getConnection(), this);
} else {
//如果activeConnections已經(jīng)很多了,那不能再new了
//取得activeConnections列表的第一個(最老的)
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
//如果checkout時間過長,則這個connection標(biāo)記為overdue(過期)
//刪掉最老的連接,然后再new一個新連接
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
} else {
//如果checkout時間不夠長,沒辦法,只能等待,在此分支會記錄一些統(tǒng)計信息
}
}
}
if (conn != null) {
if (conn.isValid()) {
//如果已經(jīng)拿到connection,則記錄一些統(tǒng)計信息
} else {
//如果沒拿到,統(tǒng)計信息:壞連接+1
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
//如果好幾次都拿不到,就放棄了,拋出異常
}
}
}
}
return conn;
}
在popConnection中
- 從PoolState對象的空閑連接列表中獲取連接,如果有空閑連接就返回。
- 從PoolState對象的活躍連接列表中獲取連接,如果連接數(shù)小于最大活躍數(shù),則new一個連接返回。如果沒有只能等待其他線程釋放連接再進(jìn)行獲取
- 無論是否獲取到連接,對連接進(jìn)行一些信息統(tǒng)計并記錄到PoolState對象中。一旦嘗試獲取連接的時間超過了閾值,就會放棄獲取連接拋出異常
關(guān)閉連接
在PooledConnection小節(jié)中見到,PooledConnection重寫了Connection的close方法。當(dāng)調(diào)用Connection的close方法時真正執(zhí)行的邏輯是PooledDataSource的pushConnection方法。該代碼邏輯很簡單,大體上說,就是把連接從活躍列表中刪除,加入到空閑列表中。具體實(shí)現(xiàn)如下
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//先從activeConnections中刪除此connection
state.activeConnections.remove(conn);
if (conn.isValid()) {
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
//如果空閑的連接太少,
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//new一個新的Connection,加入到idle列表
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
//通知其他線程可以來搶connection了
state.notifyAll();
} else {
//否則,即空閑的連接已經(jīng)足夠了
state.accumulatedCheckoutTime += conn.getCheckoutTime();
//那就將connection關(guān)閉就可以了,獲取真正的connection對象并且關(guān)閉
conn.getRealConnection().close();
conn.invalidate();
}
}
}
}
關(guān)閉過程:
- 空閑連接數(shù)<最大空閑連接數(shù) 則新建一個連接存放到PoolState的空閑列表中并通知其他線程可以來搶Connection對象
- 如果PoolState的空閑列表是滿的,那只能獲取真正的connection對象并將其關(guān)閉了。
小結(jié)
- PooledDataSource真正意義上實(shí)現(xiàn)了DataSource接口。具有連接池的意義
- PooledDataSource通過PooledConnection和PoolState來管理連接池中的連接
- PooledConnection重寫了Connection對象的close方法。調(diào)用Connection的close方法時并不會真正的關(guān)閉連接,而是先要進(jìn)行歸還連接的操作。
- PoolState是對連接列表狀態(tài)的管理。它有兩個List屬性,分別存儲了活躍連接列表和空閑連接列表
DataSourceFactory
獲取MyBatis提供的DataSource實(shí)現(xiàn),需要通過工廠DataSourceFactory接口來獲取。在這里MyBatis使用了工廠方法模式。DataSourceFactory有兩個實(shí)現(xiàn)類。分別是
- UnpooledDataSourceFactory
- PooledDataSourceFactory
我們首先來看下工廠接口定義
public interface DataSourceFactory {
//設(shè)置屬性,被XMLConfigBuilder所調(diào)用
void setProperties(Properties props);
//生產(chǎn)數(shù)據(jù)源,直接得到j(luò)avax.sql.DataSource
DataSource getDataSource();
}
其中最重要的方法就是getDataSource,它很直觀,通過工廠對象的該方法可以獲取DataSource實(shí)現(xiàn)。
UnpooledDataSourceFactory
UnpooledDataSourceFactory獲取dataSource的方法非常簡單直觀。
首先,構(gòu)造方法里里new了一個UnpooledDataSource對象存放到工廠的屬性中
然后,getDataSource直接返回該對象即可。具體實(shí)現(xiàn)如下
public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
public DataSource getDataSource() {
return dataSource;
}
}
PooledDataSourceFactory
PooledDataSourceFactory就有意思了,想偷懶,直接繼承自UnpooledDataSourceFactory。只需要在構(gòu)造方法中new一個PooledDataSource對象,再通過getDataSource方法獲取即可。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
//數(shù)據(jù)源換成了PooledDataSource
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
結(jié)語
個人感覺mybatis提供的DataSourceFactory的實(shí)現(xiàn)類有點(diǎn)雞肋。可以說還是new對象。我們知道工廠模式創(chuàng)建的一般都是比較復(fù)雜的對象,是用來幫助開發(fā)者屏蔽復(fù)雜的細(xì)節(jié)。而mybatis的這兩個實(shí)現(xiàn)都只是new對象而已。
以上就是MyBatis基礎(chǔ)支持DataSource實(shí)現(xiàn)源碼解析的詳細(xì)內(nèi)容,更多關(guān)于MyBatis基礎(chǔ)支持DataSource的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring5+SpringMvc+Hibernate5整合的實(shí)現(xiàn)
這篇文章主要介紹了Spring5+SpringMvc+Hibernate5整合的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
詳解Spring Cloud Consul 實(shí)現(xiàn)服務(wù)注冊和發(fā)現(xiàn)
這篇文章主要介紹了Spring Cloud Consul 實(shí)現(xiàn)服務(wù)注冊和發(fā)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
Java實(shí)現(xiàn)正則匹配 “1234567” 這個字符串出現(xiàn)四次或四次以上
文章介紹了如何在Java中使用正則表達(dá)式匹配一個字符串四次或四次以上的出現(xiàn),首先創(chuàng)建正則表達(dá)式,然后使用Pattern和Matcher類進(jìn)行匹配和計數(shù),通過示例代碼展示了如何實(shí)現(xiàn)這一功能,并解釋了匹配的整體次數(shù)和精確出現(xiàn)次數(shù)的邏輯,感興趣的朋友一起看看吧2025-02-02
Java中SimpleDateFormat方法超詳細(xì)分析
這篇文章主要給大家介紹了關(guān)于Java中SimpleDateFormat方法超詳細(xì)分析的相關(guān)資料,SimpleDateFormat 是一個以國別敏感的方式格式化和分析數(shù)據(jù)的具體類,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
使用JDBC實(shí)現(xiàn)數(shù)據(jù)訪問對象層(DAO)代碼示例
這篇文章主要介紹了使用JDBC實(shí)現(xiàn)數(shù)據(jù)訪問對象層(DAO)代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-10-10

