共计 8560 个字符,预计需要花费 22 分钟才能阅读完成。
代码间接放在 Github 仓库【https://github.com/Damaer/Myb…】,可间接运行,就不占篇幅了。
[TOC]
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 是很重要的。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。这个世界心愿所有都很快,更快,然而我心愿本人能走好每一步,写好每一篇文章,期待和你们一起交换。
此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~