前言:咱們使用mybatis時,關於數據源的配置多使用如c3p0,druid等第三方的數據源。其實mybatis內置了數據源的實現,提供了鏈接數據庫,池的功能。在分析了緩存和日誌包的源碼後,接下來分析mybatis中的數據源實現。html
類圖:mybatis中關於數據源的源碼包路徑以下:java
mybatis中提供了一個DataSourceFactory接口,提供了設置數據源配置信息,獲取數據源方法。查看類圖可知,有三個實現類分別提供了不一樣的數據源實現。JndiDataSourceFactory,PooledDataSourceFactory,unPooledDataSourceFactory。JndiDataSourceFactory實現較簡單,此處源碼略過。以下爲各種的相互關係。sql
unPooledDataSourceFactory,PooledDataSourceFactory源碼分析:unPooledDataSourceFactory實現了DataSourceFactory接口,實現了數據源配置及獲取數據源方法。數據庫
// 對外提供的數據源工廠接口 public interface DataSourceFactory { // 設置配置信息 void setProperties(Properties props); // 獲取數據源 DataSource getDataSource(); }
// 非池化的數據源工廠類 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(); // 構造一個非池化的數據源(下文分析數據源詳細代碼) } public void setProperties(Properties properties) { // 對數據源進行配置,此處設計反射包的知識(本章重點不在這,可忽略) Properties driverProperties = new Properties(); MetaObject metaDataSource = SystemMetaObject.forObject(dataSource); // 將dataSource類轉爲metaObject類 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); // driverProperties存儲數據庫驅動參數 } else if (metaDataSource.hasSetter(propertyName)) { // 若是有set方法 String value = (String) properties.get(propertyName); // 根據屬性類型進行類型的轉換,主要是 Integer, Long, Boolean 三種類型的轉換 Object convertedValue = convertValue(metaDataSource, propertyName, value); // 設置DataSource 的相關屬性值 metaDataSource.setValue(propertyName, convertedValue); } else { throw new DataSourceException("Unknown DataSource property: " + propertyName); } } // 設置 DataSource.driverProerties 屬性值 if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } } // 獲取數據源 public DataSource getDataSource() { return dataSource; } // 對Integer, Long, Boolean 三種類型的轉換 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; } }
public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { // dataSource實現類變爲PooledDataSource this.dataSource = new PooledDataSource(); } }
unPooledDataSourceFactory主要工做是對數據源進行參數配置,並提供獲取數據源方法。分析PooledDataSourceFactory源碼,只是繼承unPooledDataSourceFactory,將DataSource實現類改變爲PooledDataSource。編程
unPooledDataSource源碼分析:基本的數據源實現都實現了DataSource接口,重寫獲取數據庫鏈接的方法。unPooledDataSource從類名可知,不支持數據庫鏈接的池化。也就是說,每來一個獲取鏈接請求,就新建一個數據庫鏈接。讓咱們看源碼驗證下。設計模式
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(); // DriverManager中已存在的數據庫驅動加載到數據庫驅動緩存 while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } ..... 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; } 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); } // DriverManager requires the driver to be loaded via the system ClassLoader. // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html Driver driverInstance = (Driver)driverType.newInstance(); 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); } } .... }
以上代碼是UnPooledDataSource的源碼分析,可見,UnPooledDataSource並無採用池化的方法對數據庫鏈接進行管理。每次獲取鏈接,就新建一個數據庫鏈接。咱們知道數據庫鏈接的創建是個很是耗時耗資源的過程,爲了統一管理這些數據庫鏈接,mybatis爲咱們引入了PooledDataSource類。緩存
PooledDataSource源碼分析:PooledDataSource是數據源的重點,源碼比較複雜。PooledDataSource內部使用UnPooledDataSource類建立新的數據庫鏈接。PooledDataSource並不直接管理java.sql.connection鏈接,而是管理java.sql.connection的一個代理類PooledConnection。除了管理數據庫鏈接的創建,PooledDataSource內部還使用PoolState來管理數據源的狀態(即空閒鏈接數,活躍鏈接數等)。綜上,總結以下,PooledDataSource使用UnPooledDataSource類爲數據源建立真實的數據庫鏈接,使用PooledConnection爲數據源管理數據庫鏈接,使用PoolState來爲數據源管理數據源當前狀態。mybatis
PoolConnection是一個connection代理類,裏面封裝了真實的鏈接與代理鏈接,如今咱們先來分析PoolConnection的源碼。源碼分析
class PooledConnection implements InvocationHandler { // 鏈接代理類 private static final String CLOSE = "close"; private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; private int hashCode = 0; private PooledDataSource dataSource; // 數據源 private Connection realConnection; // 被代理的真實鏈接 private Connection proxyConnection; // 代理鏈接 private long checkoutTimestamp; // 從鏈接池中取出鏈接的時間 private long createdTimestamp; // 鏈接創建的時間 private long lastUsedTimestamp; // 鏈接上次使用的時間 private int connectionTypeCode; // 用於標註該鏈接所在的鏈接池 private boolean valid; // 鏈接有效的標誌
PooledConnection實現了InvocationHandler接口,則可見是一個代理對象。查看屬性可知,內部有真實鏈接與代理鏈接,並附帶鏈接的一些記錄信息。查看該類的構造方法。學習
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); // 使用動態代理生成鏈接的代理類 } /* * Invalidates the connection */ // 將該連接置爲無效 public void invalidate() { valid = false; }
查看構造方法可知,內部除了初始化一些屬性外,還將鏈接的代理類也進行初始化了。那代理類究竟作了什麼,查看重寫的invoke方法源碼。
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())) { // 若要執行的方法不是object方法,則檢查鏈接的有效性 // issue #579 toString() should never fail // throw an SQLException instead of a Runtime 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."); } }
由源碼可知,代理鏈接在執行方法時,會先檢查此鏈接的有效性,而後執行真實的方法。分析完PoolConnection後,對PoolState進行源碼解析。
public class PoolState { // 鏈接池狀態信息 protected PooledDataSource dataSource; // 此狀態信息關聯的數據源 protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 空閒鏈接列表 protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 活躍鏈接列表 protected long requestCount = 0; // 請求數 protected long accumulatedRequestTime = 0; // 累加請求所用時間 protected long accumulatedCheckoutTime = 0; // 累加佔用鏈接所用時間 protected long claimedOverdueConnectionCount = 0; // 鏈接超時的數量 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 累加超時的鏈接超時的時間 protected long accumulatedWaitTime = 0; // 累加等待獲取鏈接所用時間 protected long hadToWaitCount = 0; // 等待獲取鏈接的線程數 protected long badConnectionCount = 0; // 失效的鏈接數
PoolState是對DataSource的狀態管理類,主要包括如累計鏈接超時時間,失效鏈接的獲取等一些狀態信息的管理。除了包括一些數據庫鏈接的記錄信息外,內部還維護了兩個數據庫鏈接的列表idleConnections,activeConnections.。分別用來存放空閒的數據庫鏈接列表,活躍的數據庫鏈接列表,針對此兩個列表的操做,下文在分析PooledDataSource時會進行詳細介紹。
對PoolConnection和PoolState分析結束後,具體分析PoolDataSource源碼。
public class PooledDataSource implements DataSource { private static final Log log = LogFactory.getLog(PooledDataSource.class); private final PoolState state = new PoolState(this); // 維護數據源的狀態 private final UnpooledDataSource dataSource; // 使用UnpooledDataSource來創建真正的鏈接 // OPTIONAL CONFIGURATION FIELDS protected int poolMaximumActiveConnections = 10; // 最大活躍的鏈接數 protected int poolMaximumIdleConnections = 5; // 最大空閒的鏈接數 protected int poolMaximumCheckoutTime = 20000; // 最大checkout時間(checkOutTime指的是從數據源中獲取鏈接到歸還鏈接的時間) protected int poolTimeToWait = 20000; // 最大等待時間 protected String poolPingQuery = "NO PING QUERY SET"; // 使用該語句來驗證該鏈接是否有效 protected boolean poolPingEnabled = false; protected int poolPingConnectionsNotUsedFor = 0; private int expectedConnectionTypeCode; // hashcode
查看PoolDataSource基本屬性,可知內部使用PoolState來維護數據源的狀態信息,使用UnpooledDataSource來產真正的鏈接。並提供了一些如設置最大空閒,活躍鏈接數的配置信息。做爲DataSource的實現,PooledDataSource不只提供瞭如popConnection獲取數據庫鏈接的接口。還提供了forceCloseAll來關閉全部數據鏈接。pushConnection將使用結束的數據庫鏈接放入數據源中。如今開始分析第一個方法popConnection,流程圖以下,代碼中都有詳細註釋,請耐看。
// 獲取鏈接 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) { 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); // 則使用unPooledDataSource新建一個鏈接,並封裝成代理鏈接PooledConnection @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++; // 記錄壞的鏈接數+1 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; }
經分析,獲取鏈接的過程爲,先去查找空閒鏈接列表,若存在空閒列表,則直接從空閒列表中拿出數據庫鏈接。若無空閒鏈接,則判斷當前存活的數據庫鏈接是否超過了指定的活躍鏈接數,若沒有超過,則新建數據庫鏈接。若超過了,則去拿活躍鏈接數的第一個鏈接判斷是否鏈接超時(爲何拿第一個?由於是隊列,隊尾插入,對頭獲取,對頭的鏈接沒有超時,則後面的確定沒有超時)若發現第一個鏈接已經超過指定的數據庫鏈接時間,則將此鏈接從活躍列表中移除,並標誌爲失效,而後本身新建一個數據庫鏈接。若第一個鏈接沒有過時,則表明如今數據源不能提供任何鏈接了,必須等待,直接wait,釋放鎖,等待線程喚醒。拿到了數據庫鏈接後,須要檢查該鏈接是否有效,如有效,則放入活躍鏈接列表中,並返回給用戶。
當一個鏈接使用完畢後,須要放回到數據源中進行管理,如今分析pushConnection源碼。流程圖和源碼分析以下:
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++; } } }
鏈接使用結束後並非立馬釋放,而是檢查當前空閒列表的鏈接數是否已超過指定空閒的鏈接數,若沒有超過,則放入到空閒鏈接列表中。不然將該鏈接設爲無效。並喚醒阻塞中的獲取鏈接的線程。
當用戶指定變動數據源配置信息時,如數據庫地址,用戶名,密碼等,都須要對數據源進行重置,清空現存的數據庫鏈接後修改配置信息。現查看清空數據源的方法forceCloseAll源碼。此方法較簡單,就不貼流程圖了。
//關閉池中全部的活躍鏈接和空閒鏈接 public void forceCloseAll() { synchronized (state) { expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword()); for (int i = state.activeConnections.size(); i > 0; i--) { // 獲取全部的活躍鏈接 try { PooledConnection conn = state.activeConnections.remove(i - 1); // 移除 conn.invalidate(); // 失效 Connection realConn = conn.getRealConnection(); if (!realConn.getAutoCommit()) { // 事務回滾 realConn.rollback(); } realConn.close(); } catch (Exception e) { // ignore } } for (int i = state.idleConnections.size(); i > 0; i--) { // 獲取全部的空閒鏈接 try { PooledConnection conn = state.idleConnections.remove(i - 1); // 移除 conn.invalidate(); // 失效 Connection realConn = conn.getRealConnection(); if (!realConn.getAutoCommit()) { // 事務回滾 realConn.rollback(); } realConn.close(); } catch (Exception e) { // ignore } } } if (log.isDebugEnabled()) { log.debug("PooledDataSource forcefully closed/removed all connections."); } }
經分析,forceCloseAll對全部的空閒列表中,活躍列表中的數據庫鏈接所有移除並置爲不可用。池中恢復到初始化狀態。
總結:本文對mybatis中的數據源部分進行了源碼解析。在學習源碼的過程當中,加深了對不少設計模式的理解,體會到了大神們的編程習慣,不只僅是源碼自己,更多的是思想上的理解。在學習中也知道了不急於求成,一個一個的包去分析,而後再去整和業務流程。如你對此源碼也感興趣,能夠評論下,我會把本身的mybatis中文註釋源碼包分享。但此註釋都是本身手寫,不能確保準確性,僅提供參考。任重而道遠,源碼之路但願本身能堅持下來。