0

0

Mybatis【2.2】-- Mybatis创建sqlSession源码分析的几点疑问?

爱谁谁

爱谁谁

发布时间:2025-07-20 09:50:14

|

943人浏览过

|

来源于php中文网

原创

1.为什么我们使用SQLSessionFactoryBuilder的时候不需要自己关闭流?

我们看我们的代码:

代码语言:javascript代码运行次数:0运行复制
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()这个方法:

代码语言:javascript代码运行次数:0运行复制
    // 将inputstream传递进去,调用了另一个分装的build()方法    public SqlSessionFactory build(InputStream inputStream) {        return this.build((InputStream)inputStream, (String)null, (Properties)null);    }

跟进去,我们再来看另一个build方法,里面有一个finally模块,无论怎么样都会执行close方法,所以这就是为什么我们在使用的时候为什么不用关闭inputstream的原因:因为这个流是在finally代码块中被关闭了。

代码语言:javascript代码运行次数:0运行复制
    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的会话,类似我们想要发信息给别人,肯定需要打开一个和别人的会话。

代码语言:javascript代码运行次数:0运行复制
sqlSession=sqlSessionFactory.openSession();

我们需要查看源码,我们发现opensession是sqlSessionFactory的一个接口方法,sqlSessionFactory是一个接口。

代码语言:javascript代码运行次数:0运行复制
public interface SqlSessionFactory {    // 在这里只贴出了一个方法,其他的就不贴了    SqlSession openSession();    }

idea选中该方法,ctrl + alt +B,我们可以发现有DefaultSqlSessionFactory,和SqlSessionManager这两个类实现了SqlSessionFactory这个接口

Mybatis【2.2】-- Mybatis创建sqlSession源码分析的几点疑问?

那么我们需要跟进去DefaultSqlSessionFactory这个类的openSesseion方法,在里面调用了一个封装好的方法:openSessionFromDataSource()

代码语言:javascript代码运行次数:0运行复制
    public SqlSession openSession() {        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);    }

当然在DefaultSqlSessionFactory这个类里面还有一个方法,参数是autoCommit,也就是可以指定是否自动提交:

代码语言:javascript代码运行次数:0运行复制
    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对象了。

代码语言:javascript代码运行次数:0运行复制
    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就创建完成了。

代码语言:javascript代码运行次数:0运行复制
    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {        this.configuration = configuration;        this.executor = executor;        this.dirty = false;        this.autoCommit = autoCommit;    }
3.增删改是怎么执行的

我们使用到这句代码:

代码语言:javascript代码运行次数:0运行复制
sqlSession.insert("insertStudent",student);

我们发现同样是接口方法,上面我们知道SqlSession其实是DefaultSqlSession所实现的接口,那么我们跟进去DefaultSqlSession的insert()方法,我们发现其实inset方法底层也是实现了update这个方法,同样的delete方法在底层也是调用了update这个方法,增,删,改本质上都是改。

代码语言:javascript代码运行次数:0运行复制
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的对象,然后就通过执行器执行修改的操作:

代码语言:javascript代码运行次数:0运行复制
    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的值:

代码语言:javascript代码运行次数:0运行复制
    public void commit() {        this.commit(false);    }

我们跟进去,发现上面传进去的false是变量force,里面调用了一个isCommitOrRollbackRequired(force)方法,执行的结果返回给commit方法当参数。

代码语言:javascript代码运行次数:0运行复制
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。

代码语言:javascript代码运行次数:0运行复制
private boolean isCommitOrRollbackRequired(boolean force) {    return !this.autoCommit && this.dirty || force;}

返回上一层的,我们知道this.isCommitOrRollbackRequired(force)的返回值是true。

代码语言:javascript代码运行次数:0运行复制
this.executor.commit(this.isCommitOrRollbackRequired(force));

跟进去commit方法,这个commit方法是一个接口方法,实现接口的有BaseExecutor,还有CachingExecutor,我们选择BaseExecutor这个接口实现类:

代码语言:javascript代码运行次数:0运行复制
// required是truepublic 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()这个方法:

代码语言:javascript代码运行次数:0运行复制
    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:

代码语言:javascript代码运行次数:0运行复制
    private boolean isCommitOrRollbackRequired(boolean force) {        return !this.autoCommit && this.dirty || force;    }

我们返回上一层,executor.close()方法,参数是false:

代码语言:javascript代码运行次数:0运行复制
this.executor.close(this.isCommitOrRollbackRequired(false));

跟进去close()方法,forceRollback的值是false,我们发现有一个this.rollback(forceRollback)

代码语言:javascript代码运行次数:0运行复制
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();是不会执行的,这个因为我们在前面做了提交了,所以是不用回滚的:

代码语言:javascript代码运行次数:0运行复制
     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。

代码语言:javascript代码运行次数:0运行复制
    private boolean isCommitOrRollbackRequired(boolean force) {        return !this.autoCommit && this.dirty || force;    }

返回上一层,close的参数就会变成true

代码语言:javascript代码运行次数:0运行复制
this.executor.close(this.isCommitOrRollbackRequired(false));

close()方法里面调用了this.rollback(forceRollback);,参数为true,我们跟进去,可以看到确实执行了回滚:

代码语言:javascript代码运行次数:0运行复制
     public void rollback(boolean required) throws SQLException {        if (!this.closed) {            try {                this.clearLocalCache();                this.flushStatements(true);            } finally {                if (required) {                    this.transaction.rollback();                }            }        }    }

所以只要我们执行了提交(commit),那么关闭的时候就不会执行回滚,只要没有提交事务,就会发生回滚,所以里面的dirty是很重要的。

【作者简介】:秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。

此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~

- END -

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

556

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

732

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

414

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

991

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

658

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

553

2023.09.20

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

3

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 3.5万人学习

SQL优化与排查(MySQL版)
SQL优化与排查(MySQL版)

共26课时 | 2.3万人学习

MySQL索引优化解决方案
MySQL索引优化解决方案

共23课时 | 2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号