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 源码分析之事物管理的更多相关文章

  1. MyBatis源码分析之环境准备篇

    前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...

  2. 【MyBatis源码分析】环境准备

    前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...

  3. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  4. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  5. MyBatis源码分析(5)——内置DataSource实现

    @(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...

  6. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  7. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  8. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  9. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

随机推荐

  1. Enumeration和Iterator

    首先,Enumeration已经被Iterator取代了..... Enumeration是个接口,不是类,使用时需要具体的实现类. 里面只定义了两个方法: hasMoreElements()和nex ...

  2. Asp.net 中,在服务端向客户端写脚本的常用方法

    在Asp.net 服务端处理脚本,一般都用 ClientScriptManager ,即web窗体服务端的this.ClientScript.该对象比较常用的方法: 1.RegisterArrayDe ...

  3. [转]大整数算法[11] Karatsuba乘法

    ★ 引子         前面两篇介绍了 Comba 乘法,最后提到当输入的规模很大时,所需的计算时间会急剧增长,因为 Comba 乘法的时间复杂度仍然是 O(n^2).想要打破乘法中 O(n^2) ...

  4. App测试需注意

    APP测试的时候,建议让开发打好包APK和IPA安装包,测试人员自己安装应用,进行测试.在测试过程中需要注意的测试点如下: 1安装和卸载 ●应用是否可以在iOS不同系统版本或Android不同系统版本 ...

  5. usbnet驱动深入分析-usb虚拟网卡host端【转】

    转自:http://blog.csdn.net/zh98jm/article/details/6339320 1.驱动流程:   2.明确probe函数的功能: probe有usb core 经枚举过 ...

  6. 【Linux技术】ubuntu常用命令【转】

    转自:http://www.cnblogs.com/lcw/p/3159462.html 查看软件xxx安装内容:dpkg -L xxx查找软件库中的软件:apt-cache search 正则表达式 ...

  7. mysql优化【转】

    最近听讲了博森瑞老师的mysql优化公开课,这个是我整理的笔记. 现在说一下mysql的内存和I/O方面的两个特点. 一. mysql内存特点: 1.  也有全局内存和每个session的内存(每个s ...

  8. Nodejs 发送邮件

    var nodemailer = require("nodemailer");var mailTitle='http://bemupa.forumieren.com:Best Mu ...

  9. 洛谷P2886牛继电器

    传送门啦 倍增 $ Floyd $ 注意结构体里二维数组不能开到 $ 2000 $ #include <iostream> #include <cstdio> #include ...

  10. angular可自定义的对话框,弹窗指令

    指令不明的,推荐 AngularJS指令参数详解 github地址 以下为示例代码 <!DOCTYPE html> <html lang="en" ng-app= ...