Mybatis源碼之美:2.12.解析`environments`元素,完成`Mybatis`中的多環境配置

解析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屬性,是當前環境配置的唯一標誌,同時他下面必須配置一個transactionManagerdataSource子元素。

transactionManager

其中transactionManager用來配置當前環境的事務管理器,其DTD定義如下:

<!--ELEMENT transactionManager (property*)-->
<!--ATTLIST transactionManager
type CDATA #REQUIRED
-->

transactionManager有一個必填的type屬性,表示使用的事務管理器的類型,在Mybatis中默認提供了兩種類型的事務管 理器:JDBCMANAGED。 這兩個簡短的名稱依託於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,POOLEDJNDI

同樣,這三個簡短的名稱也是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是一個簡單的,同步的,線程安全的數據庫連接池,通過複用JDBCConnection,避免了重複創建新的鏈接實例所必須的初始化和認證時間,提高了應用程序對數據庫訪問的併發能力。

UNPOOLED對應的UnpooledDataSourceFactory用於獲取一個UnpooledDataSourceUnpooledDataSource是一個簡單的數據源,他對每一次獲取鏈接的請求都會打開一個新的鏈接。

dataSource

dataSource標籤下允許出現多個property子標籤,這些property將會轉換成Properties用於初始化和配置對應的DataSource. 在瞭解了每一個元素標籤的作用之後,我們繼續回到解析environments的代碼上來。

調用解析的入口(XmlConfigBuilder):

private void parseConfiguration(XNode root) {
    // ...
    // 加載多環境源配置,尋找當前環境(默認爲default)對應的事務管理器和數據源
    environmentsElement(root.evalNode("environments"));
   // ...
}

解析並構建MybatisEnvironment對象:

/**
 * 解析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標籤的過程並不是很複雜,他首先通過environmentsdefault屬性獲取用戶指定的默認環境標誌。

然後遍歷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節點的操作,主要是通過解析元素transactionManagerdataSource,獲取對應的TransactionFactory 對象以及DataSourceFactory對象。

dataSource元素的解析過程和transactionManager近乎一致,都是先解析出type屬性對應的實際類型,然後通過反射獲取對象實例,最後調用實例對象的setProperties()方法完成自定義的參數的同步工作。

關於transactionManager的解析過程:

    /**
     * 根據environments&gt;environment&gt;transactionManager節點配置事務管理機制
     *
     * @param context environments&gt;environment&gt;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會調用DataSourceFactorygetDataSource()方法獲取具體的DataSource實例。

到這裏,配置Environment對象的必備元素已經全部得到,接下來通過使用Environment對象的構建器來構建一個 Environment對象,並將該Environment實例同步到Mybatis的配置類Configuration中的environment屬性中。

至此,整個environments元素的解析工作也已經完成。

在繼續解析後續元素前,讓我們先來了解一下TransactionFactoryDataSourceFactory相關的信息。

首先我們看一下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對象的工廠實例,在mybatisTransaction對象封裝了原生的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有兩種默認實現,分別是:JdbcTransactionFactoryManagedTransactionFactory

JdbcTransactionFactory負責創建JdbcTransaction對象實例,ManagedTransactionFactory負責創建ManagedTransaction對象實例。

TransactionFactory > 像這種工廠和產品皆被抽象出來的工廠定義,通常我們稱之爲抽象工廠,對應着設計模式中的抽象工廠模式。

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不同實現類的大致作用,因爲這裏涉及到數據庫連接池相關的知識,所以更詳細的DataSourceFactory相關的信息,將會在擴展數據庫連接池相關的知識時給出。

至此,基本完成了environments元素的處理工作。

關注我,一起學習更多知識

關注我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章