Mybatis 源码分析之事物管理
Mybatis 提供了事物的顶层接口:
public interface Transaction {
/**
* Retrieve inner database connection
* @return DataBase connection
* @throws SQLException
*/
Connection getConnection() throws SQLException;
/**
* Commit inner database connection.
* @throws SQLException
*/
void commit() throws SQLException;
/**
* Rollback inner database connection.
* @throws SQLException
*/
void rollback() throws SQLException;
/**
* Close inner database connection.
* @throws SQLException
*/
void close() throws SQLException;
}
还有一个事物工厂:
public interface TransactionFactory {
/**
* Sets transaction factory custom properties.
* @param props
*/
void setProperties(Properties props);
/**
* Creates a {@link Transaction} out of an existing connection.
* @param conn Existing database connection
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* Creates a {@link Transaction} out of a datasource.
* @param dataSource DataSource to take the connection from
* @param level Desired isolation level
* @param autoCommit Desired autocommit
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
对于这两个接口我们一般是不直接操作的,但是它的影响是实实在在的。毕竟作为一个 ORM 框架,事物的管理是少不了的。它的实现大致可以分为两类,非 Spring 相关的事物和基于 Spring 管理的事物。
非 Spring 相关
关于 JdbcTransaction 和 ManagedTransaction 这两个实现就不多说了,实际上 getConnection 和 close 方法都是直接操作的 Connection。ManagedTransaction 的提交和回滚是个空的实现,交给容器了。
Mybatis对外提供的统一接口是SqlSession,通常情况下我们可以这样使用:
public void doSomethingWithTemplate(){
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try {
sqlSession = sqlSessionFactory.openSession();
doSomething();
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
DefaultSqlSession 持有 Executor,将事物相关的操作做了简单的封装,Executor 又在此基础上加入了一级缓存等相关操作,如 commit 方法:
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
最终都调用了Mybatis提供的事物接口相关方法,以 JdbcTransaction 为例:
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
这个提交就是判断连接不为 null,而且不是自动提交的事物,那么就调用 JDBC 连接的 commit 方法,回滚也是类似。
结合 Spring
在mybatis-spring中,提供了一个实现:
public class SpringManagedTransactionFactory implements TransactionFactory {
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
public void setProperties(Properties props) {
// not needed in this version
}
}
在SqlSessionFactoryBean 中的 buildSqlSessionFactory 方法中指定了事物工厂为 SpringManagedTransactionFactory,然后将其放到 Environment 实例中:
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
//......
return this.sqlSessionFactoryBuilder.build(configuration);
最后返回了sqlSessionFactoryBuilder 的 build 方法构建的 SqlSessionFactory:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
DefaultSqlSessionFactory#openSessionFromDataSource:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这时候 DataSource 是 Spring 管理的,事物是 SpringManagedTransaction。连接是找 Spring 要的,跟踪一下发现事物由 Spring 直接处理了。
管理与操作
不管有没有 Spring,Mybatis 都要作为一个独立的 ORM 框架存在,所以事物管理是免不了的。Mybatis 定义了 Transaction 接口,对外提供的 SqlSession 间接操作了事物相关的接口。底层可以有不同的实现,处理起来更灵活。
JdbcTransaction 中的实现就是直接操作 JDBC Connection,ManagedTransaction 的实现就是为空,不做任何处理。但是作为和 Spring 结合使用的 SpringManagedTransaction,这个就有点复杂了。
都交给了 Spring,那么它怎么办,Mybatis 底层也有依赖于事物的操作,如缓存。
SqlSessionUtils 中的 SqlSessionSynchronization 内部类继承了 TransactionSynchronizationAdapter,当 Spring 提交或者回滚时就会通过这个来回调。具体可以在事物提交前和提交后做一些操作,来弥补事物不在 Mybatis 这一方带来的缺憾。
谁来管理,谁来操作
看一下 SpringManagedTransaction 是怎么获取连接的:
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = isConnectionTransactional(this.connection, this.dataSource);
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
再看下提交和回滚的实现:
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Committing JDBC Connection [" + this.connection + "]");
}
this.connection.commit();
}
}
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rolling back JDBC Connection [" + this.connection + "]");
}
this.connection.rollback();
}
}
最后是否提交和回滚还得依赖一系列判断,其中 isConnectionTransactional 是这样判断的:
public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
if (dataSource == null) {
return false;
}
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
return (conHolder != null && connectionEquals(conHolder, con));
}
就是当前的JDBC连接是否是事务性的。如果是 Spring 管理的事物,这里就返回 true。如果不是,还能留一手,在这里补上一脚。
状态的维护
既然事物是 Spring 管理,它如何管理呢?
就拿基于 AOP 的事物管理来说,切面都在 Service 层,考虑这样一种情况,一个线程中可能同时存在多个事物:把一个基于 Servlet 的请求看作一个线程,调用了一个 Service ,而这个 Service 包含了多个相关 Mapper 的操作,这些操作必须存在于同一个事物中。
Mapper 方法的执行模板位于 SqlSessionTemplate:
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
这个和我们客户端的方法执行模板还是有区别的,第一个需要考虑的问题是:这个方法往上层追溯,必然是位于 Spring 的事物执行模板之中。那么这里的 SqlSession 的获取和关闭就不能随随便便,必须跟着“上面”走。
查看源码可以看到,不论是 SqlSession 的获取还是关闭,都是基于当前事物的状态判断,而不是直接在 ThreadLocal 中拿或者直接创建一个新的,体现在代码中就是:
SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
holder.requested();
if (logger.isDebugEnabled()) {
logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
}
return holder.getSqlSession();
}
if (logger.isDebugEnabled()) {
logger.debug("Creating a new SqlSession");
}
SqlSession session = sessionFactory.openSession(executorType);
第一个就是 holder 不为 null, 第二是要求在同一个事物中,这些判断依赖于 ResourceHolderSupport 和 TransactionSynchronizationManager,这是比线程更细粒度的控制,来自 Spring 管理的事物状态。
试想一下,作为底层的框架,事物由 Spring 管理,Mybatis 如何知道事物是否开启,如何判断是否在同一个事物,而这些它不能不知道,毕竟 SqlSession 是它管理的,而 SqlSession 的生命周期又和事物息息相关。
上面举了一个例子,如果多个 Mapper 存在于同一个事物中,那么每次获取的 SqlSession 必然是同一个,不会创建新的,这样一级缓存也会发挥出功效。
如果多个请求最终调用同一个 Mapper 呢?这时候 Mapper 持有的 SqlSession 是同一个(SqlSessionTemplate),但实际在 invoke 方法中获取 SqlSession 却各不相同。
最后对 Mybatis 事物做一个总结:

Mybatis 源码分析之事物管理的更多相关文章
- MyBatis源码分析之环境准备篇
前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...
- 【MyBatis源码分析】环境准备
前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(5)——内置DataSource实现
@(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...
- MyBatis源码分析(4)—— Cache构建以及应用
@(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...
- 【MyBatis源码分析】select源码分析及小结
示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...
- Mybatis源码分析-BaseExecutor
根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
随机推荐
- CentOS 7快速入门系列教程(一)
基本命令 ls 列举当前目录下的所有文件夹 ls -l 查看文件还是文件夹 d表示文件夹 -表示文件 ls --help man ls 询问命令 man 3 malloc 查看函数 cd 跳转 ...
- 【转】Graphics.DrawImage 方法 IntPtr 结构 GDI 句柄 知识收集
Graphics.DrawImage 方法 在指定的位置使用原始物理大小绘制指定的 Image. 命名空间:System.Drawing 程序集:System.Drawing(在 system.dra ...
- 微服务深入浅出(6)-- 熔断器Hystrix
概念 在分布式系统中,一种不可避免的情况就是某些服务会出现故障,导致依赖他们的其他服务出现远程调度的线程问题(雪崩效应).而Hystrix提供的熔断器,通过隔离服务的访问点,能阻止这种分布式系统中出现 ...
- react页面间传递参数
react-router页面跳转,带请求参数 this.context.router.push({pathname:'/car_datail',state:{item:"hello" ...
- ECMAScript——(二)
1.语法 区分大小写 变量是弱类型(定义变量时只用 var 运算符,可以将它初始化为任意值.) 每行结尾的分号可有可无(建议写上) 注释与Java一样 括号表示代码块 2.变量 变量可以不用声明,变量 ...
- 微信小程序实战--开发一个简单的快递单号查询
功能如图: 虽然工作中只负责小程序后台开发,但是还是小程序开发产生了浓厚的兴趣,官方文档也是超级详细了 这里就简单做一个快递单号的查询: 新建一个page: 接着就可以写wxml了.这里用一个简单的i ...
- 【洛谷题解】P2303 [SDOi2012]Longge的问题
题目传送门:链接. 能自己推出正确的式子的感觉真的很好! 题意简述: 求\(\sum_{i=1}^{n}gcd(i,n)\).\(n\leq 2^{32}\). 题解: 我们开始化简式子: \(\su ...
- 【黑客免杀攻防】读书笔记14 - 面向对象逆向-虚函数、MFC逆向
虚函数存在是为了克服类型域解决方案的缺陷,以使程序员可以在基类里声明一些能够在各个派生类里重新定义的函数. 1 识别简单的虚函数 代码示例: #include "stdafx.h" ...
- fc26 url
aarch64 http://linux.yz.yamagata-u.ac.jp/pub/linux/fedora-projects/fedora-secondary/releases/26/Ever ...
- docker 错误排查:无法进入容器.
docker 错误排查:无法进入容器. #docker exec -it 3c1d bash rpc error: code = 2 desc = oci runtime error: exec fa ...