mybatis原始碼解析之Configuration載入(二)
概述
上一篇我們講了configuation.xml中幾個標籤的解析,例如<properties>,<typeAlises>,<settings>等,今天我們來介紹剩下的兩個比較重要的標籤之一,<environments>,這個標籤主要用於我們訪問資料庫的配置進行設定。
<environments>解析
我們先來看下configuation.xml中<environments>元素的配置:
1 <environments default="development"> 2 <environment id="development"> 3 <transactionManager type="JDBC" /> 4 <dataSource type="POOLED"> 5 <property name="driver" value="${driveClass}" /> 6 <property name="url" value="${url}" /> 7 <property name="username" value="${userName}" /> 8 <property name="password" value="${password}" /> 9 </dataSource> 10 </environment> 11 </environments>
從上面的具體配置我們可以看出一些東西來:首先我們可以配置多個<environment>標籤,它的下面主要配置兩個東西,transactionManager和datasource,但是真正起作用的是<environments>標籤中default元素標出的那個。下面我們看下具體的解析流程:
1 private void environmentsElement(XNode context) throws Exception {
2 if (context != null) {
3 if (environment == null) {
4 environment = context.getStringAttribute("default");
5 }
6 for (XNode child : context.getChildren()) { 7 String id = child.getStringAttribute("id"); 8 if (isSpecifiedEnvironment(id)) { 9 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); 10 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); 11 DataSource dataSource = dsFactory.getDataSource(); 12 Environment.Builder environmentBuilder = new Environment.Builder(id) 13 .transactionFactory(txFactory) 14 .dataSource(dataSource); 15 configuration.setEnvironment(environmentBuilder.build()); 16 } 17 } 18 } 19 }
我們從程式碼中的for 迴圈遍歷可以看出,<environments>標籤下面是可以配置多個<environment>子標籤的,每個子標籤用id來區分,具體使用哪一個,看<environments>的default值,這也印證了我們一開始的分析。第3到5行程式碼,就是去獲取<environments>的default屬性,第7行程式碼,獲取子標籤<environment>的id屬性,判斷是不是上面default配置的那個,如果是,第9行程式碼,根據<transactionManager>標籤獲取事物管理器:
1 private TransactionFactory transactionManagerElement(XNode context) throws Exception {
2 if (context != null) {
3 String type = context.getStringAttribute("type");
4 Properties props = context.getChildrenAsProperties();
5 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
6 factory.setProperties(props); 7 return factory; 8 } 9 throw new BuilderException("Environment declaration requires a TransactionFactory."); 10 }
這邊重點看下第5行程式碼,跟到最後可以發現,它其實是從typeAliasRegistry 這個map中根據type去獲取class,進而得到例項,程式碼如下:
1 protected Class<?> resolveAlias(String alias) {
2 return typeAliasRegistry.resolveAlias(alias);
3 }
那這麼的根據獲取到是哪個類呢?其實我們上面說到過,其實一些預設的typeAlias,我們再看下關於這邊的:
1 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
2 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
3
4 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
5 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
6 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
這部分是在configuation中的,類初始化的時候就會載入,我們可以看到,transactionManager有兩種配置,JDBC和MANAGED,分別對應於JdbcTransactionFactory和ManagedTransactionFactory。下面我們就來分析下這兩個類,首先看下這兩個類所在的目錄解結構:
我們可以看出這邊使用了工廠模式,其中
Transaction是事物介面,主要定義瞭如下介面:
Connection getConnection() throws SQLException; // 獲取資料庫連線
void commit() throws SQLException; // 提交
void rollback() throws SQLException; // 回滾
void close() throws SQLException; // 關閉資料庫連線
Integer getTimeout() throws SQLException; // 獲取設定的事物超時時間
TransactionFactory是事物工廠介面,主要定義瞭如下介面:
void setProperties(Properties props); // 設定屬性
Transaction newTransaction(Connection conn); // 根據連線建立事物例項
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); // 根據資料來源,事物隔離級別,是否自動提交建立事物例項。
這樣設計的目的就是為了便於擴充套件,以後要實現不同型別的事物只要實現這兩個介面就行了。
上面配置檔案中的JDBC和MANAGED其實就是對應於mybatis內建兩種事物型別,JDBCTransaction和ManagedTransaction,
二者的不同之處在於:前者是直接使用JDK提供的JDBC來管理事務的各個環節:提交、回滾、關閉等操作,而後者則什麼都不做,那麼後者有什麼意義呢,當然很重要。當我們單獨使用MyBatis來構建專案時,我們要在Configuration配置檔案中進行環境(environment)配置,在其中要設定事務型別為JDBC,意思是說MyBatis被單獨使用時就需要使用JDBC型別的事務模型,因為在這個模型中定義了事務的各個方面,使用它可以完成事務的各項操作。而MANAGED型別的事務模型其實是一個託管模型,也就是說它自身並不實現任何事務功能,而是託管出去由其他框架來實現,你可能還不明白,這個事務的具體實現就交由如Spring之類的框架來實現,而且在使用SSM整合框架後已經不再需要單獨配置環境資訊(包括事務配置與資料來源配置),因為在在整合jar包(mybatis-spring.jar)中擁有覆蓋mybatis裡面的這部分邏輯的程式碼,實際情況是即使你顯式設定了相關配置資訊,系統也會視而不見......託管的意義顯而易見,正是為整合而設。我們學習MyBatis的目的正是由於其靈活性和與Spring等框架的無縫整合的能力,所以有關JDBC事務模組的內容明顯不再是MyBatis功能中的重點,也許只有在單獨使用MyBatis的少量系統中才會使用到。
但是還是得去了解下,我們先看下JdbcTransactionFactory,這個實現了TransactionFactory,將獲取JDBCTransaction例項的過程隱藏了起來,其主要程式碼如下:
1 public class JdbcTransactionFactory implements TransactionFactory { 2 3 @Override 4 public void setProperties(Properties props) { 5 } 6 7 @Override 8 public Transaction newTransaction(Connection conn) { 9 return new JdbcTransaction(conn); 10 } 11 12 @Override 13 public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { 14 return new JdbcTransaction(ds, level, autoCommit); 15 } 16 }
我們先看setProperties方法,引數是Properties,這個方法是在解析事物標籤的時候,如果有屬性設定,就要執行,用於覆蓋預設的配置,但是對於JdbcTransaction,即使你設定了,也不起作用,因為這邊是是一個空方法,當然,我們一般不會設定。
下面兩個方法名字一樣,只是引數不一樣,一個是根據連線去獲取JdbcTransaction的例項,一個是根據資料來源,事物隔離級別,是否自動提交來獲取,講這兩個方法之前,我們先來看下,JdbcTransaction中相關提交、回滾、關閉操作的實現:
提交:
1 if (connection != null && !connection.getAutoCommit()) { 2 if (log.isDebugEnabled()) { 3 log.debug("Committing JDBC Connection [" + connection + "]"); 4 } 5 connection.commit(); 6 } 7 }
回滾:
1 public void rollback() throws SQLException { 2 if (connection != null && !connection.getAutoCommit()) { 3 if (log.isDebugEnabled()) { 4 log.debug("Rolling back JDBC Connection [" + connection + "]"); 5 } 6 connection.rollback(); 7 } 8 }
關閉:
1 public void close() throws SQLException { 2 if (connection != null) { 3 resetAutoCommit(); 4 if (log.isDebugEnabled()) { 5 log.debug("Closing JDBC Connection [" + connection + "]"); 6 } 7 connection.close(); 8 } 9 }
我們可以看到,執行這些方法的時候都是針對connection來操作的,首先我們就要獲取connection,
獲取連線:
1 public Connection getConnection() throws SQLException {
2 if (connection == null) {
3 openConnection();
4 }
5 return connection;
6 }
我們接著看openConnection方法:
1 protected void openConnection() throws SQLException { 2 if (log.isDebugEnabled()) { 3 log.debug("Opening JDBC Connection"); 4 } 5 connection = dataSource.getConnection(); 6 if (level != null) { 7 connection.setTransactionIsolation(level.getLevel()); 8 } 9 setDesiredAutoCommit(autoCommmit); 10 }
setDesiredAutoCommit的具體內容如下:
1 protected void setDesiredAutoCommit(boolean desiredAutoCommit) { 2 try { 3 if (connection.getAutoCommit() != desiredAutoCommit) { 4 if (log.isDebugEnabled()) { 5 log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); 6 } 7 connection.setAutoCommit(desiredAutoCommit); 8 } 9 } catch (SQLException e) { 10 // Only a very poorly implemented driver would fail here, 11 // and there's not much we can do about that. 12 throw new TransactionException("Error configuring AutoCommit. " 13 + "Your driver may not support getAutoCommit() or setAutoCommit(). " 14 + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); 15 } 16 }
從這個兩個方法我們可以看出,connection可以從datasource中獲取,並補充設定它的事物隔離級別,是否自動提交等屬性。現在我們再來看一下JdbcTransactionFactory中那兩個方法就不難理解了,說到底就是為了獲得connection,進而去操作事物。
最後,我們再來看一個JdbcTransaction中的方法,
1 protected void resetAutoCommit() { 2 try { 3 if (!connection.getAutoCommit()) { 4 // MyBatis does not call commit/rollback on a connection if just selects were performed. 5 // Some databases start transactions with select statements 6 // and they mandate a commit/rollback before closing the connection. 7 // A workaround is setting the autocommit to true before closing the connection. 8 // Sybase throws an exception here. 9 if (log.isDebugEnabled()) { 10 log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); 11 } 12 connection.setAutoCommit(true); 13 } 14 } catch (SQLException e) { 15 if (log.isDebugEnabled()) { 16 log.debug("Error resetting autocommit to true " 17 + "before closing the connection. Cause: " + e); 18 } 19 } 20 }
這個方法是在關閉連線的時候呼叫的,mybatis預設的自動提交為true,如果將其設定為了false,就需要將其復位。那麼自動提交的初始值是在哪邊設定的呢?