Mybatis【2.2】-- Mybatis關於建立SqlSession源碼分析的幾點疑問?

代碼直接放在Github倉庫【https://github.com/Damaer/Mybatis-Learning 】,可直接運行,就不佔篇幅了。java

1.爲何咱們使用SQLSessionFactoryBuilder的時候不須要本身關閉流?

咱們看咱們的代碼:git

public class StudentDaoImpl implements IStudentDao {
    private SqlSession sqlSession;
	public void insertStu(Student student) {
		try {
			InputStream inputStream;
			inputStream = Resources.getResourceAsStream("mybatis.xml");
			SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
			sqlSession=sqlSessionFactory.openSession();
			sqlSession.insert("insertStudent",student);
			sqlSession.commit();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
		    if(sqlSession!=null){
		        sqlSession.close();
            }
        }
	}
}

當咱們使用inputStream = Resources.getResourceAsStream("mybatis.xml");的時候,咱們並須要去關閉inputstream,咱們能夠查看源碼,首先看到SqlSessionFactoryBuilder().build()這個方法:github

// 將inputstream傳遞進去,調用了另外一個分裝的build()方法
    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

跟進去,咱們再來看另外一個build方法,裏面有一個finally模塊,不管怎麼樣都會執行close方法,因此這就是爲何咱們在使用的時候爲何不用關閉inputstream的緣由:由於這個流是在finally代碼塊中被關閉了。sql

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
            try {
                // 關閉流
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }
        return var5;
    }

2. Sqlsession是如何建立的?

語句裏面執行代碼:使用SQLSessionFactory去打開一個session,這裏的session咱們能夠初步理解爲一個sql的會話,相似咱們想要發信息給別人,確定須要打開一個和別人的會話。數據庫

sqlSession=sqlSessionFactory.openSession();

咱們須要查看源碼,咱們發現opensession是sqlSessionFactory的一個接口方法,sqlSessionFactory是一個接口。session

public interface SqlSessionFactory {
    // 在這裏只貼出了一個方法,其餘的就不貼了
    SqlSession openSession();
    }

idea選中該方法,ctrl + alt +B,咱們能夠發現有DefaultSqlSessionFactory,和SqlSessionManager這兩個類實現了SqlSessionFactory這個接口

那麼咱們須要跟進去DefaultSqlSessionFactory這個類的openSesseion方法,在裏面調用了一個封裝好的方法:openSessionFromDataSource()mybatis

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

固然在DefaultSqlSessionFactory這個類裏面還有一個方法,參數是autoCommit,也就是能夠指定是否自動提交:app

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

咱們再跟進去源碼,咱們會發現有一個參數是autoCommit,也就是自動提交,咱們能夠看到上一步傳值是false,也就是不會自動提交,經過configuration(主配置)獲取environment(運行環境),而後經過environment(環境)開啓和獲取一個事務工廠,經過事務工廠獲取事務對象Transaction,經過事務對象建立一個執行器executor,Executor是一個接口,實現類有好比SimpleExecutor,BatchExecutor,ReuseExecutor,因此咱們下面代碼裏的execType,是指定它的類型,生成指定類型的Executor,把引用給接口對象,有了執行器以後就能夠return一個DefaultSqlSession對象了。ide

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        DefaultSqlSession var8;
        try {
            // configuration是主配置文件
            Environment environment = this.configuration.getEnvironment();
            // 獲取事務工廠,事務管理器可使jdbc之類的
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            // 獲取事務對象Transaction
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 經過事務對象建立一個執行器executor
            Executor executor = this.configuration.newExecutor(tx, execType);
            // DefaultSqlSession是SqlSession實現類,建立一個DefaultSqlSession並返回
            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;
    }

咱們跟 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);這句代碼,咱們這是初始化函數賦值於各個成員變量,咱們發現裏面有一個dirty成員,這是幹什麼用的呢?從名字上來說咱們理解是髒的,這裏既然設置爲false,那就是不髒的意思。那到底什麼是髒呢?髒是指內存裏面的數據與數據庫裏面的數據存在不一致的問題,若是一致就是不髒的
後面會解釋這個dirty的做用之處,到這裏一個SqlSession就建立完成了。函數

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

3.增刪改是怎麼執行的

咱們使用到這句代碼:

sqlSession.insert("insertStudent",student);

咱們發現一樣是接口方法,上面咱們知道SqlSession實際上是DefaultSqlSession所實現的接口,那麼咱們跟進去DefaultSqlSession的insert()方法,咱們發現其實inset方法底層也是實現了update這個方法,一樣的delete方法在底層也是調用了update這個方法,增,刪,改本質上都是改

public int insert(String statement, Object parameter) {
    return this.update(statement, parameter);
}
public int update(String statement) {
    return this.update(statement, (Object)null);
}

那麼咱們如今跟進去update方法中,dirty變成ture,代表即將改數據,因此數據庫數據與內存中數據不一致了,statement是咱們穿過來的id,這樣就能夠經過id拿到statement的對象,而後就經過執行器執行修改的操做:

public int update(String statement, Object parameter) {
        int var4;
        try {
            // dirty變成ture,代表數據和數據庫數據不一致,須要更新
            this.dirty = true;
            // 經過statement的id把statement從配置中拿到映射關係
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            // 執行器執行修改的操做
            var4 = this.executor.update(ms, this.wrapCollection(parameter));
        } catch (Exception var8) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);
        } finally {
            ErrorContext.instance().reset();
        }
        return var4;
    }

4.SqlSession.commit()爲何能夠提交事務(transaction)?

首先,咱們使用到的源碼,一樣選擇DefaultSqlSession這個接口的方法,咱們發現commit裏面調用了另外一個commit方法,傳進去一個false的值:

public void commit() {
        this.commit(false);
    }

咱們跟進去,發現上面傳進去的false是變量force,裏面調用了一個isCommitOrRollbackRequired(force)方法,執行的結果返回給commit方法當參數。

public void commit(boolean force) {
    try {
        this.executor.commit(this.isCommitOrRollbackRequired(force));
        // 提交以後dirty置爲false,由於數據庫與內存的數據一致了。
        this.dirty = false;
    } catch (Exception var6) {
        throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + var6, var6);
    } finally {
        ErrorContext.instance().reset();
    }
}

咱們跟進去isCommitOrRollbackRequired(force)這個方法,這個方法從命名上是須要提交仍是回滾的意思。在前面咱們知道autoCommit是false,那麼取反以後就是true,關於dirty咱們知道前面咱們執行過insert()方法,insert的底層調用了update方法,將dirty置爲true,表示即將修改數據,那咱們知道!this.autoCommit && this.dirty的值就是true,那麼就短路了,因此整個表達式的值就是true。

private boolean isCommitOrRollbackRequired(boolean force) {
    return !this.autoCommit && this.dirty || force;
}

返回上一層的,咱們知道this.isCommitOrRollbackRequired(force)的返回值是true。

this.executor.commit(this.isCommitOrRollbackRequired(force));

跟進去commit方法,這個commit方法是一個接口方法,實現接口的有BaseExecutor,還有CachingExecutor,咱們選擇BaseExecutor這個接口實現類:

// required是true
public void commit(boolean required) throws SQLException {
    // 若是已經 關閉,那麼就沒有辦法提交,拋出異常
    if (this.closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    } else {
        this.clearLocalCache();
        this.flushStatements();
        // 若是required是true,那麼就提交事務
        if (required) {
            this.transaction.commit();
        }
    }
}

5.爲何sqlsession關閉就不須要回滾了?

假如咱們在上面已經提交過了,那麼dirty的值就爲false。咱們使用的是sqlSession.close();,跟進去源碼,一樣是接口,咱們跟DefaoultSqlsession的方法,一樣調用了isCommitOrRollbackRequired()這個方法:

public void close() {
        try {
            this.executor.close(this.isCommitOrRollbackRequired(false));
            this.dirty = false;
        } finally {
            ErrorContext.instance().reset();
        }
    }

咱們跟進去isCommitOrRollbackRequired(false)這個方法,咱們知道force傳進來的值是false,autoCommit是false(只要咱們使用無參的sqlSessionFactory.openSession();),取反以後!autoCommit是true,可是dirty已是false,因此!this.autoCommit && this.dirty的值是false,那麼force也是false,因此整一個表達式就是false:

private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

咱們返回上一層,executor.close()方法,參數是false:

this.executor.close(this.isCommitOrRollbackRequired(false));

跟進去close()方法,forceRollback的值是false,咱們發現有一個this.rollback(forceRollback)

public void close(boolean forceRollback) {
        try {
            try {
                this.rollback(forceRollback);
            } finally {
                // 最後若是事務不爲空,那麼咱們就關閉事務
                if (this.transaction != null) {
                    this.transaction.close();
                }
            }
        } catch (SQLException var11) {
            log.warn("Unexpected exception on closing transaction.  Cause: " + var11);
        } finally {
            this.transaction = null;
            this.deferredLoads = null;
            this.localCache = null;
            this.localOutputParameterCache = null;
            this.closed = true;
        }
    }

咱們跟進去rollback()這個方法,咱們能夠發現required是fasle,因此 this.transaction.rollback();是不會執行的,這個由於咱們在前面作了提交了,因此是不用回滾的:

public void rollback(boolean required) throws SQLException {
        if (!this.closed) {
            try {
                this.clearLocalCache();
                this.flushStatements(true);
            } finally {
                if (required) {
                    this.transaction.rollback();
                }

            }
        }

    }

假如咱們如今執行完insert()方法,可是沒有使用commit(),那麼如今的dirty就是true,也就是數據庫數據與內存的數據不一致。咱們再執行close()方法的時候,dirty是true,!this.autoCommit是true,那麼整個表達式就是true。

private boolean isCommitOrRollbackRequired(boolean force) {
        return !this.autoCommit && this.dirty || force;
    }

返回上一層,close的參數就會變成true

this.executor.close(this.isCommitOrRollbackRequired(false));

close()方法裏面調用了 this.rollback(forceRollback);,參數爲true,咱們跟進去,能夠看到確實執行了回滾:

public void rollback(boolean required) throws SQLException {
        if (!this.closed) {
            try {
                this.clearLocalCache();
                this.flushStatements(true);
            } finally {
                if (required) {
                    this.transaction.rollback();
                }

            }
        }

    }

因此只要咱們執行了提交(commit),那麼關閉的時候就不會執行回滾,只要沒有提交事務,就會發生回滾,因此裏面的dirty是很重要的。

【做者簡介】
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界但願一切都很快,更快,可是我但願本身能走好每一步,寫好每一篇文章,期待和大家一塊兒交流。

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者覈實刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

相關文章
相關標籤/搜索