1. 程式人生 > >Mybatis中的事務管理器詳述

Mybatis中的事務管理器詳述

在上篇文章<>中,我們結合原始碼對mybatis中的資料來源和連線池進行了比較詳細的說明。在這篇文章中,我們講講相關的另外一個主題——事務管理器。

在前面的文章中,我們知道mybatis支援兩種事務型別,分別為JdbcTransaction和ManagedTransaction。接下來,我們從mybatis的xml配置檔案入手,講解事務管理器工廠的建立,然後講述事務的建立和使用,最後分析這兩種事務的實現和兩者的區別。

我們先看看配置檔案中相關的配置:


Mybatis定義了一個事務型別介面Transaction,JdbcTransaction和ManagedTransaction兩種事務型別都實現了Transaction介面。我們看看Transaction這個介面的定義:

public interface Transaction {

  /**
   * Retrieve inner database connection
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException;

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException;

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException;

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException;

  /**
   * Get transaction timeout if set
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException;
  
}

在事務介面中,定義了若干方法,如下結構所示:

事務的繼承關係如下:

JdbcTransaction和ManagedTransaction的區別如下:

JdbcTransaction:利用java.sql.Connection物件完成對事務的提交(commit())、回滾(rollback())、關閉(close())等;

ManagedTransaction:MyBatis自身不會去實現事務管理,而是讓程式的容器來實現對事務的管理;

那麼在mybatis中又是怎麼使用事務管理器的呢?首先需要根據xml中的配置確定需要建立什麼樣的事務管理器,然後從事務管理器中獲取相應的事務。

在mybatis初始化的時候,在解析<transactionManager>節點的時候,根據設定的type型別去初始化相應的事務管理器,解析原始碼如下所示:

 /** 
   * 解析<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,則建立一個JdbcTransactionFactory例項,如果type配置成MANAGED,則會建立一個ManagedTransactionFactory例項。這兩個事務管理器型別都實現了mybatis定義的TransactionFactory介面。

事務管理器工廠介面的定義如下所示:

public interface TransactionFactory {

  /**
   * Sets transaction factory custom properties.
   * @param props
   */
  void setProperties(Properties props);

  /**
   * Creates a {@link Transaction} out of an existing connection.
   * @param conn Existing database connection
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(Connection conn);
  
  /**
   * Creates a {@link Transaction} out of a datasource.
   * @param dataSource DataSource to take the connection from
   * @param level Desired isolation level
   * @param autoCommit Desired autocommit
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

從介面定義看,不管是JdbcTransactionFactory,還是ManagedTransactionFactory,都需要自行實現事務Transaction的建立工作。我們從原始碼上看看這兩個類都是怎麼定義實現的。

JdbcTransactionFactory定義如下:

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定義如下:
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);
  }
}

從原始碼看,JdbcTransactionFactory會建立JDBC型別的事務JdbcTransaction,而ManagedTransactionFactory則會建立ManagedTransaction。

接下來,我們再來看看這兩種型別的事務型別。

先從JdbcTransaction看起吧,這種型別的事務就是使用Java自帶的Connection來實現事務的管理。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;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  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) {
      if (log.isDebugEnabled()) {
        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);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }
  
}

最後我們再來看看ManagedTransaction物件,這個物件因為是將事務的管理交給容器去控制,所以,這裡的ManagedTransaction是沒有做任何控制的。我們先來看看原始碼:
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;
  }

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

  @Override
  public void commit() throws SQLException {
    // Does nothing
  }

  @Override
  public void rollback() throws SQLException {
    // Does nothing
  }

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

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}

從原始碼我們可以看到,ManagedTransaction的commit和rollback方法是沒有做任何事情的,它將事務交由了更上層的容易來進行控制和實現。至此,關於事務管理器我們描述的已經差不多了,如果需要深究可以自己再去研究研究。

如果想了解我最新的博文,請關注我的部落格,謝謝。如果想看到更多相關技術文章並願意支援我繼續寫下去,歡迎給我打賞,對您的打賞我表示感謝。吐舌頭