解析environments
元素,完成Mybatis
中的多環境配置
在完成枯燥的基於settings
配置Configuration
對象的過程之後,就到了解析environments
標籤,配置Mybatis
的多環境的過程了。
Mybatis
默認是支持多環境配置的,在Mybatis
中有一個Environment
的對象,該對象有三個簡單的參數:
/**
* Mybatis環境容器
*
* @author Clinton Begin
*/
public final class Environment {
// 環境唯一標誌
private final String id;
// 事務工廠
private final TransactionFactory transactionFactory;
// 數據源
private final DataSource dataSource;
}
其中id
是當前環境的唯一標誌,屬於語義化屬性。
transactionFactory
屬性對應的是TransactionFactory
對象,他是一個事務創建工廠,用於創建Transaction
對象。
Transaction
對象包裝了JDBC的Connection
,用於處理數據庫鏈接的生命週期,包括鏈接的:創建,提交/回滾和關閉。
dataSource
屬性對應的是DataSource
對象,指向了一個JDBC
數據源。
在Mybatis關於environments
的DTD定義是這樣的:
<!--ELEMENT environments (environment+)-->
<!--ATTLIST environments
default CDATA #REQUIRED
-->
在environments
中必須指定default
屬性的值,他是默認環境的ID
,用於指定默認使用的環境配置,同時在environments
中允許出現一個或多個environment
子元素。
<!--ELEMENT environment (transactionManager,dataSource)-->
<!--ATTLIST environment
id CDATA #REQUIRED
-->
environment
元素有一個必填的ID
屬性,是當前環境配置的唯一標誌,同時他下面必須配置一個transactionManager
和 dataSource
子元素。
transactionManager
其中transactionManager
用來配置當前環境的事務管理器,其DTD定義如下:
<!--ELEMENT transactionManager (property*)-->
<!--ATTLIST transactionManager
type CDATA #REQUIRED
-->
transactionManager
有一個必填的type
屬性,表示使用的事務管理器的類型,在Mybatis中默認提供了兩種類型的事務管 理器:JDBC
和MANAGED
。 這兩個簡短的名稱依託於Mybatis的類型別名機制,他們是在Configuration
的無參構造方法中註冊的:
public Configuration() {
// 註冊別名
// 註冊JDBC別名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
// 註冊事務管理別名
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
...
}
其中JDBC
對應的JdbcTransactionFactory
創建的JdbcTransaction
對象直接使用了默認的JDBC
的提交和回滾設置,依賴於從數據源得到的鏈接來管理事務作用域。
而MANAGED
對應的ManagedTransactionFactory
創建的ManagedTransaction
對象是Transaction
的實現之一,它會忽略提交和回滾請求,但是會響應關閉連接的請求,不過,我們可以通過參數closeConnection
來控制他如何處理關閉連接的請求,當closeConnection
的值爲false
時,他將會忽略掉關閉鏈接的請求。
<transactionmanager type="MANAGED">
<property name="closeConnection" value="false" />
</transactionmanager>
在上面的示例中,我們可以發現transactionManager
也允許配置property
標籤,用於自定義的事務管理器的參數。
dataSource
dataSource
元素用來配置JDBC
數據源,他的DTD定義如下:
<!--ELEMENT dataSource (property*)-->
<!--ATTLIST dataSource
type CDATA #REQUIRED
-->
dataSource
標籤同樣有一個必填的type
屬性,它用於指向JDBC數據源的具體實例,在Mybatis
中默認提供了三種類型的數據源: UNPOOLED
,POOLED
和JNDI
。
同樣,這三個簡短的名稱也是Mybatis
的類型別名,其註冊也是在Configuration
對象的無參構造中:
public Configuration() {
// 註冊別名
...
// 註冊JNDI別名
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
// 註冊池化數據源別名
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
// 註冊爲池化的數據源
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
...
}
其中JNDI
對應的JndiDataSourceFactory
的作用是基於JNDI
加載一個可用的DataSource
。
POOLED
對應的PooledDataSourceFactory
用於獲取一個PooledDataSource
。
PooledDataSource
是一個簡單的,同步的,線程安全的數據庫連接池,通過複用JDBC
的Connection
,避免了重複創建新的鏈接實例所必須的初始化和認證時間,提高了應用程序對數據庫訪問的併發能力。
UNPOOLED
對應的UnpooledDataSourceFactory
用於獲取一個UnpooledDataSource
,UnpooledDataSource
是一個簡單的數據源,他對每一次獲取鏈接的請求都會打開一個新的鏈接。
dataSource
標籤下允許出現多個property
子標籤,這些property
將會轉換成Properties
用於初始化和配置對應的DataSource
. 在瞭解了每一個元素標籤的作用之後,我們繼續回到解析environments
的代碼上來。
調用解析的入口(XmlConfigBuilder
):
private void parseConfiguration(XNode root) {
// ...
// 加載多環境源配置,尋找當前環境(默認爲default)對應的事務管理器和數據源
environmentsElement(root.evalNode("environments"));
// ...
}
解析並構建Mybatis
的Environment
對象:
/**
* 解析environments節點
*
* @param context environments節點
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 配置默認環境
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 獲取環境唯一標誌
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 配置默認數據源
// 創建事務工廠
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 創建數據源工廠
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 創建數據源
DataSource dataSource = dsFactory.getDataSource();
// 通過環境唯一標誌,事務工廠,數據源構建一個環境容器
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 配置Mybatis當前的環境容器
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析environments
標籤的過程並不是很複雜,他首先通過environments
的default
屬性獲取用戶指定的默認環境標誌。
然後遍歷environments
下的所有environment
節點,通過environment
節點的id
屬性獲取待處理環境environment
的唯一標誌,
如果當前標誌和用戶指定的默認環境標誌一致的話,則處理該environment
節點,否則跳過處理。
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}
處理environment
節點的操作,主要是通過解析元素transactionManager
和dataSource
,獲取對應的TransactionFactory
對象以及DataSourceFactory
對象。
dataSource
元素的解析過程和transactionManager
近乎一致,都是先解析出type
屬性對應的實際類型,然後通過反射獲取對象實例,最後調用實例對象的setProperties()
方法完成自定義的參數的同步工作。
關於transactionManager
的解析過程:
/**
* 根據environments>environment>transactionManager節點配置事務管理機制
*
* @param context environments>environment>transactionManager節點內容
*/
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
// 獲取事務類型
String type = context.getStringAttribute("type");
// 獲取事務參數配置
Properties props = context.getChildrenAsProperties();
// 創建事務工廠
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
// 設置定製參數
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
關於dataSource
的解析過程:
/**
* 解析dataSource元素
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 獲取dataSource的type屬性指向的DataSourceFactory別名。
String type = context.getStringAttribute("type");
// 獲取用戶定義配置的參數集合
Properties props = context.getChildrenAsProperties();
// 解析別名對應的具體類型,並反射獲取實體。
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
// 設值
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
不同於TransactionFactory
,在DataSourceFactory
被初始化之後,Mybatis
會調用DataSourceFactory
的getDataSource()
方法獲取具體的DataSource
實例。
到這裏,配置Environment
對象的必備元素已經全部得到,接下來通過使用Environment
對象的構建器來構建一個 Environment
對象,並將該Environment
實例同步到Mybatis
的配置類Configuration
中的environment
屬性中。
至此,整個environments
元素的解析工作也已經完成。
在繼續解析後續元素前,讓我們先來了解一下TransactionFactory
和DataSourceFactory
相關的信息。
首先我們看一下TransactionFactory
:
/**
* 用於創建Transaction對象的實例
*
* @author Clinton Begin
*/
public interface TransactionFactory {
/**
* 配置事務工廠的自定義屬性
* @param props 自定義屬性
*/
void setProperties(Properties props);
/**
* 通過已有的鏈接創建一個事務
* @param conn 現有的數據庫鏈接
* @return Transaction 事務
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* 從指定的數據源中創建一個事務
* @param dataSource 數據源
* @param level 事務級別
* @param autoCommit 是否自動提交
* @return Transaction 事務
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
從方法定義上我們可以看出,TransactionFactory
是創建Transaction
對象的工廠實例,在mybatis
中Transaction
對象封裝了原生的Connection
對象, 接管了對Connection
對象的控制權,包括:創建鏈接,提交事務,回滾事務,關閉鏈接以及獲取事務超時時間。
/**
* 包裝數據庫連接。
* 處理連接生命週期,包括:創建,準備,提交/回滾和關閉
*
* @author Clinton Begin
*/
public interface Transaction {
/**
* 獲取一個數據庫鏈接
*
* @return 數據庫鏈接
*/
Connection getConnection() throws SQLException;
/**
* 提交
*/
void commit() throws SQLException;
/**
* 回滾
*/
void rollback() throws SQLException;
/**
* 關閉鏈接
*/
void close() throws SQLException;
/**
* 獲取事務超時時間
*/
Integer getTimeout() throws SQLException;
}
TransactionFactory
有兩種默認實現,分別是:JdbcTransactionFactory
和ManagedTransactionFactory
。
JdbcTransactionFactory
負責創建JdbcTransaction
對象實例,ManagedTransactionFactory
負責創建ManagedTransaction
對象實例。
> 像這種工廠和產品皆被抽象出來的工廠定義,通常我們稱之爲抽象工廠,對應着設計模式中的抽象工廠模式。
JdbcTransactionFactory
的實現比較簡單,僅僅是調用JdbcTransaction
不同的構造方法完成JdbcTransaction
對象的初始化工作而已,同時,他不會處理用戶傳入的自定義屬性配置。
/**
* 創建一個JdbcTransaction實例
*
* @author Clinton Begin
*
* @see JdbcTransaction
*/
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public void setProperties(Properties props) {
}
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
ManagedTransactionFactory
略有不同的地方在於,它會讀取用戶傳入的名爲closeConnection
的自定義屬性,並在創建ManagedTransaction
對象實例時使用該屬性完成ManagedTransaction
對象的初始化工作。
/**
* 創建一個ManagedTransactionFactory實例
*
* @author Clinton Begin
*
* @see ManagedTransaction
*/
public class ManagedTransactionFactory implements TransactionFactory {
private boolean closeConnection = true;
@Override
public void setProperties(Properties props) {
if (props != null) {
String closeConnectionProperty = props.getProperty("closeConnection");
if (closeConnectionProperty != null) {
closeConnection = Boolean.valueOf(closeConnectionProperty);
}
}
}
@Override
public Transaction newTransaction(Connection conn) {
return new ManagedTransaction(conn, closeConnection);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// Silently ignores autocommit and isolation level, as managed transactions are entirely
// controlled by an external manager. It's silently ignored so that
// code remains portable between managed and unmanaged configurations.
return new ManagedTransaction(ds, level, closeConnection);
}
}
JdbcTransaction
功能的實現依賴於從數據源得到的鏈接,他會將提交和回滾等請求直接委託給JDBC
原生的Connection
對象來完成。
而ManagedTransaction
作爲Transaction
的另一種實現,不同於JdbcTransaction
, 當對鏈接的操作請求到來時,ManagedTransaction
默認會忽略所有的提交和回滾請求,同時針對關閉鏈接的請求,會根據創建ManagedTransaction
對象時傳入的closeConnection
來決定是否受理。
之後我們簡單看一下DataSourceFactory
相關的類圖:
在前面我們大概瞭解了DataSourceFactory
不同實現類的大致作用,因爲這裏涉及到數據庫連接池相關的知識,所以更詳細的DataSourceFactory
相關的信息,將會在擴展數據庫連接池相關的知識時給出。
至此,基本完成了environments
元素的處理工作。