天天看點

MyBatis源碼解析之基礎子產品—Transaction

MyBatis源碼解析之基礎子產品—Transaction

MyBatis源碼解析之基礎子產品—Transaction

前文回顧

上一篇,咱們一起學習了Mybatis的DataSource子產品相關源碼,掌握了三種資料源工廠的邏輯,同時也掌握非池化資料源的連接配接建立,池化資料源如何從空閑清單擷取連接配接并放到活躍連接配接清單,及連接配接的歸還到空閑隊列的邏輯。

下面跟随筆者的思路,咱們繼續學習另一個重要子產品——Transaction子產品。

核心要點

本篇幅主要講解Mybatis在事務管理的抽象方案,以及提供的兩種簡單實作:jdbc實作及外部容器的處理邏輯。

架構設計

Transaction子產品所在包路徑為

org.apache.ibatis.transaction

,其具體劃分如下:

transaction
- jdbc
  - JdbcTransaction
  - JdbcTransactionFactory
- managed
  - ManagedTransaction
  - ManagedTransactionFactory
- DataSourceException
- TransactionFactory
- Transaction           

老規矩,咱們先從宏觀架構圖上了解設計脈絡,見下圖:

MyBatis源碼解析之基礎子產品—Transaction

不想說又不得不說,Mybatis真是将Factory設計模式使用者的爐火純青。縱觀整個項目源碼,大量的使用工廠或工廠方法模式。

源碼解讀

基于面向接口程式設計的思路,咱們首先看下Mybatis事務管理中的兩個核心接口:

Transaction

TransactionFactory

Transaction

正如Mybatis作者對該接口的定義:

// Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.           

翻譯過來就是:處理整個連接配接生命周期的各種操作,包括連接配接建立,事務送出、復原及連接配接關閉等資料庫事務相關的操作。

該接口共有五個方法。對于碼畜們來說,源碼加注釋才是國際通用語言。各方法描述如下:

/**
 * 處理整個連接配接生命周期的各種操作,包括連接配接建立,事務送出、復原及連接配接關閉等資料庫事務相關的操作。
 */
public interface Transaction {

  /** 擷取資料庫連接配接 */
  Connection getConnection() throws SQLException;

  /** 資料送出 */
  void commit() throws SQLException;

  /** 資料復原 */
  void rollback() throws SQLException;

  /** 關閉資料庫連接配接 */
  void close() throws SQLException;

  /** 擷取逾時時間,如果設定的話,Mybatis預設的兩種實作,均設定為null */
  Integer getTimeout() throws SQLException;
}           

以上為Transaction事務處理的核心接口方法。具體的實作邏輯等咱們稍後進行解析說明。

TransactionFactory

見名知意,該接口為事務的建立工廠,其唯一的目的就是建立事務對象。從源碼中可以看出

TransactionFactory

功能非常簡單,就是擷取事務對象。當然擷取事務前會設定相關屬性。具體見代碼加注釋:

package org.apache.ibatis.transaction;

import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.session.TransactionIsolationLevel;

/**
 * 事務建立工廠接口
 */
public interface TransactionFactory {
   */
  /** 設定相關配置資訊 */
  default void setProperties(Properties props) {
    // NOP
  }

  /** 根據連接配接對象擷取事務對象 */
  Transaction newTransaction(Connection conn);

  /**根據資料源、事務隔離級别、是否自動送出屬性 建構事務對象*/
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}           

一個設定配置資訊的方法,兩個擷取事務對象的重載方法。

JdbcTransaction

接下來看下基于jdbc的事務管理方案:

JdbcTransaction類實作中有四個屬性。分别為連接配接對象、資料源、事務隔離級别、是否自動送出等。

其構造方法有兩個。一個是基于connection對象為參數的構造方法。一個則是基于資料源、事務隔離級别、autoCommit為參數的構造方法。

然後學習下JdbcTransaction實作的幾個方法:

getConnection() :首選判斷connection對象是否為null,為null的情況下通過調用私有方法openConnection()來擷取連接配接對象。

commit():在connection非空且設定為手動送出的方式下,執行資料庫連接配接送出操作。

rollback():在connection非空且設定為手動送出的方式下,執行資料庫連接配接復原操作。

close():在connection非空時執行資料庫連接配接關閉操作,當然,如果資料庫為手動送出,則先設定為自動送出。

具體請看源碼及注釋:

package org.apache.ibatis.transaction.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionException;

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 autoCommit; //是否自動送出

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = 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 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);
      }
    }
  }

  /**通過datasource擷取資料庫連接配接,然後屬性level、autoCommit設定對應的事務隔離級别和是否自動送出*/
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }
  
  /** 設定是否自動送出 */
  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);
    }
  }

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

JdbcTransactionFactory

JdbcTransactionFactory非常簡單,隻是實作了TransactionFactory兩個擷取執行個體的方法:

public class JdbcTransactionFactory implements TransactionFactory {

  /** 通過資料庫連接配接擷取事務 */
  @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);
  }
}           

ManagedTransaction

通過其名字可知,該事務管理方式是有外部容器托管的。ManagedTransaction 與JdbcTransaction一樣也有四個屬性,不過差別的一個屬性是boolean類型的closeConnection屬性,與之對應的構造方法也有所差別。

對應ManagedTransaction的接口實作,其資料庫連接配接對象擷取也是通過datasource,而commit、rollback則是空實作,相對應的送出復原則由外部容器來完成。

對于事務的關閉,則根據屬性字段closeConnection值來決定。具體請檢視代碼及注釋:

public class ManagedTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(ManagedTransaction.class);

  private DataSource dataSource; //資料源
  private TransactionIsolationLevel level; //事務隔離級别
  private Connection connection; //資料庫連接配接
  private final 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 {
    // closeConnection 為true 且connection不為空時執行關閉
    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關于事務的實作比較簡單。抽象了事務管理對象并提供了一個簡單的jdbc類型實作。而Managed基本是空實作,最終會有外部容器進行托管。具體會在MyBatis內建Spring中進行說明。

關于MyBatis的Transaction子產品介紹至此告一段落。感謝垂閱,如有不妥之處請多多指教~

微觀世界,達觀人生。

做一名踏實的coder !

歡迎關注我的個人微信公衆号 : todobugs ~