github地址:https://github.com/dchack/Mybatis-source-code-learn (歡迎star)
TransactionFactory
官方文檔:
在 MyBatis 中有兩種類型的事務管理器(也就是 type=”[JDBC|MANAGED]”):
JDBC – 這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
MANAGED – 這個配置幾乎沒做什麼。它從來不提交或回滾一個連接,而是讓容器來管理事務的整個生命週期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接,然而一些容器並不希望這樣,因此需要將 closeConnection 屬性設置爲 false 來阻止它默認的關閉行爲。例如:<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
提示如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器, 因爲 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。
以上配置transactionManager屬性來配置使用哪一種TransactionFactory
的代碼,肯定在MybatisXMLConfigBuilder
中可以找到:
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
從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);
}
TransactionFactory
接口描述實現者需要從Connection
或DataSource
生產org.apache.ibatis.transaction.Transaction
出來。
接口實現類如下:
他們各自生產的Transaction
分別是:
- JdbcTransaction
- ManagedTransaction
- SpringManagedTransaction
Transaction接口:
/**
* Wraps a database connection.
* Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.
*
* @author Clinton Begin
*/
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;
}
抽象出了控制connection生命週期的核心接口:getConnection(create),commit,rollback,close。
JdbcTransaction的實現:
三個操作方法:commit,rollback,close
,都是connection
的封裝而已,commit,rollback執行的條件需要已經生成好connection
並且AutoCommit
沒有設置true,close方法會調用resetAutoCommit
方法重置Connection
的autoCommit
屬性爲true
:
@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();
}
}
重置autoCommit屬性方法:
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);
}
}
}
在看下getConnection
方法的實現:
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
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);
}
openConnection中設置了事務隔離級別(transaction isolation level)和autoCommmit。
事務隔離級別在TransactionIsolationLevel
枚舉中可以看到:
public enum TransactionIsolationLevel {
NONE(Connection.TRANSACTION_NONE),
READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
private final int level;
private TransactionIsolationLevel(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
}
在java.sql.Connection
中的定義和註釋如下:
/**
* A constant indicating that transactions are not supported.
*/
int TRANSACTION_NONE = 0;
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads can occur.
* This level allows a row changed by one transaction to be read
* by another transaction before any changes in that row have been
* committed (a "dirty read"). If any of the changes are rolled back,
* the second transaction will have retrieved an invalid row.
*/
int TRANSACTION_READ_UNCOMMITTED = 1;
/**
* A constant indicating that
* dirty reads are prevented; non-repeatable reads and phantom
* reads can occur. This level only prohibits a transaction
* from reading a row with uncommitted changes in it.
*/
int TRANSACTION_READ_COMMITTED = 2;
/**
* A constant indicating that
* dirty reads and non-repeatable reads are prevented; phantom
* reads can occur. This level prohibits a transaction from
* reading a row with uncommitted changes in it, and it also
* prohibits the situation where one transaction reads a row,
* a second transaction alters the row, and the first transaction
* rereads the row, getting different values the second time
* (a "non-repeatable read").
*/
int TRANSACTION_REPEATABLE_READ = 4;
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads are prevented.
* This level includes the prohibitions in
* <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the
* situation where one transaction reads all rows that satisfy
* a <code>WHERE</code> condition, a second transaction inserts a row that
* satisfies that <code>WHERE</code> condition, and the first transaction
* rereads for the same condition, retrieving the additional
* "phantom" row in the second read.
*/
int TRANSACTION_SERIALIZABLE = 8;
關於事務隔離級別
幾個概念:
- 髒讀:讀取的數據可以取到其他未提交事務修改的數據
- 不可重複讀:一個事務中多次讀取相同的數據,因其他事務在中間修改了這個數據,導致第一個事務多次讀的數據會不相同
- 幻讀:就是在一個事務提交時發現之前查的條件發生了改變
隔離級別:
- 提交讀(READ_COMMITTED)只能讀取到已經提交的數據
- 未提交讀(READ_UNCOMMITTED)允許髒讀
- 可重複讀(REPEATABLE_READ)在同一事務中保證多次讀取的數據是一致的
- 串行讀(SERIALIZABLE)每次讀都需要獲取表級鎖,讀寫互相阻塞
mysql中查看隔離級別設置:
select @@global.tx_isolation;
另外我們也看到JdbcTransaction
中是需要autoCommmit
設置true的,否則是不能完成事務功能的。
ManagedTransaction
從類註釋上可以看到:ManagedTransaction
是將事務的生命週期交給容器管理,可以理解它都是空實現,比如commit,rollback
,close
可以通過closeConnection
字段來關閉。
SpringManagedTransaction
後續進入Mybatis擴展模塊時展開。