
MyBatis源碼解析之基礎子產品—Transaction
前文回顧
上一篇,咱們一起學習了Mybatis的DataSource子產品相關源碼,掌握了三種資料源工廠的邏輯,同時也掌握非池化資料源的連接配接建立,池化資料源如何從空閑清單擷取連接配接并放到活躍連接配接清單,及連接配接的歸還到空閑隊列的邏輯。
下面跟随筆者的思路,咱們繼續學習另一個重要子產品——Transaction子產品。
核心要點
本篇幅主要講解Mybatis在事務管理的抽象方案,以及提供的兩種簡單實作:jdbc實作及外部容器的處理邏輯。
架構設計
Transaction子產品所在包路徑為
org.apache.ibatis.transaction
,其具體劃分如下:
transaction
- jdbc
- JdbcTransaction
- JdbcTransactionFactory
- managed
- ManagedTransaction
- ManagedTransactionFactory
- DataSourceException
- TransactionFactory
- 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 ~