原創作品,可以轉載,但是請標注出處地址:http://www.cnblogs.com/V1haoge/p/6675633.html
1 回顧
上一篇中我解說了數據源接口DataSource與數據源工廠接口DataSourceFactory,這二者是MyBatis數據源模塊的基礎,包括本文中的非池型非池型數據源(UnpooledDataSource)和之后的池型數據源(PooledDataSource)、托管型數據源(JndiDataSourceFactory)都是在這兩個接口上產生的。
本文解讀一下MyBatis中的非池型數據源,這是基礎的數據源,之后要解讀的池型數據源又是以此數據源為基礎產生的。
2 非池型數據源及其工廠
2.1 非池型數據源工廠:UnpooledDataSourceFactory
該數據源工廠實現了DataSourceFactory接口,源碼如下:
1 package org.apache.ibatis.datasource.unpooled; 2 import java.util.Iterator; 3 import java.util.Properties; 4 import java.util.Set; 5 import javax.sql.DataSource; 6 import org.apache.ibatis.datasource.DataSourceException; 7 import org.apache.ibatis.datasource.DataSourceFactory; 8 import org.apache.ibatis.reflection.MetaObject; 9 import org.apache.ibatis.reflection.SystemMetaObject; 10 11 public class UnpooledDataSourceFactory implements DataSourceFactory{ 12 private static final String DRIVER_PROPERTY_PREFIX = "driver.";//屬性前綴 13 private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();//屬性前綴的長度 14 protected DataSource dataSource; 15 /* 16 * 在工廠的構造器中創建具體數據源的實例並賦值,這將用於供getDataSource()方法獲取數據源實例 17 */ 18 public UnpooledDataSourceFactory() 19 { 20 this.dataSource = new UnpooledDataSource(); 21 } 22 /* 23 * 設置數據源驅動器屬性,有兩種情況 24 */ 25 public void setProperties(Properties properties) 26 { 27 Properties driverProperties = new Properties(); 28 MetaObject metaDataSource = SystemMetaObject.forObject(this.dataSource); 29 for (Iterator i$ = properties.keySet().iterator(); i$.hasNext(); ) { Object key = i$.next(); 30 String propertyName = (String)key; 31 if (propertyName.startsWith("driver.")) {//第一種情況,以driver.開頭的屬性名 32 String value = properties.getProperty(propertyName); 33 driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); 34 } else if (metaDataSource.hasSetter(propertyName)) {//元對象中擁有針對屬性名的set設置方法 35 String value = (String)properties.get(propertyName); 36 Object convertedValue = convertValue(metaDataSource, propertyName, value); 37 metaDataSource.setValue(propertyName, convertedValue); 38 } else { 39 throw new DataSourceException("Unknown DataSource property: " + propertyName); 40 } 41 } 42 if (driverProperties.size() > 0) 43 metaDataSource.setValue("driverProperties", driverProperties); 44 } 45 46 public DataSource getDataSource() 47 { 48 return this.dataSource; 49 } 50 /* 51 * 值類型強轉方法 52 */ 53 private Object convertValue(MetaObject metaDataSource, String propertyName, String value) { 54 Object convertedValue = value; 55 Class targetType = metaDataSource.getSetterType(propertyName); 56 if ((targetType == Integer.class) || (targetType == Integer.TYPE)) 57 convertedValue = Integer.valueOf(value); 58 else if ((targetType == Long.class) || (targetType == Long.TYPE)) 59 convertedValue = Long.valueOf(value); 60 else if ((targetType == Boolean.class) || (targetType == Boolean.TYPE)) { 61 convertedValue = Boolean.valueOf(value); 62 } 63 return convertedValue; 64 }
以上源碼中,數據源工廠可通過無參構造器創建具體的數據源實例,然后可通過getDataSource()方法,來獲取之前在構造器中創建的數據源實例,getDataSource()方法是對外的。
該工廠類的重點是設置屬性的方法:setProperties(Properties properties),我們繼續簡單解析:
第27行:創建局部變量driverProperties,用於存放參數屬性列表中經過過濾的屬性
第28行:在我們創建該工廠實例的基礎上(即字段dataSource已被賦值的基礎上),調用MyBatis提供的元對象MetaObject來生成針對dataSource實例的元實例metaDataSource。
解析:元實例,可以看成是針對具體實例的一批裝飾器,在包裝器核心是具體實例,外圍是多個裝飾器(包裝器),用於增強功能。
第29行:遍歷獲取的屬性列表Properties(參數)
第31-33行:針對參數列表中屬性名是以driver.為前綴的屬性,獲取其值,保存在driverProperties中以備后用。
第34-37行:針對參數列表中屬性名在dataSource實例中存在set方法的屬性,獲取其值,將值經過必要的轉換后,將其保存到metaDataSource元實例中
這個其實是最常使用的,我們在配置文件中配置的driver、url、username、password四個參數,全部都是以此種方式保存到dataSource實例中的。
第42-44行:最后對driverProperties變量進行null判斷,即判斷有無通過前綴方式配置的屬性,如果有則將這些配置同樣保存到metaDataSource元實例中
這樣到最后其實所有的配置信息都保存到了metaDataSource元實例中,這樣說其實不對,其實最后通過MetaObject的setValue()方法,將所有這些經過過濾的屬性設置保存到了元實例的核心:dataSource中了(內部原理,榮后稟明),對應於UnpolledDataSource中的driver、url、username、password、driverProperties這五個字段(見下文)。
總結來看看:數據源工廠的目的就是講配置文件中的配置內容配置到通過自己的構造器創建的具體數據源實例中,再使用getDataSource()方法返回給調用方。
2.2 非池型數據源:UnpolledDataSource
該類實現了DataSource數據源接口,實現了其中的所有抽象方法。
非池型是相對池型而言的,池型數據源統籌管理着一個數據庫連接池,在這個池中擁有指定數量的數據庫連接實例connection可供使用,其內部采用一定的規則指定數據連接對象的使用、創建、銷毀規則,來為多線程數據庫訪問提供及時有效的數據庫連接。
非池型數據源,即保持有一個數據庫連接實例的數據源。下面我們來看看這種數據源的實現方式:
首先我們來看看字段:
1 private ClassLoader driverClassLoader; 2 private Properties driverProperties; 3 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); 4 private String driver; 5 private String url; 6 private String username; 7 private String password; 8 private Boolean autoCommit; 9 private Integer defaultTransactionIsolationLevel;
我們一一進行解析:
ClassLoader driverClassLoader:這個是數據庫的驅動類加載器:目的正是用於從磁盤中加載數據庫驅動類到內存
Properties driverProperties:驅動器屬性,這個用於保存我們手動設置的數據庫驅動器屬性,如果我們不進行設置,則會使用默認值。這個屬性設置的鍵一般都會是以“driver.”為前綴。
Map<String,Driver> registeredDrivers = new ConcurrentHashMap<String,Driver>():表示數據庫驅動注冊器,其內部保存着所有已注冊的數據庫驅動類實例,這個字段是static修飾的,表示在數據源類加載的時候就會創建,這個時候創建的其實是個空集合。再者使用ConcurrentHashMap集合,這是一個線程安全的鍵值對型集合,它幾乎可以與HashTable通用(二者都是線程安全的集合類)。這個靜態字段其實是和之后要提到的一個靜態代碼塊配合工作的(容后介紹)。
String driver:數據庫驅動類名
String url:數據庫服務器URL地址
String username:數據庫服務器連接用戶名
String password:數據庫服務器連接密碼
Boolean autoCommit:是否自動提交,這是一個邏輯值,真表示使用自動提交,假表示關閉自動提交。上一文中提到了自動提交,其實那里的自動提交值就是在這里設置的。
Integer defaultTransactionIsolationLevel:表示默認的事務級別
靜態代碼塊:
1 static { 2 Enumeration<Driver> drivers = DriverManager.getDrivers(); 3 while (drivers.hasMoreElements()) { 4 Driver driver = drivers.nextElement(); 5 registeredDrivers.put(driver.getClass().getName(), driver); 6 } 7 }
這個靜態塊中的內容需要結合之前的靜態字段registeredDrivers來進行解析。
DriverManager是JDK提供的驅動器管理類,在該類加載時會執行其中的靜態塊,靜態塊中調用了一個loadInitialDrivers()方法,這是一個加載原始驅動器的方法,他將JDK中的原始配置jdbc.drivers中的驅動器名表示的驅動類加載到內存,其會調用本地方法進行驅動類進行加載,然后調用DriverManager中的registerDriver()方法將驅動類實例一一保存到DriverManager中的registeredDrivers集合中。
我們這里調用DriverManager中getDrivers()方法,將會獲取DriverManager中在集合registeredDrivers中的保存的驅動器實例,在獲取的時候會進行類加載驗證,驗證的目的是確保使用本類加載器獲取的驅動器類與在registeredDrivers中保存的對應驅動類實例的類是同一類型(==)。最后獲取到的是驅動實例的枚舉。
對這個枚舉進行遍歷,將其中所有驅動器實例保存到我們當前的UnpooledDataSource中的靜態集合registeredDrivers(區別於DriverManager中的同名集合,二者類型都不同)中。
這樣保證在該類加載的時候就將默認的驅動器實例加載到靜態集合中以備用。該靜態代碼塊的作用就是為registeredDrivers集合賦值。
下面我們來看看實現的DataSource中的構造方法:
1 public UnpooledDataSource() { 2 } 3 public UnpooledDataSource(String driver, String url, String username, String password) { 4 this.driver = driver; 5 this.url = url; 6 this.username = username; 7 this.password = password; 8 } 9 public UnpooledDataSource(String driver, String url, Properties driverProperties) { 10 this.driver = driver; 11 this.url = url; 12 this.driverProperties = driverProperties; 13 } 14 public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) { 15 this.driverClassLoader = driverClassLoader; 16 this.driver = driver; 17 this.url = url; 18 this.username = username; 19 this.password = password; 20 } 21 public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) { 22 this.driverClassLoader = driverClassLoader; 23 this.driver = driver; 24 this.url = url; 25 this.driverProperties = driverProperties; 26 }
可見UnpooledDataSource提供了五個構造器,第一個為無參構造器,其余皆帶有賦值功能:以驅動器類全限定名driver、數據庫服務器地址url、數據庫登錄用戶名及密碼為參數是最為常用的數據源構建方式,也可以將用戶名與密碼整合為以驅動器參數driverProperties的形式傳參,甚至還可以指定驅動類的類加載器driverClassLoader。
這里最常用的還是無參構造器,這個構造器在數據源工廠的無參構造器中被調用,用於創建一個空的UnpolledDataSource實例,然后使用工廠類中的setProperties()方法,為這個空實例中的各個字段進行賦值(采用上面提到的第二種方式進行讀取配置信息並保存到實例中),從而創建一個飽滿的實例,
下面看看實現自接口的方法
1 public void setLoginTimeout(int loginTimeout) throws SQLException 2 { 3 DriverManager.setLoginTimeout(loginTimeout); 4 } 5 public int getLoginTimeout() throws SQLException 6 { 7 return DriverManager.getLoginTimeout(); 8 } 9 public void setLogWriter(PrintWriter logWriter) throws SQLException 10 { 11 DriverManager.setLogWriter(logWriter); 12 } 13 public PrintWriter getLogWriter() throws SQLException 14 { 15 return DriverManager.getLogWriter(); 16 }
17 public Logger getParentLogger()
18 {
19 return Logger.getLogger("global");
20 }
這五個方法繼承自CommonDataSource通用數據源接口,其中各方法的意義如下:
setLoginTimeout():表示設置數據源連接數據庫的最長等待時間,以秒為單位
getLoginTimeout():表示獲取數據源連接到數據庫的最長等待時間
setLogWriter():設置數據源的日志輸出者(log writer)為給定的一個PrintWriter實例
getLogWriter():獲取數據源的日志輸出者。
通過上面的源碼可以發現,數據源類中的這些方法的實現都是直接調用的DriverManager的對應方法。
getParentLogger():獲取這個DataSource所使用的所有Logger的父Logger。
下面是UnpooledDataSource中最重要的方法:
1 @Override 2 public Connection getConnection() throws SQLException { 3 return doGetConnection(username, password); 4 } 5 @Override 6 public Connection getConnection(String username, String password) throws SQLException { 7 return doGetConnection(username, password); 8 }
這是兩個獲取數據源連接的方法,第一個是無參方法,它內部使用默認的用戶名與面目進行數據庫連接,第二個提供了指定的用戶名與密碼,使用指定的用戶名與密碼進行數據庫連接,並將該連接返回。二者內部都調用了同一個方法doGetConnection(),這是真正執行數據庫連接並獲取這個連接的方法。
1 private Connection doGetConnection(String username, String password) throws SQLException { 2 Properties props = new Properties(); 3 if (driverProperties != null) { 4 props.putAll(driverProperties); 5 } 6 if (username != null) { 7 props.setProperty("user", username); 8 } 9 if (password != null) { 10 props.setProperty("password", password); 11 } 12 return doGetConnection(props); 13 }
解析:內部定義一個Properties屬性變量用於存儲傳遞的參數,先對driverProperties(里面保存的是以driver.為前綴設置的配置信息)進行判斷,如果有值直接將其內部的值全部轉移到新的properties中,再判斷傳遞的username與password,並將其保存到properties中,其實這時,properties中保存的是有關數據源連接的基礎信息。
上面的方法最后調用了重載的同名方法:
1 private Connection doGetConnection(Properties properties) throws SQLException { 2 initializeDriver(); 3 //屬性的前綴是以“driver.”開 頭的,它 是 通 過 DriverManager.getConnection(url,driverProperties)方法傳遞給數據庫驅動 4 Connection connection = DriverManager.getConnection(url, properties); 5 configureConnection(connection); 6 return connection; 7 }
在這個重載方法中首先調用initializeDriver()方法進行驅動器初始化:
1 private synchronized void initializeDriver() throws SQLException { 2 //這里便是大家熟悉的初學JDBC時的那幾句話了 Class.forName newInstance() 3 if (!registeredDrivers.containsKey(driver)) { 4 Class<?> driverType; 5 try { 6 if (driverClassLoader != null) { 7 driverType = Class.forName(driver, true, driverClassLoader); 8 } else { 9 driverType = Resources.classForName(driver); 10 } 11 // DriverManager requires the driver to be loaded via the system ClassLoader. 12 // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html 13 Driver driverInstance = (Driver)driverType.newInstance(); 14 DriverManager.registerDriver(new DriverProxy(driverInstance)); 15 registeredDrivers.put(driver, driverInstance); 16 } catch (Exception e) { 17 throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); 18 } 19 } 20 }
這是驅動器初始化的方法,該方法使用synchronized進行修飾,表示這是一個同步方法,同一時刻只能被一處調用,這個方法的作用是加載驅動器類,並將其實例注冊到DriverManager中,同時將其保存到本實例的registeredDrivers中。
這里有個問題就是DriverProxy,這是一個驅動代理,這個類是以靜態代理類的方式定義的,其實現了Driver接口,實現了Driver接口中的所有抽象方法,是一個真正的代理類,代理Driver真正的實現類,即真正起作用的驅動類實例,代理類將驅動類的所有方法全部保護起來,這里真正的目的還請大家商酌討論。
然后返回上一步方法中,通過調研DriverManager的getConnection方法來獲取數據庫連接connection,最后調用configureConnection()方法進行數據庫連接的最后配置,配置的內容如下:
1 private void configureConnection(Connection conn) throws SQLException { 2 if (autoCommit != null && autoCommit != conn.getAutoCommit()) { 3 conn.setAutoCommit(autoCommit); 4 } 5 if (defaultTransactionIsolationLevel != null) { 6 conn.setTransactionIsolation(defaultTransactionIsolationLevel); 7 } 8 }
配置內容為:自動提交(boolean值)與事務級別
最后將獲取到的數據庫連接返回。
為什么我會說失誤呢?因為應該是現有數據源,再有數據庫連接,再有事務操作,所以本應該先介紹DataSource,再介紹Transaction的,但是這里我說反了......
(在此做個記錄)