MyBatis 的配置文件全部配置會被 org.apache.ibatis.builder.xml.XMLConfigBuilder
類讀取,
咱們能夠經過此類來了解各個配置是如何運做的。
而 MyBatis 的映射文件配置會被 org.apache.ibatis.builder.xml.XMLMapperBuilder
類讀取。
咱們能夠經過此類來了解映射文件的配置時如何被解析的。java
本文探討 事務管理器 和 數據源 相關代碼
如下是 mybatis 配置文件中 environments 節點的通常配置。sql
<!-- mybatis-config.xml --> <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>
environments 節點的加載也不算複雜,它只會加載 id 爲 development 屬性值的 environment 節點。
它的加載代碼在 XMLConfigBuilder
類的 environmentsElement()
方法中,代碼很少,邏輯也簡單,此處很少講。數據庫
接下來咱們看看 environment 節點下的子節點。transactionManager 節點的 type 值默認提供有 JDBC 和 MANAGED ,dataSource 節點的 type 值默認提供有 JNDI 、 POOLED 和 UNPOOLED 。
它們對應的類均可以在 Configuration
類的構造器中找到,固然下面咱們也一個一個來分析。apache
如今咱們大概瞭解了配置,而後來分析這些配置與 MyBatis 類的關係。緩存
transactionManager 節點對應 TransactionFactory
接口,使用了 抽象工廠模式 。MyBatis 給咱們提供了兩個實現類:ManagedTransactionFactory
和 JdbcTransactionFactory
,它們分別對應者 type 屬性值爲 MANAGED 和 JDBC 。mybatis
TransactionFactory 有三個方法,咱們須要注意的方法只有 newTransaction()
,它用來建立一個事務對象。app
void setProperties(Properties props); Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
其中 JdbcTransactionFactory
建立的事務對象是 JdbcTransaction
的實例,該實例是對 JDBC 事務的簡單封裝,實例中 Connection
和 DataSource
對象正是事務所在的 鏈接 和 數據源 。TransactionIsolationLevel
表明當前事務的隔離等級,它是一個枚舉類,簡單明瞭無需多言。而 autoCommit 表示是否開啓了自動提交,開啓了,則沒有事務的提交和回滾等操做的意義了。ide
ManagedTransactionFactory
建立的事務對象是 ManagedTransaction
的實例,它自己並不控制事務,即 commit
和 rollback
都是不作任何操做,而是交由 JavaEE 容器來控制事務,以方便集成。oop
DataSourceFactory
是獲取數據源的接口,也使用了 抽象工廠模式 ,代碼以下,方法極爲簡單:ui
public interface DataSourceFactory { /** * 可傳入一些屬性配置 */ void setProperties(Properties props); DataSource getDataSource(); }
MyBatis 默認支持三種數據源,分別是 UNPOOLED 、 POOLED 和 JNDI 。對應三個工廠類:UnpooledDataSourceFactory
、 PooledDataSourceFactory
和 JNDIDataSourceFactory
。
其中 JNDIDataSourceFactory
是使用 JNDI 來獲取數據源。咱們不多使用,而且代碼不是很是複雜,此處不討論。咱們先來看看 UnpooledDataSourceFactory
:
public class UnpooledDataSourceFactory implements DataSourceFactory { private static final String DRIVER_PROPERTY_PREFIX = "driver."; private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } @Override public void setProperties(Properties properties) { Properties driverProperties = new Properties(); // MetaObject 用於解析實例對象的元信息,如字段的信息、方法的信息 MetaObject metaDataSource = SystemMetaObject.forObject(dataSource); // 解析全部配置的鍵值對key-value,發現非預期的屬性當即拋異常,以便及時發現 for (Object key : properties.keySet()) { String propertyName = (String) key; if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { // 添加驅動的配置屬性 String value = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); } else if (metaDataSource.hasSetter(propertyName)) { // 爲數據源添加配置屬性 String value = (String) properties.get(propertyName); Object convertedValue = convertValue(metaDataSource, propertyName, value); metaDataSource.setValue(propertyName, convertedValue); } else { throw new DataSourceException("Unknown DataSource property: " + propertyName); } } if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } } @Override public DataSource getDataSource() { return dataSource; } /** * 將 String 類型的值轉爲目標對象字段的類型的值 */ private Object convertValue(MetaObject metaDataSource, String propertyName, String value) { Object convertedValue = value; Class<?> targetType = metaDataSource.getSetterType(propertyName); if (targetType == Integer.class || targetType == int.class) { convertedValue = Integer.valueOf(value); } else if (targetType == Long.class || targetType == long.class) { convertedValue = Long.valueOf(value); } else if (targetType == Boolean.class || targetType == boolean.class) { convertedValue = Boolean.valueOf(value); } return convertedValue; } }
雖然代碼看起來複雜,實際上很是簡單,在建立工廠實例時建立它對應的 UnpooledDataSource
數據源。setProperties()
方法用於給數據源添加部分屬性配置,convertValue()
方式時一個私有方法,就是處理 當 DataSource
的屬性爲整型或布爾類型時提供對字符串類型的轉換功能而已。
最後咱們看看 PooledDataSourceFactory
,這個類很是簡單,僅僅是繼承了 UnpooledDataSourceFactory
,而後構造方法替換數據源爲 PooledDataSource
。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } }
雖然它的代碼極少,實際上都在 PooledDataSource
類中。
看完了工廠類,咱們來看看 MyBatis 提供的兩種數據源類: UnpooledDataSource
和 PooledDataSource
。
UnpooledDataSource
看名字就知道是沒有池化的特徵,相對也簡單點,如下代碼省略一些不重要的方法
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class UnpooledDataSource implements DataSource { private ClassLoader driverClassLoader; private Properties driverProperties; private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); private String driver; private String url; private String username; private String password; private Boolean autoCommit; // 事務隔離級別 private Integer defaultTransactionIsolationLevel; static { // 遍歷全部可用驅動 Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } // ...... private Connection doGetConnection(Properties properties) throws SQLException { // 每次獲取鏈接都會檢測驅動 initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; } /** * 初始化驅動,這是一個 同步 方法 */ private synchronized void initializeDriver() throws SQLException { // 若是不包含驅動,則準備添加驅動 if (!registeredDrivers.containsKey(driver)) { Class<?> driverType; try { // 加載驅動 if (driverClassLoader != null) { driverType = Class.forName(driver, true, driverClassLoader); } else { driverType = Resources.classForName(driver); } Driver driverInstance = (Driver)driverType.newInstance(); // 註冊驅動代理到 DriverManager DriverManager.registerDriver(new DriverProxy(driverInstance)); // 緩存驅動 registeredDrivers.put(driver, driverInstance); } catch (Exception e) { throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); } } } private void configureConnection(Connection conn) throws SQLException { // 設置是否自動提交事務 if (autoCommit != null && autoCommit != conn.getAutoCommit()) { conn.setAutoCommit(autoCommit); } // 設置 事務隔離級別 if (defaultTransactionIsolationLevel != null) { conn.setTransactionIsolation(defaultTransactionIsolationLevel); } } private static class DriverProxy implements Driver { private Driver driver; DriverProxy(Driver d) { this.driver = d; } /** * Driver 僅在 JDK7 中定義了本方法,用於返回本驅動的全部日誌記錄器的父記錄器 * 我的也不是十分明確它的用法,畢竟不多會關注驅動的日誌 */ public Logger getParentLogger() { return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); } // 其餘方法均爲調用 driver 對應的方法,此處省略 } }
這裏 DriverProxy
僅被註冊到 DriverManager
中,這是一個代理操做,但源碼上並無什麼特別的處理代碼,我也不懂官方爲何在這裏加代理,有誰明白的能夠留言相互討論。這裏的其餘方法也不是很是複雜,我都已經標有註釋,應該均可以看懂,再也不細說。
以上即是 UnpooledDataSource
的初始化驅動和獲取鏈接關鍵代碼。
接下來咱們來看最後一個類 PooledDataSource
,它也是直接實現 DataSource
,不過由於擁有池化的特性,它的代碼複雜很多,固然效率比 UnpooledDataSource
會高出很多。
PooledDataSource
經過兩個輔助類 PoolState
和 PooledConnection
來完成池化功能。PoolState
是記錄鏈接池運行時的狀態,定義了兩個 PooledConnection
集合用於記錄空閒鏈接和活躍鏈接。PooledConnection
內部定義了兩個 Connection
分別表示一個真實鏈接和代理鏈接,還有一些其餘字段用於記錄一個鏈接的運行時狀態。
先來詳細瞭解一下 PooledConnection
/** * 此處使用默認的訪問權限 * 實現了 InvocationHandler */ class PooledConnection implements InvocationHandler { private static final String CLOSE = "close"; private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; /** hashCode() 方法返回 */ private final int hashCode; private final Connection realConnection; private final Connection proxyConnection; // 省略 checkoutTimestamp、createdTimestamp、lastUsedTimestamp private boolean valid; /* * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in * * @param connection - the connection that is to be presented as a pooled connection * @param dataSource - the dataSource that the connection is from */ public PooledConnection(Connection connection, PooledDataSource dataSource) { this.hashCode = connection.hashCode(); this.realConnection = connection; this.dataSource = dataSource; this.createdTimestamp = System.currentTimeMillis(); this.lastUsedTimestamp = System.currentTimeMillis(); this.valid = true; this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } /* * 設置鏈接狀態爲不正常,不可以使用 */ public void invalidate() { valid = false; } /* * 查看鏈接是否可用 * * @return 若是可用則返回 true */ public boolean isValid() { return valid && realConnection != null && dataSource.pingConnection(this); } /** * 自動上一次使用後通過的時間 */ public long getTimeElapsedSinceLastUse() { return System.currentTimeMillis() - lastUsedTimestamp; } /** * 存活時間 */ public long getAge() { return System.currentTimeMillis() - createdTimestamp; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 對於 close() 方法,將鏈接放回池中 dataSource.pushConnection(this); return null; } else { try { if (!Object.class.equals(method.getDeclaringClass())) { checkConnection(); } return method.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } private void checkConnection() throws SQLException { if (!valid) { throw new SQLException("Error accessing PooledConnection. Connection is invalid."); } } }
本類實現了 InvocationHandler
接口,這個接口是用於 JDK 動態代理的,在這個類的構造器中 proxyConnection 就是建立了此代理對象。
來看看 invoke()
方法,它攔截了 close()
方法,再也不關閉鏈接,而是將其繼續放入池中,而後其餘已實現的方法則是每次調用都須要檢測鏈接是否合法。
而 PoolState
類,這個類實際上沒什麼可說的,都是一些統計字段,沒有複雜邏輯,不討論; 須要注意該類是針對一個 PooledDataSource
對象統計的 。
也就是說 PoolState
的統計字段是關於整個數據源的,而一個 PooledConnection
則是針對單個鏈接的。
最後咱們回過頭來看 PooledDataSource
類,數據源的操做就只有兩個,獲取鏈接,釋放鏈接,先來看看獲取鏈接
public class PooledDataSource implements DataSource { private final UnpooledDataSource dataSource; @Override public Connection getConnection() throws SQLException { return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return popConnection(username, password).getProxyConnection(); } /** * 獲取一個鏈接 */ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; // conn == null 也多是沒有得到鏈接,被通知後再次走流程 while (conn == null) { synchronized (state) { // 是否存在空閒鏈接 if (!state.idleConnections.isEmpty()) { // 池裏存在空閒鏈接 conn = state.idleConnections.remove(0); } else { // 池裏不存在空閒鏈接 if (state.activeConnections.size() < poolMaximumActiveConnections) { // 池裏的激活鏈接數小於最大數,建立一個新的 conn = new PooledConnection(dataSource.getConnection(), this); } else { // 最壞的狀況,沒法獲取鏈接 // 檢測最先使用的鏈接是否超時 PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // 使用超時鏈接,對超時鏈接的操做進行回滾 state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) { /* * Just log a message for debug and continue to execute the following statement * like nothing happened. Wrap the bad connection with a new PooledConnection, * this will help to not interrupt current executing thread and give current * thread a chance to join the next competition for another valid/good database * connection. At the end of this loop, bad {@link @conn} will be set as null. */ log.debug("Bad connection. Could not roll back"); } } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); oldestActiveConnection.invalidate(); } else { // 等待可用鏈接 try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } 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()); // 激活鏈接池數+1 state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { // 鏈接壞掉了,超過必定閾值則拋異常提醒 state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) { // 省略日誌 throw new SQLException( "PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { // 省略日誌 throw new SQLException( "PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; } }
上面的代碼都已經加了註釋,整體流程不算複雜:
while => 鏈接爲空
釋放鏈接操做,更爲簡單,判斷更少
protected void pushConnection(PooledConnection conn) throws SQLException { // 同步操做 synchronized (state) { // 從活動池中移除鏈接 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(); } // 廢棄原先的對象 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); state.idleConnections.add(newConn); newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); // 該對象已經不能用於鏈接了 conn.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); } state.notifyAll(); } else { state.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } // 關閉鏈接 conn.getRealConnection().close(); if (log.isDebugEnabled()) { log.debug("Closed connection " + conn.getRealHashCode() + "."); } conn.invalidate(); } } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } state.badConnectionCount++; } } }
部分碼註釋已添加,這裏就說一下整體流程:
若是該鏈接可用
整體流程大概就是這樣
如下還有兩個方法代碼較多,但邏輯都很簡單,稍微說明一下:
pingConnection()
執行一條 SQL 檢測鏈接是否可用。forceCloseAll()
回滾並關閉激活鏈接池和空閒鏈接池中的鏈接