在描述mybatis數據源之前,先拋出幾個問題,這幾個問題都能在本文得到解答
1.mybatis是如何獲取到mysql連接的? 2.mybatis的Connection是怎么被創建的?
1.Datasource的分類
我們已一段mybatis的配置文件為例
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
datasource的type共有三個選項
UNPOOLED 不使用連接池的數據源
POOLED 使用連接池的數據源
JNDI 使用JNDI實現的數據源
2.Datasource的配置加載與創建
mybatis在項目啟動階段會加載配置文件,讀取xml中的配置信息到Configuration中。我們看下datasource是怎么加載進來的。這段代碼在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
點進去,找到這個方法。
這里通過工廠模式,反射生成一個DatasourceFactory。
這里DataSourceFactory有三個子類,也就是上述三個datasource的類型。
3.Connection的創建
當Datasource的配置加載到configuration后。每一次執行sql都需要Datasource對象創建Connection去連接數據庫。
我們以一次查詢為例,看下這個connection到底是怎么生成的。
transaction對象持有connection和datasource,我們再點進去
終於看到了真正獲取connection是datasource,那datasource是怎么獲取connection的呢?
4.連接池
datasource有三種類型,相對應的每種datasource創建connection各不相同。常見的是POOLEDA和UNPOOLED兩種類型。
UNPOOLED顧名思義就是不用連接池的方式,每次用到一個就生產一個
POOLEDA類型采用了連接池的方式,內部通過Poolstate對象來維護連接池對象
Poolstate內部有兩個數組,idleConnections用來存放空閑的connection,activeConnections用來存放連接中的connection。
具體的獲取一個Connection連接如下
private final PoolState state = new PoolState(this); private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; while (conn == null) { //這個對象鎖鎖的范圍有點大,不過這也是因為PoolState內部的兩個集合是ArrayList會產生並發問題 synchronized (state) { //有空閑連接 if (state.idleConnections.size() > 0) { // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection conn = new PooledConnection(dataSource.getConnection(), this); @SuppressWarnings("unused") //used in logging, if enabled Connection realConn = conn.getRealConnection(); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // Cannot create new connection PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { oldestActiveConnection.getRealConnection().rollback(); } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // Must wait try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } if (conn != null) { if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; }
1. 先看是否有空閑(idle)狀態下的PooledConnection對象,如果有,就直接返回一個可用的PooledConnection對象;否則進行第2步。
2. 查看活動狀態的PooledConnection池activeConnections是否已滿;如果沒有滿,則創建一個新的PooledConnection對象,然后放到activeConnections池中,然后返回此PooledConnection對象;否則進行第三步;
3. 看最先進入activeConnections池中的PooledConnection對象是否已經過期:如果已經過期,從activeConnections池中移除此對象,然后創建一個新的PooledConnection對象,添加到activeConnections中,然后將此對象返回;否則進行第4步。
4. 線程等待,循環2步
以上描述的是通過連接池獲取connection,那在關閉connection后,參照獲取連接的步驟,首先從activeConnections中移除,再判斷idle數組是否已經滿了,如果滿了再判斷下數組中第一個連接是否已經任然可用,如果可用再把這個這個連接銷毀。
上文我們提到了PoolState
兩個集合里面存放着PooledConnection對象,PooledConnection對象是Connection的代理類
PooledConnection相比Connection多了什么呢,答案就在invoke方法中,當connection對象調用close方法時,會調用datasource的pushConnection方法,pushConnection的方法大致和上面的猜想一致