MyBatis基礎(chǔ)支持DataSource實(shí)現(xiàn)源碼解析
DataSource
在數(shù)據(jù)庫應(yīng)用中,客戶端與數(shù)據(jù)庫服務(wù)端建立的連接對(duì)象(Connection)是寶貴的資源,每次請(qǐng)求數(shù)據(jù)庫都創(chuàng)建連接,使用完畢后會(huì)銷毀連接,這是一種很浪費(fèi)資源的操作。因此Java提出了DataSource接口??梢园阉?dāng)作一個(gè)連接池。程序初始化時(shí),創(chuàng)建一批連接放入到連接池中,如果需要請(qǐng)求數(shù)據(jù)庫就從連接池中取出連接對(duì)象(Connection)使用完畢后把連接歸還給連接池。這樣就減少了每次請(qǐng)求都創(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)建一個(gè)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來獲取連接對(duì)象。這是JDBC原生獲取連接對(duì)象的方式。
值得一說的是:UnpooledDataSource的其他方法都是基于DriverManager實(shí)現(xiàn)的。也就是說,使用UnpooledDataSource作為連接池的話等價(jià)于沒有使用連接池。
PooledDataSource
PooledDataSource才是真正意義上的連接池,它提供了連接池的大?。J(rèn)10)、最大活躍連接數(shù)量、空閑連接數(shù)量等蠶食設(shè)置。并且對(duì)Connection對(duì)象進(jìn)行了JDK動(dòng)態(tài)代理,重寫了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ì)說明 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)制返回之前,池中連接被檢查的時(shí)間 protected int poolMaximumCheckoutTime = 20000; //這是給連接池一個(gè)打印日志狀態(tài)機(jī)會(huì)的低層次設(shè)置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時(shí)間 為了避免連接池沒有配置時(shí)靜默失 敗)。 protected int poolTimeToWait = 20000; //發(fā)送到數(shù)據(jù)的偵測(cè)查詢,用來驗(yàn)證連接是否正常工作,并且準(zhǔn)備 接受請(qǐng)求。默認(rèn)是“NO PING QUERY SET” ,這會(huì)引起許多數(shù)據(jù)庫驅(qū)動(dòng)連接由一 個(gè)錯(cuò)誤信息而導(dǎo)致失敗 protected String poolPingQuery = "NO PING QUERY SET"; //開啟或禁用偵測(cè)查詢 protected boolean poolPingEnabled = false; //用來配置 poolPingQuery 多次時(shí)間被用一次 protected int poolPingConnectionsNotUsedFor = 0; private int expectedConnectionTypeCode; }
這些字段主要記錄了連接池的重要信息:連接池大小、空閑時(shí)最大連接數(shù)、最大活躍連接數(shù)、超時(shí)時(shí)間等。而整整揭開PooledDataSource獲取連接對(duì)象的神秘面紗還需要介紹兩個(gè)類。PooledConnection和PoolState
PooledConnection
PooledConnection實(shí)現(xiàn)了InvocationHandler接口,他是用來做JDK動(dòng)態(tài)代理的。前文提到過,mybatis使用JDK動(dòng)態(tài)代理重寫了Connection對(duì)象的close方法,就是在該類中實(shí)現(xiàn)的邏輯。該類有幾個(gè)重要屬性。
- private PooledDataSource dataSource; // dataSource的副本
- private Connection realConnection; // 真實(shí)連接對(duì)象
- private Connection proxyConnection; // 實(shí)際返回的代理對(duì)象
接下來來看下代理對(duì)象的invoke方法是如何重寫close方法的。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); //如果調(diào)用close的話,忽略它,反而將這個(gè)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并不管理連接對(duì)象。那么程序初始化的時(shí)候創(chuàng)建的一批連接存放到哪里了呢?答案是存在PoolState對(duì)象中,而PooledDataSource有一個(gè)屬性就是PoolState。也就是說PooledDataSource是通過PoolState來管理連接池的。
一批連接在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è)類后,我們來看下PooledDataSource是怎么獲取連接的。獲取連接的邏輯在PooledDataSource#getConnection方法中,getConnection方法只是一個(gè)殼子,具體調(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()) { //如果有空閑的連接的話,返回第一個(gè)空閑連接 conn = state.idleConnections.remove(0); } else { //如果沒有空閑的連接 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í)間過長,則這個(gè)connection標(biāo)記為overdue(過期) //刪掉最老的連接,然后再new一個(gè)新連接 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate(); } else { //如果checkout時(shí)間不夠長,沒辦法,只能等待,在此分支會(huì)記錄一些統(tǒng)計(jì)信息 } } } if (conn != null) { if (conn.isValid()) { //如果已經(jīng)拿到connection,則記錄一些統(tǒng)計(jì)信息 } else { //如果沒拿到,統(tǒng)計(jì)信息:壞連接+1 state.badConnectionCount++; localBadConnectionCount++; conn = null; //如果好幾次都拿不到,就放棄了,拋出異常 } } } } return conn; }
在popConnection中
- 從PoolState對(duì)象的空閑連接列表中獲取連接,如果有空閑連接就返回。
- 從PoolState對(duì)象的活躍連接列表中獲取連接,如果連接數(shù)小于最大活躍數(shù),則new一個(gè)連接返回。如果沒有只能等待其他線程釋放連接再進(jìn)行獲取
- 無論是否獲取到連接,對(duì)連接進(jìn)行一些信息統(tǒng)計(jì)并記錄到PoolState對(duì)象中。一旦嘗試獲取連接的時(shí)間超過了閾值,就會(huì)放棄獲取連接拋出異常
關(guān)閉連接
在PooledConnection小節(jié)中見到,PooledConnection重寫了Connection的close方法。當(dāng)調(diào)用Connection的close方法時(shí)真正執(zhí)行的邏輯是PooledDataSource的pushConnection方法。該代碼邏輯很簡(jiǎn)單,大體上說,就是把連接從活躍列表中刪除,加入到空閑列表中。具體實(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); //通知其他線程可以來搶connection了 state.notifyAll(); } else { //否則,即空閑的連接已經(jīng)足夠了 state.accumulatedCheckoutTime += conn.getCheckoutTime(); //那就將connection關(guān)閉就可以了,獲取真正的connection對(duì)象并且關(guān)閉 conn.getRealConnection().close(); conn.invalidate(); } } } }
關(guān)閉過程:
- 空閑連接數(shù)<最大空閑連接數(shù) 則新建一個(gè)連接存放到PoolState的空閑列表中并通知其他線程可以來搶Connection對(duì)象
- 如果PoolState的空閑列表是滿的,那只能獲取真正的connection對(duì)象并將其關(guān)閉了。
小結(jié)
- PooledDataSource真正意義上實(shí)現(xiàn)了DataSource接口。具有連接池的意義
- PooledDataSource通過PooledConnection和PoolState來管理連接池中的連接
- PooledConnection重寫了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),需要通過工廠DataSourceFactory
接口來獲取。在這里MyBatis使用了工廠方法模式。DataSourceFactory有兩個(gè)實(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,它很直觀,通過工廠對(duì)象的該方法可以獲取DataSource實(shí)現(xiàn)。
UnpooledDataSourceFactory
UnpooledDataSourceFactory獲取dataSource的方法非常簡(jiǎn)單直觀。
首先,構(gòu)造方法里里new了一個(gè)UnpooledDataSource對(duì)象存放到工廠的屬性中
然后,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ì)象,再通過getDataSource方法獲取即可。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory { //數(shù)據(jù)源換成了PooledDataSource public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } }
結(jié)語
個(gè)人感覺mybatis提供的DataSourceFactory的實(shí)現(xiàn)類有點(diǎn)雞肋??梢哉f還是new對(duì)象。我們知道工廠模式創(chuàng)建的一般都是比較復(fù)雜的對(duì)象,是用來幫助開發(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)文章
詳解Java編寫并運(yùn)行spark應(yīng)用程序的方法
這篇文章主要介紹了詳解Java編寫并運(yùn)行spark應(yīng)用程序的方法,內(nèi)容詳細(xì),結(jié)合了作者實(shí)際工作中的問題進(jìn)行具體分析,具有一定參考價(jià)值。2017-09-09ElasticSearch創(chuàng)建后索引修改數(shù)據(jù)類型方法步驟
Elasticsearch存儲(chǔ)數(shù)據(jù)之前需要先創(chuàng)建索引,類似于結(jié)構(gòu)型數(shù)據(jù)庫建庫建表,創(chuàng)建索引時(shí)定義了每個(gè)字段的索引方式和數(shù)據(jù)類型,這篇文章主要給大家介紹了關(guān)于ElasticSearch創(chuàng)建后索引修改數(shù)據(jù)類型的方法步驟,需要的朋友可以參考下2023-09-09Java中數(shù)組的創(chuàng)建與傳參方法(學(xué)習(xí)小結(jié))
這篇文章主要介紹了Java中數(shù)組的創(chuàng)建與傳參方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Java?如何通過注解實(shí)現(xiàn)接口輸出時(shí)數(shù)據(jù)脫敏
這篇文章主要介紹了Java?如何通過注解實(shí)現(xiàn)接口輸出時(shí)數(shù)據(jù)脫敏,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12