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 自然 ...
随机推荐
- javascript的未知尺寸图片保持比例水平垂直居中函数
JavaScript的图片在容器内水平垂直居中的函数,利用图片加载获取图片大小,使之在父节点内水平垂直居中 展示方式有两种: 1.当参数keepImageFull为true:保持图片比例,使图片可完整 ...
- 程序员 & 设计师都能用上的 75 份速查手册
分享75份开发人员和设计师会用到的速查手册,由 vikas 收集整理,包括:jQuery.HTML.HTML5.CSS.CSS3.JavaScript.Photoshop .git.Linux.Jav ...
- beta版1.1.1
先期发布的alpha版1.0.0版本通过张硕组的测评,我小组跟进修改了出现的问题. 1.首先解决了互测版本中无法正常退出界面的问题,并有退出提示,(确定,取消). 2.就之前提到的关于前期部分功能的割 ...
- python 操作 memcache
目录 Memcached Memcached安装 python操作Memcached Memcache模块常用方法 Memcached Memcached是一个高性能的分布式内存对象缓存系统,用于动态 ...
- APScheduler API -- apscheduler.triggers.date
apscheduler.triggers.date API Trigger alias for add_job(): date class apscheduler.triggers.date.Date ...
- 一个罕见的MSSQL注入漏洞案例
一个罕见的MSSQL注入漏洞案例 这里作者准备分享一个在去年Google赏金计划中发现的相当罕见漏洞,也是作者在整个渗透测试生涯中唯一一次遇到的. 目标网站使用了微软 SQL Server 数据库并且 ...
- 【划水闲谈】Terraria 1.3.5更新
我知道这本应是一个算法博客,但又有谁规定了不能发点其他内容呢? Terraria,一个有趣的沙盒游戏.在这里,你可以建造,挖掘,开始一次又一次新的冒险. 4月19日,Re-Logic承诺的官方中文版终 ...
- CentOS_5.5_安装GCC编译LiME
1 概述 近期遇到个使用CentOS 5.5的系统,生产环境没有GCC.GDB.要对这台机器抓取关键内存回去用volatility分析. 思路1:使用工具Dump某个进程的内存.使用cat /proc ...
- Python类相关的装饰器
一.装饰器装饰类方法 from functools import wraps def wrapper(func): @wraps(func) def inner(self,*args,**kwargs ...
- sql的主键,int类型,自增,自动编号到了规定最大数,接下来数据库会怎么做
答案:它会从1开始重新编号,但是避开已经重复的值.