1 回顧
上一文中解讀了MyBatis中非池型數(shù)據(jù)源的源碼,非池型也就是只擁有單一數(shù)據(jù)連接的數(shù)據(jù)源,他只管理著一個數(shù)據(jù)連接,這種數(shù)據(jù)源現(xiàn)在很少使用,一般都是用池型數(shù)據(jù)源,因為單個連接的情況下,為了保證操作的正確性,針對這個連接的使用要進行同步,這樣無疑會拖慢系統(tǒng)運行速度。
而使用池型數(shù)據(jù)源,在池中保存有多個數(shù)據(jù)庫連接,可以供多個數(shù)據(jù)庫訪問線程同時獲取現(xiàn)成的不同的數(shù)據(jù)庫連接,既保證了數(shù)據(jù)訪問的安全性,也能極大的提升系統(tǒng)的運行速度。
2 池型數(shù)據(jù)源
現(xiàn)在的Java項目中多采用池型數(shù)據(jù)源,C3P0,DBCP之類的也都提供了池型數(shù)據(jù)源,在MyBatis中也自定義了一種池型數(shù)據(jù)源PooledDataSource,這個pooled正好與之前的Configuration配置文件中配置的數(shù)據(jù)源的類型“POOLED”對應(yīng)。
<dataSource type="POOLED">
2.1 池型數(shù)據(jù)源工廠
首先我們來看看池型數(shù)據(jù)源的數(shù)據(jù)源工廠:PooledDataSourceFactory
1 package org.apache.ibatis.datasource.pooled; 2 import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; 3 public class PooledDataSourceFactory extends UnpooledDataSourceFactory { 4 public PooledDataSourceFactory() { 5 this.dataSource = new PooledDataSource(); 6 } 7 }
代碼很簡單,內(nèi)部只有一個方法,用于獲取吃型數(shù)據(jù)源的實例。果然好簡單,你想錯了......
從類結(jié)構(gòu)就可以看出,這個類繼承了UnpooledDataSourceFactory工廠類,也就是說,PooledDataSourceFactory也擁有UnpooledDataSourceFactory中羅列的諸多方法功能,最主要繼承的功能就是設(shè)置屬性的功能,這個功能可以將被讀取到內(nèi)存中的數(shù)據(jù)源配置信息保存到數(shù)據(jù)源實例中。
這個功能之前已經(jīng)有過介紹,這里不再贅述。下面看看PooledDataSource數(shù)據(jù)源。
這里在介紹PooledDataSource之前需要先對MyBatis自定義的池連接輔助類進行介紹。
2.2 池型連接:PooledConnedtion
class PooledConnection implements InvocationHandler {
不看不知道,一看就明白,這是一個動態(tài)代理,采用的是JDK動態(tài)代理,說明PooledConnection池連接就是為了創(chuàng)建一個真實連接的代理,使用連接代理來調(diào)用真實連接來進行其他操作。
同時這個代理對連接的功能進行了擴充,將其轉(zhuǎn)化為池型連接,即池型的概念與實現(xiàn)有一部分就在這個類中進行,這個類將一個普通的連接包裝成為一個池型化的連接,使其適用于MyBatis自定義的池型數(shù)據(jù)源。當(dāng)然這并不是包裝器模式,明顯的代理模式(動態(tài)代理模式)。
下面讓我們來仔細看看如何將一個簡單的連接包裝成為一個池型連接。
首先我們來看看池型連接擁有哪些屬性?
1 private static final String CLOSE = "close"; 2 private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; 3 private int hashCode = 0; 4 private PooledDataSource dataSource; 5 //真正的連接 6 private Connection realConnection; 7 //代理的連接 8 private Connection proxyConnection; 9 private long checkoutTimestamp; 10 private long createdTimestamp; 11 private long lastUsedTimestamp; 12 private int connectionTypeCode; 13 private boolean valid;
這里我們一一介紹:
String CLOSE = "close":這個很好理解,這只是下方代碼中需要使用到的一個靜態(tài)字符串常量而已,表示關(guān)閉。
Class<?>[] IFACES = new Class<?>[] {Connection.class}:這個和上面類似,也是下方要使用的一個靜態(tài)數(shù)組常量,表示連接類型,這里明顯使用到了多態(tài)的概念。Connection是所有連接的祖類,
int hashCode = 0:這是數(shù)據(jù)庫連接(真實連接)的HashCode值,默認(rèn)為0,表示當(dāng)真實連接不存在即為null時的值。
PooledDataSource dataSource:池型數(shù)據(jù)源,為什么在池型連接中會需要池型數(shù)據(jù)源實例呢?在下面你會看到它的應(yīng)用,它的主要目的還是為了方便調(diào)用其內(nèi)部定義的部分方法來輔助完成池型連接的一些功能判斷。
Connection realConnection:表示真實的數(shù)據(jù)庫連接,屬于被代理的對象
Connection proxyConnection:表示使用JDK動態(tài)代理創(chuàng)建的代理真實連接的代理連接實例
long checkoutTinmestamp:表示數(shù)據(jù)庫連接被檢出的時間戳,這個用于計算具體的檢出時間
long createdTimestamp:表示數(shù)據(jù)庫連接被創(chuàng)建的時間戳,用于計算數(shù)據(jù)庫連接被創(chuàng)建的時間
long lastUsedTimestamp:表示連接被最后使用的時間戳,用于計算數(shù)據(jù)庫連接被最后使用的時間
int connectionTypeCode:數(shù)據(jù)庫連接的類型編碼,格式為:url+username+password
boolean valid:表示連接是否可用的邏輯值
以上的屬性大多就是在原有的數(shù)據(jù)庫連接的基礎(chǔ)上做的再包裝,為其賦予更多的屬性,使其成為一個不折不扣的池型連接,這就像為一個人穿上各種衣服,裝飾,學(xué)習(xí)各種知識能力,最后將其包裝成一個某一方面的專業(yè)人士。呵呵,這里是真正的專業(yè)人士,真正擁有專業(yè)能力的人士。當(dāng)然最核心的還是這個人了,在池型連接中也一樣,最核心的當(dāng)然還是這個真實連接了。
下面看看構(gòu)造器:
1 public PooledConnection(Connection connection, PooledDataSource dataSource) { 2 this.hashCode = connection.hashCode(); 3 this.realConnection = connection; 4 this.dataSource = dataSource; 5 this.createdTimestamp = System.currentTimeMillis(); 6 this.lastUsedTimestamp = System.currentTimeMillis(); 7 this.valid = true; 8 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); 9 }
在這個構(gòu)造器中需要傳遞兩個參數(shù),分別為:數(shù)據(jù)庫連接實例與池型數(shù)據(jù)源實例,將其分別賦值給對應(yīng)的屬性,同時初始化其余屬性的值,最重要的還是最后一項,調(diào)用Proxy的newProxyInstance()方法來生成代理連接實例,其參數(shù)分別為:Connection類的類加載器、接口數(shù)組、當(dāng)前類的實例。(這是固定格式,套路,詳情可參見《代理模式之動態(tài)代理》、《java靜態(tài)代理與動態(tài)代理簡單分析》)
在這個動態(tài)代理實現(xiàn)中最重要的還是invoke方法的實現(xiàn):
1 @Override 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 String methodName = method.getName(); 4 //如果調(diào)用close的話,忽略它,反而將這個connection加入到池中 5 if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { 6 dataSource.pushConnection(this); 7 return null; 8 } else { 9 try { 10 if (!Object.class.equals(method.getDeclaringClass())) { 11 // issue #579 toString() should never fail 12 // throw an SQLException instead of a Runtime 13 //除了toString()方法,其他方法調(diào)用之前要檢查connection是否還是合法的,不合法要拋出SQLException 14 checkConnection(); 15 } 16 //其他的方法,則交給真正的connection去調(diào)用 17 return method.invoke(realConnection, args); 18 } catch (Throwable t) { 19 throw ExceptionUtil.unwrapThrowable(t); 20 } 21 } 22 } 23 24 private void checkConnection() throws SQLException { 25 if (!valid) { 26 throw new SQLException("Error accessing PooledConnection. Connection is invalid."); 27 } 28 }
我們一句句來看:
首先獲取要調(diào)用方法的名稱methodName,然后對這個名稱進行驗證,如果是close方法的話,那么忽略關(guān)閉的實現(xiàn),轉(zhuǎn)而將這個連接加到連接池中(推入pushConnection),這表示在池型了數(shù)據(jù)源中,當(dāng)連接不再使用后是要返回池中備用的,而不是直接被關(guān)閉銷毀。
如果調(diào)用的方法是不是close時,則首先進行該方法申明處的判斷,如果這個方法不是來自O(shè)bject類(剔除toString()方法),那么對當(dāng)前的連接的可用性進行判斷,如果不可用(valid值為false),則拋出SqlException,否則繼續(xù)執(zhí)行下一步,由真實連接進行方法調(diào)用。
原理也很簡單,總的來說就是實現(xiàn)方法調(diào)用(這也是代理的目的所在),外面看起來是由代理類執(zhí)行方法,其實內(nèi)部是由真實連接類來執(zhí)行方法。
2.3 池狀態(tài)類:PoolState
池狀態(tài),顧名思義,用于描述連接池的狀態(tài)(或稱為屬性也行)的類,這個屬性不同于池連接的屬性,這是出于連接池的屬性,這個屬性是作用于整個連接池的,而連接池中包含有限個池連接,一定要明白其中的關(guān)系。
在這個類中定義了諸多屬性,但是這些屬性多用于統(tǒng)計信息的輸出,什么是統(tǒng)計信息呢,就是將池連接的一些信息做一番統(tǒng)計并輸出。所以其中重要的屬性并不多,如下:
1 protected PooledDataSource dataSource; 2 //空閑的連接 3 protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); 4 //活動的連接 5 protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); 6 //----------以下是一些統(tǒng)計信息---------- 7 //請求次數(shù) 8 protected long requestCount = 0; 9 //總請求時間 10 protected long accumulatedRequestTime = 0; 11 protected long accumulatedCheckoutTime = 0; 12 protected long claimedOverdueConnectionCount = 0; 13 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; 14 //總等待時間 15 protected long accumulatedWaitTime = 0; 16 //要等待的次數(shù) 17 protected long hadToWaitCount = 0; 18 //壞的連接次數(shù) 19 protected long badConnectionCount = 0; 20 //構(gòu)造器 21 public PoolState(PooledDataSource dataSource) { 22 this.dataSource = dataSource; 23 }
上面代碼注釋中解釋的很明白,第一個是數(shù)據(jù)源實例,這個實例在通過構(gòu)造器創(chuàng)建實例的時候傳入并賦值,然后及時兩個ArrayList集合分別用于保存空閑的池連接實例與活動(使用)中的池連接實例。其余的就是一些統(tǒng)計信息,對于這些統(tǒng)計信息,不再詳述,可自行翻看源碼。
這里做簡單介紹:類中重寫了toString()方法,用于輸出這些信息,在類中用于獲取這些信息的方法都是同步方法,保證線程安全性,保證獲得的是正確的信息。
下面就是重點:池型數(shù)據(jù)源
2.4 池型數(shù)據(jù)源:PooledDataSource
這是一個同步的線程安全的數(shù)據(jù)庫連接池。
其實對于一個有連接池的數(shù)據(jù)源來說,針對池中數(shù)據(jù)連接的操作就是定義這個池型數(shù)據(jù)源的重點所在,那么針對池型連接的操作有哪些呢?
獲取池連接(推出池連接)
收回池連接(推入池連接)
關(guān)閉池連接
在加上一個池連接的可用性判斷,而我們的重點也就集中在這幾點。
首先我們來看看池型數(shù)據(jù)源擁有那些屬性:
1 public class PooledDataSource implements DataSource { 2 3 private static final Log log = LogFactory.getLog(PooledDataSource.class); 4 5 //有一個池狀態(tài) 6 private final PoolState state = new PoolState(this); 7 8 //里面有一個UnpooledDataSource 9 private final UnpooledDataSource dataSource; 10 11 // OPTIONAL CONFIGURATION FIELDS 12 //正在使用連接的數(shù)量 13 protected int poolMaximumActiveConnections = 10; 14 //空閑連接數(shù) 15 protected int poolMaximumIdleConnections = 5; 16 //在被強制返回之前,池中連接被檢查的時間 17 protected int poolMaximumCheckoutTime = 20000; 18 //這是給連接池一個打印日志狀態(tài)機會的低層次設(shè)置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時間 為了避免連接池沒有配置時靜默失 敗)。 19 protected int poolTimeToWait = 20000; 20 //發(fā)送到數(shù)據(jù)的偵測查詢,用來驗證連接是否正常工作,并且準(zhǔn)備 接受請求。默認(rèn)是“NO PING QUERY SET” ,這會引起許多數(shù)據(jù)庫驅(qū)動連接由一 個錯誤信息而導(dǎo)致失敗 21 protected String poolPingQuery = "NO PING QUERY SET"; 22 //開啟或禁用偵測查詢 23 protected boolean poolPingEnabled = false; 24 //用來配置 poolPingQuery 多次時間被用一次 25 protected int poolPingConnectionsNotUsedFor = 0; 26 27 private int expectedConnectionTypeCode; 28 ...... 29 }
結(jié)合源碼中的注釋內(nèi)容可知:
PoolState state = new PoolState(this):擁有一個池狀態(tài)屬性,無可厚非,池狀態(tài)正是用于描述整個連接池整體的,這里將當(dāng)前數(shù)據(jù)源實例作為參數(shù)賦予池狀態(tài)形成一個不變的實例(final修飾的作用),這里的不變指的是這個池狀態(tài)的實例是不變的,但不并意味著池狀態(tài)中的各屬性的值也不變,這個要看池狀態(tài)類中屬性是如何定義的,查看源碼發(fā)現(xiàn),這兩個集合也是final修飾的,這表明這兩個集合實例也是不會變的,但這同樣無法保證集合中屬性值的不變性,所以,final修飾所針對的就是最外層,他并不會對其內(nèi)部的定義產(chǎn)生影響。
UnpooledDataSource dataSource:擁有一個非池型數(shù)據(jù)源。可以這么說,一個池型數(shù)據(jù)源就是一個非池型數(shù)據(jù)源加上一個連接池,也就是說,池型數(shù)據(jù)源是在非池型數(shù)據(jù)源基礎(chǔ)上發(fā)展而來,是以非池型為基礎(chǔ)的。
int poolMaximumActiveConnections = 10:連接池中最多可擁有的活動連接數(shù),這是最大值,池中保存的活動連接數(shù)不能超過這個值(10個),當(dāng)要超過時,在沒有空閑連接的基礎(chǔ)下,不能在新建連接,而是從活動鏈接中取最老的那個連接進行使用。(這個發(fā)生在推出連接時)
int poolMaximumIdleConnections = 5:連接池中最多可擁有的空閑連接數(shù),這是最大值,池中保存的空閑連接數(shù)不能超過這個值(5個),當(dāng)要超過時,將多出的真實連接直接關(guān)閉,池連接置為無效。(這個發(fā)生在推入連接時)
int poolMaximumCheckoutTime = 20000:連接池最大檢出時間(可以理解為驗證時間),如果一個連接驗證時間超過設(shè)定值,則將這個連接設(shè)置為過期(發(fā)生在推出連接時)
int poolTimeToWait = 20000:池等待時間,當(dāng)需要從池中獲取一個連接時,如果空閑連接數(shù)量為0,而活動連接的數(shù)量也達到了最大值,那么就針對那個最早取出的連接進行檢查驗證(check out),如果驗證成功(即在上面poolMaximumCheckoutTime限定的時間內(nèi)驗證通過),說明這個連接還處于使用狀態(tài),這時取出操作暫停,線程等待限定時間,這個限定時間就是這個參數(shù)的使用位置。
String poolPingQuery = "NO PING QUERY SET":在驗證連接是否有效的時候,對數(shù)據(jù)庫執(zhí)行查詢,查詢內(nèi)容為該設(shè)置內(nèi)容。整個目的就是為了得知這個數(shù)據(jù)庫連接還是否能夠使用(未關(guān)閉,并處于正常狀態(tài)),這是一個偵測查詢。
boolean poolPingEnabled = false:這是一個開關(guān),表示是否打開偵測查詢功能,默認(rèn)為false,表示關(guān)閉該功能。
int poolPingConnectionsNotUsedFor = 0:如果一個連接在限定的時間內(nèi)一直未被使用,那么就要對該連接進行驗證,以確定這個連接是否處于可用狀態(tài)(即進行偵測查詢),這個限定的時間就使用poolPingConnectionsNotUsedFor來設(shè)定,默認(rèn)值為0。
int expectedConnectionTypeCode:連接的類型編碼,這個類型編碼在創(chuàng)建池型數(shù)據(jù)源實例的時候會被組裝,他的組裝需要從數(shù)據(jù)源中獲取連接的url、username、password三個值,將其按順序組合在一起,這個類型編碼可用于區(qū)別連接種類。
上面說到了屬性字段,下面緊接著就說說針對屬性的操作方法:
state:它只有g(shù)et方法,用于獲取池狀態(tài)值,因為他是final的,所以不設(shè)置set方法
dataSource:它的值在構(gòu)造器中創(chuàng)建并進行賦值,是final的,不存在get與set方法。
poolMaximumActiveConnections:擁有set與get方法,可設(shè)置新值,也能獲取其值。
poolMaximumCheckoutTime:擁有set與get方法,可設(shè)置新值,也能獲取其值。
poolTimeToWait:擁有set與get方法,可以設(shè)置新值,也能獲取其值。
poolPingQuery:擁有set與get方法,可以設(shè)置新值,也能獲取其值。
poolPingEnabled:擁有set與get方法,可以設(shè)置新值,也能獲取其值。
poolPingConnectionsNotUsedFor:擁有set與get方法,可以設(shè)置新值,也能獲取其值。
這里有個注意點,在上述的每個set方法中,其中還包括針對UnpooledDataSource中的屬性的set方法重寫之中,都在最后擁有這么一個方法:forceCloseAll()
1 public void setPoolPingConnectionsNotUsedFor(int milliseconds) { 2 this.poolPingConnectionsNotUsedFor = milliseconds; 3 forceCloseAll(); 4 }
這是什么意思呢?讓我們來看看forceCloseAll()這個方法就明白了: