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

