MyBatis事務之JdbcTransaction

Mybatis管理事務是分爲兩種方式:

(1)使用JDBC的事務管理機制,就是利用java.sql.Connection對象完成對事務的提交
(2)使用MANAGED的事務管理機制,這種機制mybatis自身不會去實現事務管理,而是讓程序的容器(JBOSS,WebLogic)來實現對事務的管理

JdbcTransaction

本文主要是學習第一種方式:

先來複習一下JDBC的事務:
獲取數據庫連接:conncetion對象
開始事務:conncetion.setAutoCommit(false),false表示不自動提交事務;
事務提交:conncetion.commit();
事務回滾:conncetion.rollback();

當我們使用MyBatis進行增刪改操作的時候,如果不提交事務,就沒有辦法更新數據庫,因爲會自動回滾,這表明,mybatis自動爲我們開始了事務,而且設置爲不自動提交事務。

例如下面的代碼執行刪除操作:

        //加載核心配置文件
        String resource = "sqlMapConfig.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        //創建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //創建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //獲得Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.deleteUserById(31);
        //如果不提交,事務會自動回滾,無法插入數據到數據庫
        sqlSession.commit();
        //釋放資源
        sqlSession.close();

打印了一下日誌:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 809762318.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3043fe0e]
DEBUG [main] - ==>  Preparing: delete from user where id = ? 
DEBUG [main] - ==> Parameters: 31(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3043fe0e]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3043fe0e]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3043fe0e]
DEBUG [main] - Returned connection 809762318 to pool.

從日誌中發現,在執行刪除操作的時候,MyBatis顯示通過JDBC連接了數據庫,然後設置了自動提交爲false,這也證明了,mybatis自動爲我們開始了事務,而且設置爲不自動提交事務。

但是事務是什麼時候開啓的呢,於是就去扒了一下源碼。

因爲事務是包含在一次SqlSession會話中的,所以從創建會話開始分析,即SqlSessionFactory的openSession方法開始分析
,SqlSessionFactory是一個接口,它有兩個實現類,DefaultSqlSessionFactory和SqlSessionManager,默認情況下使用的是DefaultSqlSessionFactory。
DefaultSqlSessionFactory的openSession()方法:

public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

調用了:openSessionFromDataSource方法:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        DefaultSqlSession var8;
        try {
            //加載配置文件中的數據庫信息
            Environment environment = this.configuration.getEnvironment();
            //創建事務工廠
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //實例化事務對象,傳入數據源,事務等級,以及是否自動提交事務,但是通過查看源碼發現,事務並沒有在這裏提交
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //這是一個執行的對象,是正操作數據庫的對象。
            Executor executor = this.configuration.newExecutor(tx, execType);
            //實例化一個會話對象,並傳入executor,當我們通過SqlSession向數據庫發送指令時,最終都是調用的executor方法。
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }
        return var8;
    }

這裏面有一個實例化了一個事務工廠TransactionFactory,TransactionFactory是一個接口:它也有兩個實現類:JdbcTransactionFactory和ManagedTransactionFactory,我們使用的是JDBC事務,所以使用JdbcTransactionFactory的newTransaction方法,創建JdbcTransaction。

查看JdbcTransaction源碼:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

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 autoCommmit;

    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        this.dataSource = ds;
        this.level = desiredLevel;
        this.autoCommmit = desiredAutoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    //獲取數據庫連接
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

    //事務提交
    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();
        }

    }

    //事務回滾
    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();
        }

    }

    //關閉資源
    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) {
            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);
    }
}

在getConnection()方法中調用了openConnection()方法,獲取數據庫連接,設置了等級,開啓了事務。

回到上面的openSessionFromDataSource方法,創建好事務對象後,緊接着又實例化了兩個對象:一個是Executor對象,一個的DefaultSqlSession對象,並將這個對象返回,並沒有調用getConnection()方法,所以事務的開始並不在OpenSession方法中,那麼我猜測,事務的開始,是在準備執行向數據庫發送Sql語句的時候,於是回到最上面執行刪除的代碼中:

        //加載核心配置文件
        String resource = "sqlMapConfig.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        //創建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //創建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //獲得Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.deleteUserById(31);
        //如果不提交,事務會自動回滾,無法插入數據到數據庫
        sqlSession.commit();
        //釋放資源
        sqlSession.close();

在userMapper.deleteUserById(31)這裏打了一下斷點,然後一路追蹤到了SimpleExecutor發doUpdate方法中,SimpleExecutor是Executor的實現類:

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

然後查看:prepareStatement()方法:

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection);
        handler.parameterize(stmt);
        return stmt;
    }

看到了getConnection,繼續追進去:

進入BaseExecutor的getConnection方法:

    protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = this.transaction.getConnection();
        return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
    }

Connection connection = this.transaction.getConnection();這一句調用了傳入的transaction的getConnection方法,
這裏的transaction使我們在opensession方法中,實例化,並傳入的。至此終於找到事務在哪開啓的了。

分析了一大堆:其實只要記住,MyBatis使用JDBC進行事務管理的時候,會默認開始事務,並設置不自動提交事務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章