Mybatis【2.2】-- Mybatis关于建立SqlSession源码分析的几点疑问?

代码直接放在Github仓库【 https://github.com/Damaer/Myb... 】,可直接运行,就不占篇幅了。

[TOC]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是很重要的。

【做者简介】
秦怀,公众号【秦怀杂货店】做者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界但愿一切都很快,更快,可是我但愿本身能走好每一步,写好每一篇文章,期待和大家一块儿交流。

此文章仅表明本身(本菜鸟)学习积累记录,或者学习笔记,若有侵权,请联系做者核实删除。人无完人,文章也同样,文笔稚嫩,在下不才,勿喷,若是有错误之处,还望指出,感激涕零~

相关文章
相关标签/搜索