Mybatis原理--事務管理

本文將會介紹Mybatis的事務管理機制的原理,首先介紹下事務管理的特質、在Mybatis中是如何創建事務、事務有哪幾種類型、不同事務的源碼實現、并比較其中的不同、并總結事務的原理。最后分享學習事務常見的小知識。

事務的概述
對于數據庫事務,具有如下幾種特質:
  • 創建(create
  • 提交(commit
  • 回滾(rollback
  • 關閉(close
    對應地,MyBatis將事務抽象成了Transaction接口,源碼如下:

Transaction源碼

可以看出,這個接口中包含最基本 的getConnection、commit、rollback、close 方法,任何實現對事物管理都需要實現這幾個方法。

Mybatis中實現事務的管理分為如下兩種:

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

Mybatis在初始化的時候,會加載解析Mybatis的xml配置文件,在xml文件中若配置了事務管理的類型,<transactionManager>type配置為"JDBC",那么,在MyBatis初始化解析<environment>節點時,會根據

  • type="JDBC" 創建一個JdbcTransactionFactory工廠,JdbcTransactionFactory能夠創建JDBC類型的事務管理機制
  • type="MANAGED" 創建一個MangedTransactionFactory工廠,MangedTransactionFactory能夠創建MANAGED類型的事務管理機制

源碼截圖如下:

Mybatis事務的創建

下面我們來看看他們的實現細節:

JdbcTransaction的創建

JdbcTransactionFactory類會根據 DataSource、隔離級別、是否自動提交 這三個參數創建Transacion,也可以根據給定的數據庫連接Connection創建Transaction
JdbcTransactionFactory 的源碼如下:

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);  
  }  
}
JdbcTransaction的實現

JdbcTransaction直接使用JDBC的提交和回滾事務管理機制 。它依賴與從dataSource中取得的連接connection 來管理transaction 的作用域,connection對象的獲取被延遲到調用getConnection()方法。如果autocommit設置為on,開啟狀態的話,它會忽略commitrollback
直觀地講,就是JdbcTransaction是使用的java.sql.Connection 上的commitrollback功能,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;
    //根據  DataSource、隔離級別、是否自動提交 三個參數創建Transacion
    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        this.dataSource = ds;
        this.level = desiredLevel;
        this.autoCommmit = desiredAutoCommit;
    }

    //根據給定的數據庫連接Connection創建Transaction
    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    public Connection getConnection() throws SQLException {
        if(this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    //使用 java.sql.Connection 的 commit
    public void commit() throws SQLException {
        if(this.connection != null && !this.connection.getAutoCommit()) {
            if(log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + this.connection + "]");
            }

            this.connection.commit();
        }

    }

    //使用 java.sql.Connection 的 rollback
    public void rollback() throws SQLException {
        if(this.connection != null && !this.connection.getAutoCommit()) {
            if(log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }

            this.connection.rollback();
        }

    }
    //使用 java.sql.Connection 的 close
    public void close() throws SQLException {
        if(this.connection != null) {
            this.resetAutoCommit();
            if(log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }

            this.connection.close();
        }

    }

    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            if(this.connection.getAutoCommit() != desiredAutoCommit) {
                if(log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(desiredAutoCommit);
            }

        } catch (SQLException var3) {
            throw new TransactionException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + var3, var3);
        }
    }

    protected void resetAutoCommit() {
        try {
            if(!this.connection.getAutoCommit()) {
                if(log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + this.connection + "]");
                }

                this.connection.setAutoCommit(true);
            }
        } catch (SQLException var2) {
            if(log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true before closing the connection.  Cause: " + var2);
            }
        }

    }

    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());
        }

        this.setDesiredAutoCommit(this.autoCommmit);
    }

    public Integer getTimeout() throws SQLException {
        return null;
    }
}
總結:
  • 當使用DataSource創建數據庫連接時,數據庫的事務隔離級別使用DataSource默認的事務隔離級別
  • 當使用 DataSource、隔離級別、是否自動提交 三個參數創建JdbcTransaction時,會使用傳入的參數來設定隔離級別和是否自動提交
  • 對select不進行事務控制
  • JdbcTransaction的事務,原理上就是封裝了一層JDBC的方法

ManagedTransaction的實現

ManagedTransaction讓容器來管理事務Transaction的整個生命周期,使用ManagedTransactioncommitrollback功能不會對事務有任何的影響,它什么都不會做,它將事務管理的權利移交給了容器來實現

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) {
            this.openConnection();
        }

        return this.connection;
    }
    //不做任何處理
    public void commit() throws SQLException {
    }
    //不做任何處理
    public void rollback() throws SQLException {
    }

    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());
        }

    }

    public Integer getTimeout() throws SQLException {
        return null;
    }
}
關于事務的隔離級別
先對不同隔離級別涉及到的名詞解釋:

? 臟讀: 對于兩個事物 T1、T2,T1 讀取了已經被 T2 更新但還沒有被提交的字段。之后, 若 T2 回滾,T1讀取的內容就是臨時且無效的。
? 不可重復讀: 對于兩個事物 T1、T2, T1 讀取了一個字段, 然后 T2 更新了該字段。 之后, T1再次讀取同一個字段, 值就不同了。
? 幻讀: 對于兩個事物 T1、T2, T1 從一個表中讀取了一個字段, 然后 T2 在該表中插入了一些新的行。 如果 T1 再次讀取同一個表, 就會多出數據

具體的隔離級別定義:
  • READ UNCOMMITTED(讀未提交數據) :允許事務讀取未被其他事務提交的變更,臟讀、不可重復讀和幻讀的問題都會出現
  • READ COMMITED(讀已提交數據) :只允許事務讀取已經被其他事務提交的變更,可以避免臟讀,但不可重復讀和幻讀問題仍然會出現
  • REPEATABLE READ(可重復讀) :確保事務可以多次從一個字段中讀取相同的值,在這個事務持續期間,禁止其他事務對這個字段進行更新,可以避免臟讀和不可重復讀,但幻讀的問題依然存在
  • SERIALIZABLE(串行化) :確保事務可以從一個表中讀取相同的行,在這個事務持續期間,禁止其他事務對該表執行插入、更新和刪除操作,所有并發問題都可以避免,但性能十分低

Oracle 默認的事務隔離級別為: READ COMMITED(讀已提交數據)
Mysql 默認的事務隔離級別為: REPEATABLE READ(可重復讀)

以上就是《Mybatis原理--事務管理》的全部內容,如有不正確的地方,請讀者指正,互相學習,共同進步,謝謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容