Mybatis【2.2】-- Mybatis关于创建SqlSession源码分析的几点疑问?
代码直接放在Github仓库【https://github.com/Damaer/Mybatis-Learning 】,可直接运行,就不占篇幅了。
1.为什么我们使用SQLSessionFactoryBuilder的时候不需要自己关闭流?
我们看我们的代码:
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()这个方法:
// 将inputstream传递进去,调用了另一个分装的build()方法
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
跟进去,我们再来看另一个build方法,里面有一个finally模块,无论怎么样都会执行close方法,所以这就是为什么我们在使用的时候为什么不用关闭inputstream的原因:因为这个流是在finally代码块中被关闭了。
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是一个接口。
public interface SqlSessionFactory {
// 在这里只贴出了一个方法,其他的就不贴了
SqlSession openSession();
}
idea选中该方法,ctrl + alt +B,我们可以发现有DefaultSqlSessionFactory,和SqlSessionManager这两个类实现了SqlSessionFactory这个接口

那么我们需要跟进去DefaultSqlSessionFactory这个类的openSesseion方法,在里面调用了一个封装好的方法:openSessionFromDataSource()
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
当然在DefaultSqlSessionFactory这个类里面还有一个方法,参数是autoCommit,也就是可以指定是否自动提交:
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对象了。
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是很重要的。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。
此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~
Mybatis【2.2】-- Mybatis关于创建SqlSession源码分析的几点疑问?的更多相关文章
- MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...
- Mybatis整合Spring实现事务管理的源码分析
一:前言 没有完整看完,但是看到了一些关键的地方,这里做个记录,过程会有点乱,以后逐渐补充最终归档为完整流程:相信看过框架源码的都知道过程中无法完全确定是怎样的流程,毕竟不可能全部都去测试一遍 ,但是 ...
- nova创建虚拟机源码分析系列之七 传入参数转换成内部id
上一篇博文将nova创建虚机的流程推进到了/compute/api.py中的create()函数,接下来就继续分析. 在分析之前简单介绍nova组件源码的架构.以conductor组件为例: 每个组件 ...
- nova创建虚拟机源码分析系列之五 nova源码分发实现
前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...
- nova创建虚拟机源码分析系列之三 PasteDeploy
上一篇博文介绍WSGI在nova创建虚拟机过程的作用是解析URL,是以一个最简单的例子去给读者有一个印象.在openstack中URL复杂程度也大大超过上一个例子.所以openstack使用了Past ...
- nova创建虚拟机源码分析系列之一 restful api
开始学习openstack源码,源码文件多,分支不少.按照学习的方法走通一条线是最好的,而网上推荐的最多的就是nova创建虚机的过程.从这一条线入手,能够贯穿openstack核心服务.写博文仅做学习 ...
- nova创建虚拟机源码分析系列之六 api入口create方法
openstack 版本:Newton 注:博文图片采用了很多大牛博客图片,仅作为总结学习,非商用.该图全面的说明了nova创建虚机的过程,从逻辑的角度清晰的描述了前端请求创建虚拟机之后发生的一系列反 ...
- nova创建虚拟机源码分析系列之八 compute创建虚机
/conductor/api.py _build_instance() /conductor/rpcapi.py _build_instance() 1 构造一些数据类型2 修改一些api版本信息 ...
- nova创建虚拟机源码分析系列之四 nova代码模拟
在前面的三篇博文中,介绍了restful和SWGI的实现.结合restful和WSGI配置就能够简单的实现nova服务模型的最简单的操作. 如下的内容是借鉴网上博文,因为写的很巧妙,将nova管理虚拟 ...
随机推荐
- 【转】Setting up SDL Extension Libraries on Windows
FROM: http://lazyfoo.net/tutorials/SDL/06_extension_libraries_and_loading_other_image_formats/window ...
- 【总结】springmvc
一.springmvc 1.基本概念 springmvc属于三层架构(表现层,业务层,持久层)的表现层.mvc指model,view,controller.Model(模型) : 通常指的是数据模型 ...
- 4g物联网模块的原理
4G DTU模块也可以被称之为是含有第四代移动通信技术的模块,是随着科技不断发展进步下物联网和移动互联网发展下的又一产物.而4G技术包括TD-LTE和FDD-LTE两种制式.集3G与WLAN于一体并能 ...
- PHP 教程:Composer 最佳实践
概述 Composer 是 PHP 应用程序的依赖管理器,最初发布于大约 8 年前,2012 年 3 月. 在 php 中使用 Composer 可以提高代码的可重用性,并使你的项目能够轻松地集成来自 ...
- css 兼容性总结
1.就是ie6双倍边距的问题,在使用了float的情况下,不管是向左还是向右都会出现双倍,最简单的解决方法就是用display:inline;加到css里面去. 2.文字本身的大小不兼容.同样是fon ...
- c#mysql数据库
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- leetcode111:combination-sum
题目描述 给出一组候选数C和一个目标数T,找出候选数中加起来和等于T的所有组合. C中的数字在组合中可以被无限次使用 注意: 题目中所有的数字(包括目标数T)都是正整数 你给出的组合中的数字 (a 1 ...
- Python的Cmd模块的简易运用学习
昨天大佬阿炳给发了一份代码给我,看着感觉很好玩,这是自己写了个命令行吗,完了我就找篇更详细一点的博客学习了一下 cmd的主要方法和属性 方法: (1)cmdloop():类似与Tkinter的mai ...
- linux 进程间通信 共享内存 mmap
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间.进程A可以即时看到进程B对共享内存中数据的更新,反 ...
- 224、Basic Calculator
Implement a basic calculator to evaluate a simple expression string. The expression string may conta ...