動態

詳情 返回 返回

《深入理解Mybatis原理》MyBatis事務管理機制 - 動態 詳情

概述

對數據庫的事務而言,應該具有以下幾點:創建(create)、提交(commit)、回滾(rollback)、關閉(close)。對應地,MyBatis將事務抽象成了Transaction接口:

MyBatis的事務管理分為兩種形式:

  • 使用JDBC的事務管理機制:即利用java.sql.Connection對象完成對事務的提交(commit())、回滾(rollback())、關閉(close())等。
  • 使用MANAGED的事務管理機制:這種機制MyBatis自身不會去實現事務管理,而是讓程序的容器如(JBOSS,Weblogic)來實現對事務的管理。

這兩者的類圖如下所示:

官網關於事務配置的內容

在 MyBatis 中有兩種類型的事務管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從數據源獲得的連接來管理事務作用域。
  • MANAGED – 這個配置幾乎沒做什麼。它從不提交或回滾一個連接,而是讓容器來管理事務的整個生命週期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接。然而一些容器並不希望連接被關閉,因此需要將 closeConnection 屬性設置為 false 來阻止默認的關閉行為。例如:
<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>
如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器,因為 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。

這兩種事務管理器類型都不需要設置任何屬性。它們其實是類型別名,換句話説,你可以用 TransactionFactory 接口實現類的全限定名或類型別名代替它們。

public interface TransactionFactory {
  default void setProperties(Properties props) { // 從 3.5.2 開始,該方法為默認方法
    // 空實現
  }
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

在事務管理器實例化後,所有在 XML 中配置的屬性將會被傳遞給 setProperties() 方法。你的實現還需要創建一個 Transaction 接口的實現類,這個接口也很簡單:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

使用這兩個接口,你可以完全自定義 MyBatis 對事務的處理。

事務的配置、創建和使用

事務的配置

我們在使用MyBatis時,一般會在MyBatisXML配置文件中定義類似如下的信息:

<environment>節點定義了連接某個數據庫的信息,其子節點<transactionManager> 的type 會決定我們用什麼類型的事務管理機制。

事務工廠的創建

MyBatis事務的創建是交給TransactionFactory 事務工廠來創建的,如果我們將<transactionManager>的type 配置為"JDBC",那麼,在MyBatis初始化解析 <environment>節點時,會根據type="JDBC"創建一個JdbcTransactionFactory工廠,其源碼如下:

/** 
 * 解析<transactionManager>節點,創建對應的TransactionFactory 
 * @param context 
 * @return 
 * @throws Exception 
 */  
private TransactionFactory transactionManagerElement(XNode context) throws Exception {  
    if (context != null) {  
        String type = context.getStringAttribute("type");  
        Properties props = context.getChildrenAsProperties();  
        /* 
         * 在Configuration初始化的時候,會通過以下語句,給JDBC和MANAGED對應的工廠類 
         * typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 
         * typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 
         * 下述的resolveClass(type).newInstance()會創建對應的工廠實例 
         */  
        TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();  
        factory.setProperties(props);  
        return factory;  
    }  
    throw new BuilderException("Environment declaration requires a TransactionFactory.");  
}  

如上述代碼所示,如果type = "JDBC",則MyBatis會創建一個JdbcTransactionFactory.class 實例;如果type="MANAGED",則MyBatis會創建一個MangedTransactionFactory.class實例。

MyBatis對<transactionManager>節點的解析會生成TransactionFactory實例;而對<dataSource>解析會生成datasouce實例,作為<environment>節點,會根據TransactionFactory和DataSource實例創建一個Environment對象,代碼如下所示:

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)) {  
                //1.解析<transactionManager>節點,決定創建什麼類型的TransactionFactory  
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                //2. 創建dataSource  
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                DataSource dataSource = dsFactory.getDataSource();  
                //3. 使用了Environment內置的構造器Builder,傳遞id 事務工廠TransactionFactory和數據源DataSource  
                Environment.Builder environmentBuilder = new Environment.Builder(id)  
                .transactionFactory(txFactory)  
                .dataSource(dataSource);  
                configuration.setEnvironment(environmentBuilder.build());  
            }  
        }  
    }  
}  

Environment表示着一個數據庫的連接,生成後的Environment對象會被設置到Configuration實例中,以供後續的使用。

上述一直在講事務工廠TransactionFactory來創建的Transaction,現在讓我們看一下MyBatis中的TransactionFactory的定義吧。

事務工廠TransactionFactory

事務工廠Transaction定義了創建Transaction的兩個方法:一個是通過指定的Connection對象創建Transaction,另外是通過數據源DataSource來創建Transaction。與JDBC 和MANAGED兩種Transaction相對應,TransactionFactory有兩個對應的實現的子類:

事務Transaction的創建

通過事務工廠TransactionFactory很容易獲取到Transaction對象實例。我們以JdbcTransaction為例,看一下JdbcTransactionFactory是怎樣生成JdbcTransaction的,代碼如下:

public class JdbcTransactionFactory implements TransactionFactory {  
 
    public void setProperties(Properties props) {  
    }  
 
    /** 
     * 根據給定的數據庫連接Connection創建Transaction 
     * @param conn Existing database connection 
     * @return 
     */  
    public Transaction newTransaction(Connection conn) {  
        return new JdbcTransaction(conn);  
    }  
 
    /** 
     * 根據DataSource、隔離級別和是否自動提交創建Transacion 
     * 
     * @param ds 
     * @param level Desired isolation level 
     * @param autoCommit Desired autocommit 
     * @return 
     */  
    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {  
        return new JdbcTransaction(ds, level, autoCommit);  
    }  
}  

如上説是,JdbcTransactionFactory會創建JDBC類型的Transaction,即JdbcTransaction。類似地,ManagedTransactionFactory也會創建ManagedTransaction。下面我們會分別深入JdbcTranaction 和ManagedTransaction,看它們到底是怎樣實現事務管理的。

JdbcTransaction

JdbcTransaction直接使用JDBC的提交和回滾事務管理機制。它依賴與從dataSource中取得的連接connection 來管理transaction 的作用域,connection對象的獲取被延遲到調用getConnection()方法。如果autocommit設置為on,開啓狀態的話,它會忽略commit和rollback。

直觀地講,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能,JdbcTransaction只是相當於對java.sql.Connection事務處理進行了一次包裝(wrapper),Transaction的事務管理都是通過java.sql.Connection實現的。JdbcTransaction的代碼實現如下:

public class JdbcTransaction implements Transaction {  
 
    private static final Log log = LogFactory.getLog(JdbcTransaction.class);  
 
    //數據庫連接  
    protected Connection connection;  
    //數據源  
    protected DataSource dataSource;  
    //隔離級別  
    protected TransactionIsolationLevel level;  
    //是否為自動提交  
    protected boolean autoCommmit;  
 
    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {  
        dataSource = ds;  
        level = desiredLevel;  
        autoCommmit = desiredAutoCommit;  
    }  
 
    public JdbcTransaction(Connection connection) {  
        this.connection = connection;  
    }  
 
    public Connection getConnection() throws SQLException {  
        if (connection == null) {  
            openConnection();  
        }  
        return connection;  
    }  
 
    /** 
     * commit()功能 使用connection的commit() 
     * @throws SQLException 
     */  
    public void commit() throws SQLException {  
        if (connection != null && !connection.getAutoCommit()) {  
            if (log.isDebugEnabled()) {  
                log.debug("Committing JDBC Connection [" + connection + "]");  
            }  
            connection.commit();  
        }  
    }  
 
    /** 
     * rollback()功能 使用connection的rollback() 
     * @throws SQLException 
     */  
    public void rollback() throws SQLException {  
        if (connection != null && !connection.getAutoCommit()) {  
            if (log.isDebugEnabled()) {  
                log.debug("Rolling back JDBC Connection [" + connection + "]");  
            }  
            connection.rollback();  
        }  
    }  
 
    /** 
     * close()功能 使用connection的close() 
     * @throws SQLException 
     */  
    public void close() throws SQLException {  
        if (connection != null) {  
            resetAutoCommit();  
            if (log.isDebugEnabled()) {  
                log.debug("Closing JDBC Connection [" + connection + "]");  
            }  
            connection.close();  
        }  
    }  
 
    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {  
        try {  
            if (connection.getAutoCommit() != desiredAutoCommit) {  
                if (log.isDebugEnabled()) {  
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");  
                }  
                connection.setAutoCommit(desiredAutoCommit);  
            }  
        } catch (SQLException e) {  
            // Only a very poorly implemented driver would fail here,  
            // and there's not much we can do about that.  
            throw new TransactionException("Error configuring AutoCommit.  "  
             + "Your driver may not support getAutoCommit() or setAutoCommit(). "  
             + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);  
        }  
    }  
 
    protected void resetAutoCommit() {  
        try {  
            if (!connection.getAutoCommit()) {  
                // MyBatis does not call commit/rollback on a connection if just selects were performed.  
                // Some databases start transactions with select statements  
                // and they mandate a commit/rollback before closing the connection.  
                // A workaround is setting the autocommit to true before closing the connection.  
                // Sybase throws an exception here.  
                if (log.isDebugEnabled()) {  
                    log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");  
                }  
                connection.setAutoCommit(true);  
            }  
        } catch (SQLException e) {  
            log.debug("Error resetting autocommit to true "  
             + "before closing the connection.  Cause: " + e);  
        }  
    }  
 
    protected void openConnection() throws SQLException {  
        if (log.isDebugEnabled()) {  
            log.debug("Opening JDBC Connection");  
        }  
        connection = dataSource.getConnection();  
        if (level != null) {  
            connection.setTransactionIsolation(level.getLevel());  
        }  
        setDesiredAutoCommit(autoCommmit);  
    }  
 
}  

ManagedTransaction

ManagedTransaction讓容器來管理事務Transaction的整個生命週期,意思就是説,使用ManagedTransaction的commit和rollback功能不會對事務有任何的影響,它什麼都不會做,它將事務管理的權利移交給了容器來實現。看如下Managed的實現代碼大家就會一目瞭然:

/** 
 *  
 * 讓容器管理事務transaction的整個生命週期 
 * connection的獲取延遲到getConnection()方法的調用 
 * 忽略所有的commit和rollback操作 
 * 默認情況下,可以關閉一個連接connection,也可以配置它不可以關閉一個連接 
 * 讓容器來管理transaction的整個生命週期 
 * @see ManagedTransactionFactory 
 */   
public class ManagedTransaction implements Transaction {  
 
    private static final Log log = LogFactory.getLog(ManagedTransaction.class);  
 
    private DataSource dataSource;  
    private TransactionIsolationLevel level;  
    private Connection connection;  
    private boolean closeConnection;  
 
    public ManagedTransaction(Connection connection, boolean closeConnection) {  
        this.connection = connection;  
        this.closeConnection = closeConnection;  
    }  
 
    public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {  
        this.dataSource = ds;  
        this.level = level;  
        this.closeConnection = closeConnection;  
    }  
 
    public Connection getConnection() throws SQLException {  
        if (this.connection == null) {  
            openConnection();  
        }  
        return this.connection;  
    }  
 
    public void commit() throws SQLException {  
        // Does nothing  
    }  
 
    public void rollback() throws SQLException {  
        // Does nothing  
    }  
 
    public void close() throws SQLException {  
        if (this.closeConnection && this.connection != null) {  
            if (log.isDebugEnabled()) {  
                log.debug("Closing JDBC Connection [" + this.connection + "]");  
            }  
            this.connection.close();  
        }  
    }  
 
    protected void openConnection() throws SQLException {  
        if (log.isDebugEnabled()) {  
            log.debug("Opening JDBC Connection");  
        }  
        this.connection = this.dataSource.getConnection();  
        if (this.level != null) {  
            this.connection.setTransactionIsolation(this.level.getLevel());  
        }  
    }
} 

注意:如果我們使用MyBatis構建本地程序,即不是WEB程序,若將type設置成"MANAGED",那麼,我們執行的任何update操作,即使我們最後執行了commit操作,數據也不會保留,不會對數據庫造成任何影響。因為我們將MyBatis配置成了“MANAGED”,即MyBatis自己不管理事務,而我們又是運行的本地程序,沒有事務管理功能,所以對數據庫的update操作都是無效的。

user avatar yizhidanshendetielian 頭像 kuailedehuanggua 頭像 aitibao_shichangyingxiao 頭像 FreakEmbedded 頭像 jinyeyoudianerliang 頭像
點贊 5 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.