1 回顧
上一文中解讀了MyBatis中非池型數據源的源碼,非池型也就是隻擁有單一數據連接的數據源,他只管理着一個數據連接,這種數據源現在很少使用,一般都是用池型數據源,因爲單個連接的情況下,爲了保證操作的正確性,針對這個連接的使用要進行同步,這樣無疑會拖慢系統運行速度。
而使用池型數據源,在池中保存有多個數據庫連接,可以供多個數據庫訪問線程同時獲取現成的不同的數據庫連接,既保證了數據訪問的安全性,也能極大的提升系統的運行速度。
2 池型數據源
現在的Java項目中多采用池型數據源,C3P0,DBCP之類的也都提供了池型數據源,在MyBatis中也自定義了一種池型數據源PooledDataSource,這個pooled正好與之前的Configuration配置文件中配置的數據源的類型“POOLED”對應。
<dataSource type="POOLED">
2.1 池型數據源工廠
首先我們來看看池型數據源的數據源工廠: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 }
代碼很簡單,內部只有一個方法,用於獲取吃型數據源的實例。果然好簡單,你想錯了......
從類結構就可以看出,這個類繼承了UnpooledDataSourceFactory工廠類,也就是說,PooledDataSourceFactory也擁有UnpooledDataSourceFactory中羅列的諸多方法功能,最主要繼承的功能就是設置屬性的功能,這個功能可以將被讀取到內存中的數據源配置信息保存到數據源實例中。
這個功能之前已經有過介紹,這裏不再贅述。下面看看PooledDataSource數據源。
這裏在介紹PooledDataSource之前需要先對MyBatis自定義的池連接輔助類進行介紹。
2.2 池型連接:PooledConnedtion
class PooledConnection implements InvocationHandler {
不看不知道,一看就明白,這是一個動態代理,採用的是JDK動態代理,說明PooledConnection池連接就是爲了創建一個真實連接的代理,使用連接代理來調用真實連接來進行其他操作。
同時這個代理對連接的功能進行了擴充,將其轉化爲池型連接,即池型的概念與實現有一部分就在這個類中進行,這個類將一個普通的連接包裝成爲一個池型化的連接,使其適用於MyBatis自定義的池型數據源。當然這並不是包裝器模式,明顯的代理模式(動態代理模式)。
下面讓我們來仔細看看如何將一個簡單的連接包裝成爲一個池型連接。
首先我們來看看池型連接擁有哪些屬性?
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":這個很好理解,這只是下方代碼中需要使用到的一個靜態字符串常量而已,表示關閉。
Class<?>[] IFACES = new Class<?>[] {Connection.class}:這個和上面類似,也是下方要使用的一個靜態數組常量,表示連接類型,這裏明顯使用到了多態的概念。Connection是所有連接的祖類,
int hashCode = 0:這是數據庫連接(真實連接)的HashCode值,默認爲0,表示當真實連接不存在即爲null時的值。
PooledDataSource dataSource:池型數據源,爲什麼在池型連接中會需要池型數據源實例呢?在下面你會看到它的應用,它的主要目的還是爲了方便調用其內部定義的部分方法來輔助完成池型連接的一些功能判斷。
Connection realConnection:表示真實的數據庫連接,屬於被代理的對象
Connection proxyConnection:表示使用JDK動態代理創建的代理真實連接的代理連接實例
long checkoutTinmestamp:表示數據庫連接被檢出的時間戳,這個用於計算具體的檢出時間
long createdTimestamp:表示數據庫連接被創建的時間戳,用於計算數據庫連接被創建的時間
long lastUsedTimestamp:表示連接被最後使用的時間戳,用於計算數據庫連接被最後使用的時間
int connectionTypeCode:數據庫連接的類型編碼,格式爲:url+username+password
boolean valid:表示連接是否可用的邏輯值
以上的屬性大多就是在原有的數據庫連接的基礎上做的再包裝,爲其賦予更多的屬性,使其成爲一個不折不扣的池型連接,這就像爲一個人穿上各種衣服,裝飾,學習各種知識能力,最後將其包裝成一個某一方面的專業人士。呵呵,這裏是真正的專業人士,真正擁有專業能力的人士。當然最核心的還是這個人了,在池型連接中也一樣,最核心的當然還是這個真實連接了。
下面看看構造器:
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 }
在這個構造器中需要傳遞兩個參數,分別爲:數據庫連接實例與池型數據源實例,將其分別賦值給對應的屬性,同時初始化其餘屬性的值,最重要的還是最後一項,調用Proxy的newProxyInstance()方法來生成代理連接實例,其參數分別爲:Connection類的類加載器、接口數組、當前類的實例。(這是固定格式,套路,詳情可參見《代理模式之動態代理》、《java靜態代理與動態代理簡單分析》)
在這個動態代理實現中最重要的還是invoke方法的實現:
1 @Override
2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
3 String methodName = method.getName();
4 //如果調用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()方法,其他方法調用之前要檢查connection是否還是合法的,不合法要拋出SQLException
14 checkConnection();
15 }
16 //其他的方法,則交給真正的connection去調用
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 }
我們一句句來看:
首先獲取要調用方法的名稱methodName,然後對這個名稱進行驗證,如果是close方法的話,那麼忽略關閉的實現,轉而將這個連接加到連接池中(推入pushConnection),這表示在池型了數據源中,當連接不再使用後是要返回池中備用的,而不是直接被關閉銷燬。
如果調用的方法是不是close時,則首先進行該方法申明處的判斷,如果這個方法不是來自Object類(剔除toString()方法),那麼對當前的連接的可用性進行判斷,如果不可用(valid值爲false),則拋出SqlException,否則繼續執行下一步,由真實連接進行方法調用。
原理也很簡單,總的來說就是實現方法調用(這也是代理的目的所在),外面看起來是由代理類執行方法,其實內部是由真實連接類來執行方法。
2.3 池狀態類:PoolState
池狀態,顧名思義,用於描述連接池的狀態(或稱爲屬性也行)的類,這個屬性不同於池連接的屬性,這是出於連接池的屬性,這個屬性是作用於整個連接池的,而連接池中包含有限個池連接,一定要明白其中的關係。
在這個類中定義了諸多屬性,但是這些屬性多用於統計信息的輸出,什麼是統計信息呢,就是將池連接的一些信息做一番統計並輸出。所以其中重要的屬性並不多,如下:
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 //----------以下是一些統計信息----------
7 //請求次數
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 //要等待的次數
17 protected long hadToWaitCount = 0;
18 //壞的連接次數
19 protected long badConnectionCount = 0;
20 //構造器
21 public PoolState(PooledDataSource dataSource) {
22 this.dataSource = dataSource;
23 }
上面代碼註釋中解釋的很明白,第一個是數據源實例,這個實例在通過構造器創建實例的時候傳入並賦值,然後及時兩個ArrayList集合分別用於保存空閒的池連接實例與活動(使用)中的池連接實例。其餘的就是一些統計信息,對於這些統計信息,不再詳述,可自行翻看源碼。
這裏做簡單介紹:類中重寫了toString()方法,用於輸出這些信息,在類中用於獲取這些信息的方法都是同步方法,保證線程安全性,保證獲得的是正確的信息。
下面就是重點:池型數據源
2.4 池型數據源:PooledDataSource
這是一個同步的線程安全的數據庫連接池。
其實對於一個有連接池的數據源來說,針對池中數據連接的操作就是定義這個池型數據源的重點所在,那麼針對池型連接的操作有哪些呢?
獲取池連接(推出池連接)
收回池連接(推入池連接)
關閉池連接
在加上一個池連接的可用性判斷,而我們的重點也就集中在這幾點。
首先我們來看看池型數據源擁有那些屬性:
1 public class PooledDataSource implements DataSource {
2
3 private static final Log log = LogFactory.getLog(PooledDataSource.class);
4
5 //有一個池狀態
6 private final PoolState state = new PoolState(this);
7
8 //裏面有一個UnpooledDataSource
9 private final UnpooledDataSource dataSource;
10
11 // OPTIONAL CONFIGURATION FIELDS
12 //正在使用連接的數量
13 protected int poolMaximumActiveConnections = 10;
14 //空閒連接數
15 protected int poolMaximumIdleConnections = 5;
16 //在被強制返回之前,池中連接被檢查的時間
17 protected int poolMaximumCheckoutTime = 20000;
18 //這是給連接池一個打印日誌狀態機會的低層次設置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時間 爲了避免連接池沒有配置時靜默失 敗)。
19 protected int poolTimeToWait = 20000;
20 //發送到數據的偵測查詢,用來驗證連接是否正常工作,並且準備 接受請求。默認是“NO PING QUERY SET” ,這會引起許多數據庫驅動連接由一 個錯誤信息而導致失敗
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 }
結合源碼中的註釋內容可知:
PoolState state = new PoolState(this):擁有一個池狀態屬性,無可厚非,池狀態正是用於描述整個連接池整體的,這裏將當前數據源實例作爲參數賦予池狀態形成一個不變的實例(final修飾的作用),這裏的不變指的是這個池狀態的實例是不變的,但不併意味着池狀態中的各屬性的值也不變,這個要看池狀態類中屬性是如何定義的,查看源碼發現,這兩個集合也是final修飾的,這表明這兩個集合實例也是不會變的,但這同樣無法保證集合中屬性值的不變性,所以,final修飾所針對的就是最外層,他並不會對其內部的定義產生影響。
UnpooledDataSource dataSource:擁有一個非池型數據源。可以這麼說,一個池型數據源就是一個非池型數據源加上一個連接池,也就是說,池型數據源是在非池型數據源基礎上發展而來,是以非池型爲基礎的。
int poolMaximumActiveConnections = 10:連接池中最多可擁有的活動連接數,這是最大值,池中保存的活動連接數不能超過這個值(10個),當要超過時,在沒有空閒連接的基礎下,不能在新建連接,而是從活動鏈接中取最老的那個連接進行使用。(這個發生在推出連接時)
int poolMaximumIdleConnections = 5:連接池中最多可擁有的空閒連接數,這是最大值,池中保存的空閒連接數不能超過這個值(5個),當要超過時,將多出的真實連接直接關閉,池連接置爲無效。(這個發生在推入連接時)
int poolMaximumCheckoutTime = 20000:連接池最大檢出時間(可以理解爲驗證時間),如果一個連接驗證時間超過設定值,則將這個連接設置爲過期(發生在推出連接時)
int poolTimeToWait = 20000:池等待時間,當需要從池中獲取一個連接時,如果空閒連接數量爲0,而活動連接的數量也達到了最大值,那麼就針對那個最早取出的連接進行檢查驗證(check out),如果驗證成功(即在上面poolMaximumCheckoutTime限定的時間內驗證通過),說明這個連接還處於使用狀態,這時取出操作暫停,線程等待限定時間,這個限定時間就是這個參數的使用位置。
String poolPingQuery = "NO PING QUERY SET":在驗證連接是否有效的時候,對數據庫執行查詢,查詢內容爲該設置內容。整個目的就是爲了得知這個數據庫連接還是否能夠使用(未關閉,並處於正常狀態),這是一個偵測查詢。
boolean poolPingEnabled = false:這是一個開關,表示是否打開偵測查詢功能,默認爲false,表示關閉該功能。
int poolPingConnectionsNotUsedFor = 0:如果一個連接在限定的時間內一直未被使用,那麼就要對該連接進行驗證,以確定這個連接是否處於可用狀態(即進行偵測查詢),這個限定的時間就使用poolPingConnectionsNotUsedFor來設定,默認值爲0。
int expectedConnectionTypeCode:連接的類型編碼,這個類型編碼在創建池型數據源實例的時候會被組裝,他的組裝需要從數據源中獲取連接的url、username、password三個值,將其按順序組合在一起,這個類型編碼可用於區別連接種類。
上面說到了屬性字段,下面緊接着就說說針對屬性的操作方法:
state:它只有get方法,用於獲取池狀態值,因爲他是final的,所以不設置set方法
dataSource:它的值在構造器中創建並進行賦值,是final的,不存在get與set方法。
poolMaximumActiveConnections:擁有set與get方法,可設置新值,也能獲取其值。
poolMaximumCheckoutTime:擁有set與get方法,可設置新值,也能獲取其值。
poolTimeToWait:擁有set與get方法,可以設置新值,也能獲取其值。
poolPingQuery:擁有set與get方法,可以設置新值,也能獲取其值。
poolPingEnabled:擁有set與get方法,可以設置新值,也能獲取其值。
poolPingConnectionsNotUsedFor:擁有set與get方法,可以設置新值,也能獲取其值。
這裏有個注意點,在上述的每個set方法中,其中還包括針對UnpooledDataSource中的屬性的set方法重寫之中,都在最後擁有這麼一個方法:forceCloseAll()
1 public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
2 this.poolPingConnectionsNotUsedFor = milliseconds;
3 forceCloseAll();
4 }
這是什麼意思呢?讓我們來看看forceCloseAll()這個方法就明白了:
1 /*
2 * Closes all active and idle connections in the pool
3 */
4 public void forceCloseAll() {
5 synchronized (state) {
6 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
7 //關閉所有的activeConnections和idleConnections
8 for (int i = state.activeConnections.size(); i > 0; i--) {
9 try {
10 PooledConnection conn = state.activeConnections.remove(i - 1);
11 conn.invalidate();
12
13 Connection realConn = conn.getRealConnection();
14 if (!realConn.getAutoCommit()) {
15 realConn.rollback();
16 }
17 realConn.close();
18 } catch (Exception e) {
19 // ignore
20 }
21 }
22 for (int i = state.idleConnections.size(); i > 0; i--) {
23 try {
24 PooledConnection conn = state.idleConnections.remove(i - 1);
25 conn.invalidate();
26
27 Connection realConn = conn.getRealConnection();
28 if (!realConn.getAutoCommit()) {
29 realConn.rollback();
30 }
31 realConn.close();
32 } catch (Exception e) {
33 // ignore
34 }
35 }
36 }
37 if (log.isDebugEnabled()) {
38 log.debug("PooledDataSource forcefully closed/removed all connections.");
39 }
40 }
註釋很明白:關閉池中所有活動的和空閒的連接。代碼也很簡潔明瞭,就是將池狀態中的兩個集合中保存的池連接全部值爲無效,所有的真是連接全部關閉。但是重要的不是這個做法,而是爲什麼要這麼做?當我們呢重新設置一個參數時,就需要將所有的連接全部關閉。
其實也很好理解:連接是在數據源完全設置完整的情況下才生成的,數據源就是連接生成的基礎,是連接存在的田地,當我們要修改數據源的基礎屬性的時候,原有設置上產生的連接必定不再適合新的設置,需要全部推倒重來,這裏就是這個意思。
下面我們來看看構造器:
1 public PooledDataSource() {
2 dataSource = new UnpooledDataSource();
3 }
4
5 public PooledDataSource(String driver, String url, String username, String password) {
6 dataSource = new UnpooledDataSource(driver, url, username, password);
7 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
8 }
9
10 public PooledDataSource(String driver, String url, Properties driverProperties) {
11 dataSource = new UnpooledDataSource(driver, url, driverProperties);
12 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
13 }
14
15 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
16 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
17 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
18 }
19
20 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
21 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
22 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
23 }
從上面列出的構造器中可以看出,池型數據源的根本還是非池型數據源,池型就是在非池型的基礎上加上池型的概念與實現。我們在創建池型數據源實例的時候首先會創建一個非池型數據源的實例並將其賦值給參數。五種構造器也是根據非池型數據源的五種構造器而來,一一對應,只是在後四個構造器中增加了連接類型編碼的組裝。這個連接類型編碼適用於區分連接種類的。
下面介紹PooledDataSource中最重要的方法,推入(popConnection)與推出(pushConnection)方法。
推出方法是由getConnection()方法調用的。
1 @Override
2 public Connection getConnection() throws SQLException {
3 //覆蓋了DataSource.getConnection方法,每次都是pop一個Connection,即從池中取出一個來
4 return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
5 }
6
7 @Override
8 public Connection getConnection(String username, String password) throws SQLException {
9 return popConnection(username, password).getProxyConnection();
10 }
1 private PooledConnection popConnection(String username, String password) throws SQLException {
2 boolean countedWait = false;
3 PooledConnection conn = null;
4 long t = System.currentTimeMillis();
5 int localBadConnectionCount = 0;
6
7 //最外面是while死循環,如果一直拿不到connection,則不斷嘗試
8 while (conn == null) {
9 synchronized (state) {
10 if (!state.idleConnections.isEmpty()) {
11 //如果有空閒的連接的話
12 // Pool has available connection
13 //刪除空閒列表裏第一個,返回
14 conn = state.idleConnections.remove(0);
15 if (log.isDebugEnabled()) {
16 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
17 }
18 } else {
19 //如果沒有空閒的連接
20 // Pool does not have available connection
21 if (state.activeConnections.size() < poolMaximumActiveConnections) {
22 //如果activeConnections太少,那就new一個PooledConnection
23 // Can create new connection
24 conn = new PooledConnection(dataSource.getConnection(), this);
25 if (log.isDebugEnabled()) {
26 log.debug("Created connection " + conn.getRealHashCode() + ".");
27 }
28 } else {
29 //如果activeConnections已經很多了,那不能再new了
30 // Cannot create new connection
31 //取得activeConnections列表的第一個(最老的)
32 PooledConnection oldestActiveConnection = state.activeConnections.get(0);
33 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
34 if (longestCheckoutTime > poolMaximumCheckoutTime) {
35 //如果checkout時間過長,則這個connection標記爲overdue(過期)
36 // Can claim overdue connection
37 state.claimedOverdueConnectionCount++;
38 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
39 state.accumulatedCheckoutTime += longestCheckoutTime;
40 state.activeConnections.remove(oldestActiveConnection);
41 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
42 oldestActiveConnection.getRealConnection().rollback();
43 }
44 //刪掉最老的連接,然後再new一個新連接
45 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
46 oldestActiveConnection.invalidate();
47 if (log.isDebugEnabled()) {
48 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
49 }
50 } else {
51 //如果checkout時間不夠長,等待吧
52 // Must wait
53 try {
54 if (!countedWait) {
55 //統計信息:等待+1
56 state.hadToWaitCount++;
57 countedWait = true;
58 }
59 if (log.isDebugEnabled()) {
60 log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
61 }
62 long wt = System.currentTimeMillis();
63 //睡一會兒吧
64 state.wait(poolTimeToWait);
65 state.accumulatedWaitTime += System.currentTimeMillis() - wt;
66 } catch (InterruptedException e) {
67 break;
68 }
69 }
70 }
71 }
72 if (conn != null) {
73 //如果已經拿到connection,則返回
74 if (conn.isValid()) {
75 if (!conn.getRealConnection().getAutoCommit()) {
76 conn.getRealConnection().rollback();
77 }
78 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
79 //記錄checkout時間
80 conn.setCheckoutTimestamp(System.currentTimeMillis());
81 conn.setLastUsedTimestamp(System.currentTimeMillis());
82 state.activeConnections.add(conn);
83 state.requestCount++;
84 state.accumulatedRequestTime += System.currentTimeMillis() - t;
85 } else {
86 if (log.isDebugEnabled()) {
87 log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
88 }
89 //如果沒拿到,統計信息:壞連接+1
90 state.badConnectionCount++;
91 localBadConnectionCount++;
92 conn = null;
93 if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
94 //如果好幾次都拿不到,就放棄了,拋出異常
95 if (log.isDebugEnabled()) {
96 log.debug("PooledDataSource: Could not get a good connection to the database.");
97 }
98 throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
99 }
100 }
101 }
102 }
103
104 }
105
106 if (conn == null) {
107 if (log.isDebugEnabled()) {
108 log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
109 }
110 throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
111 }
112
113 return conn;
114 }
結合註釋內容,我對上面的執行過程進行簡單的描述:
(1) 這是個同步方法,線程安全。但是將synchronized鎖同步放置到循環內部,而不是循環之外的原因是因爲:如果將同步鎖放置在循環之外,當多個線程執行到鎖的位置,其中一個線程獲得鎖然後開始執行循環,如果發生問題導致無限循環,那麼這個鎖將是一直被這個線程所持有,導致其他線程永久處於等待鎖的狀態,程序無法執行下去。而將鎖放置到循環內部,當多個線程來到鎖之前,其中一個線程獲得鎖,執行循環內部代碼,當執行完成一次循環,無論成功失敗,都會釋放鎖,而其他線程就可以獲得鎖進而執行。
(2) 首先驗證空閒連接集合是否爲空(驗證是否還有空閒連接備用),如果存在空閒連接,那麼直接獲取這個空閒連接,將這個連接從空閒連接集合中刪除。
(3) 如果沒有空閒連接,那麼就驗證活動連接集合中連接的數量是否達到最大值(poolMaximumActiveConnections),如果未達到最大值,這時,我們可以直接創建一個新的池型連接(需要一個真實連接於與一個池型數據源實例作爲參數)
(4) 如果活動連接集合中的連接數目已經達到最大值(poolMaximumActiveConnections),那麼就針對最早的那個活動連接(即在集合中排在0位的那個連接實例)進行驗證。並獲取其驗證時間間隔值(該連接上一次記錄驗證時間戳到當前時間的間隔),將其與池連接的最大驗證時限(poolMaximumCheckoutTime)進行比較,如果前者大,說明針對這個連接距上一次記錄驗證時間戳的時間超過了限定時限,這時將這個老連接從活動連接集合中刪除,並新建一個池連接,還以老連接所代理的真實連接爲真實連接(實際上就是創建一個新的代理),並將老的池連接設爲無效。
(5) 如果驗證時間與顯示時間比較結果爲驗證時間小於限定時限(這個限定時限的設置需要根據項目實際情況來設置,或通過經驗來設置,確保在這個時間之內連接的數據庫操作執行完畢,不然貿然將連接關閉會導致原本的數據庫操作失敗),說明這個連接還可能處於使用狀態,這時候只有等待一途,這裏將線程設置等待限定秒數(poolTimeToWait),線程進入等待狀態,那麼就會釋放同步鎖,此時其他線程就能獲得鎖來進行執行。當前線程在等待N秒之後自動進入準備狀態準備重新獲得鎖。
(6) 然後就獲得的連接進行判斷,如果連接不爲空,那麼驗證連接是否可用(isValid),如果連接可用則設置連接類型編碼,並記錄驗證時間戳(setCheckoutTimestamp)與最後一次使用時間戳(setLastUsedTimestamp),這兩個時間戳可用於計算該連接的驗證時間與最後一次使用時間,在前面會使用到這些值進行判斷。再然後將該鏈接添加到活動連接集合中。
(7)如果獲取的連接爲空,或者說沒有獲取到連接,則壞連接數加1,將連接置null,並驗證壞連接數值,如果比當前空閒連接數量+3都大的話,那麼就放棄獲取連接,並拋出SqlException,(拋出異常也就意味着執行的終止,這個線程將不再執行循環操作)
下面看看推入連接方法:
1 protected void pushConnection(PooledConnection conn) throws SQLException {
2
3 synchronized (state) {
4 //先從activeConnections中刪除此connection
5 state.activeConnections.remove(conn);
6 if (conn.isValid()) {
7 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
8 //如果空閒的連接太少,
9 state.accumulatedCheckoutTime += conn.getCheckoutTime();
10 if (!conn.getRealConnection().getAutoCommit()) {
11 conn.getRealConnection().rollback();
12 }
13 //new一個新的Connection,加入到idle列表
14 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
15 state.idleConnections.add(newConn);
16 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
17 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
18 conn.invalidate();
19 if (log.isDebugEnabled()) {
20 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
21 }
22 //通知其他線程可以來搶connection了
23 state.notifyAll();
24 } else {
25 //否則,即空閒的連接已經足夠了
26 state.accumulatedCheckoutTime += conn.getCheckoutTime();
27 if (!conn.getRealConnection().getAutoCommit()) {
28 conn.getRealConnection().rollback();
29 }
30 //那就將connection關閉就可以了
31 conn.getRealConnection().close();
32 if (log.isDebugEnabled()) {
33 log.debug("Closed connection " + conn.getRealHashCode() + ".");
34 }
35 conn.invalidate();
36 }
37 } else {
38 if (log.isDebugEnabled()) {
39 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
40 }
41 state.badConnectionCount++;
42 }
43 }
44 }
同樣的,我們結合註釋內容進行解析:
(1) 這個方法同樣是一個同步方法,擁有同步鎖,以池狀態實例爲鎖。
(2) 首先我們將當前要推入的連接實例從活動連接中刪除,表示其不再處於使用狀態。
(3) 然後對連接額可用性進行(valid)判斷,如果還處於可用狀態,則驗證空閒連接集合中的空閒連接數量是否小於設置的限定值(poolMaximumIdleConnections)和當前連接實例的類型編碼是否與當前池型數據源中的連接類型編碼一致,如果上面兩點都滿足,則進行下一步:
(4) 新建一個池型連接實例並將其添加到空閒連接集合中,這個池型連接實例是以之前要推入的連接爲基礎重新創建的,也就是說是針對那個要推入的池型連接的真實連接重新創建一個池型連接代理(只改變外包裝,實質不改變),並將原池型連接的時間戳設置統統設置到新的連接中,保持連接的持續性,然後將原池型連接置爲無效。
(5) 然後喚醒所有沉睡線程notifyAll()。
(6) 如果第(3)點中的判斷中有一個不成立(空閒連接數量達到最大值或者連接的類型編碼不一致)那麼直接將該連接的真實連接關閉,池連接置爲無效即可。
這個方法比較簡單,條理也很清楚。
下面討論池型數據源中的最後一個方法:pingConnection()
1 protected boolean pingConnection(PooledConnection conn) {
2 boolean result = true;
3
4 try {
5 result = !conn.getRealConnection().isClosed();
6 } catch (SQLException e) {
7 if (log.isDebugEnabled()) {
8 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
9 }
10 result = false;
11 }
12
13 if (result) {
14 if (poolPingEnabled) {
15 if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
16 try {
17 if (log.isDebugEnabled()) {
18 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
19 }
20 Connection realConn = conn.getRealConnection();
21 Statement statement = realConn.createStatement();
22 ResultSet rs = statement.executeQuery(poolPingQuery);
23 rs.close();
24 statement.close();
25 if (!realConn.getAutoCommit()) {
26 realConn.rollback();
27 }
28 result = true;
29 if (log.isDebugEnabled()) {
30 log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
31 }
32 } catch (Exception e) {
33 log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
34 try {
35 conn.getRealConnection().close();
36 } catch (Exception e2) {
37 //ignore
38 }
39 result = false;
40 if (log.isDebugEnabled()) {
41 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
42 }
43 }
44 }
45 }
46 }
47 return result;
48 }
這個方法也很是簡單,它的目的就是爲了驗證某一個連接是否任然可用,它會被池連接PooledConnection類中的isValid()方法調用,用於判斷一個連接是否還可用。這個方法就是真正用於判斷連接可用與否的功能性方法。
(1) 首先創建一個局部變量result用於保存判斷結果,默認爲true
(2) 然後將當前池型連接包裹的真實連接的開閉狀態值的非值賦值給result(當真實連接處於關閉狀態時,result值爲false,當真實連接處於開啓狀態時,result值爲true),如果賦值過程出現了異常,則直接將result置false
(3) 判斷result的值,如果result值爲true,則判斷poolPingEnabled的值,這是偵測查詢的開關,如果這個值爲true,表示開啓偵測查詢,那麼就可以執行以下內容。
(4) 判斷poolPingConnectionsNotUsedFor的值是否大於等於0(這個判斷的意思是判斷是否設置了正確的poolPingConnectionsNotUsedFor值),並且判斷該連接的自最後一次使用以來的時間間隔是否大於設定的poolPingConnectionsNotUsedFor值(驗證該連接是否到了需要進行偵測查詢的時間,如果小於設置時間則不進行偵測查詢)
(5) 如果上述條件均滿足,則進行一次偵測查詢,這個偵測查詢就是針對這個連接的一個測試查詢,看看整個查詢過程是否通暢,若通暢(沒有任何異常出現),則將result置爲true,一旦測試過程出現了異常,則將該連接的真實連接關閉,並將result置爲false
2.5 總結
MyBatis自行實現的池型數據源簡單易懂,代碼簡潔明快,單功能較爲簡單,無法與那些較爲專業的如dbcp和c3p0相媲美,但是滿足日常需要還是足夠了,這其中涉及到的有關池型的概念更是有助於我們真正瞭解Java中池的實現方法。