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 自然 ...
随机推荐
- why inline functions must be put in header files?
[why inline functions must be put in header files?] 编译中有2个过程:compile.link.先进行compile,compile中把源代码编译成 ...
- HTML5新增的本地存储功能(笔记)
HTML5新增的本地存储功能分为两种,分别对应两个JS对象:①本地存储对应localStorage对象,主要用于长期保存整个网站的数据(这些数据可以永久保存在客户端电脑硬盘内).②会话存储对应sess ...
- 浅谈ASP.net处理XML数据
XML是一种可扩展的标记语言,比之之前谈到的html有着很大的灵活性,虽然它只是与HTML仅有一个字母只差,但两者有很大的区别. XML也是标记语言,所以它每个标签必须要闭合,而HTML偶尔忘了闭合也 ...
- xgraph和gnuplot初体验
今天分别体验了一下xgraph和gnuplot. xgraph是ns2自带的画图工具,使用很简单.它的标准的数据文件是ascii文本文件,每一行两个数据,以空格隔开,这样就有了两列数据.把这样的文 ...
- HDU 1717 小数化分数2 数学题
解题报告:输入一个小于1的小数,让你把这个数转化成分数,但注意,输入的数据还有无限循环的小数,循环节用一对括号包含起来. 之前还没有写过小数转分数的题,当然如果没有循环小数的话,应该比较简单,但是这题 ...
- Nginx配置location及rewrite规则
Nginx配置location及rewrite规则 示例: location = / { # 精确匹配 / ,主机名后面不能带任何字符串 [ configuration A ] } loca ...
- C# 安装部署Windows服务脚本
@echo off Installutil.exe 程序目录 F:\test\TestWindows.exe 服务程序目录 @sc start "服务名称" @sc config ...
- 正在载入中......loading页面的几种方法
网页加载过程中提示“载入中…”,特别是使用动画效果,可以一个“等待”的温馨提示,用户体验很不错.下面介绍几种方法. 第一种: 原理就是,在网页载入时在页面最中间打入一个层上面显示,"网页正在 ...
- css单行文本和多行文本溢出实现省略号显示
1.单行文本溢出 文本内容 <div class="singleLine"> HelloWorldHelloWorldHelloWorldHelloWorldHello ...
- Java NIO 之 Channel(通道)
历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 一 Channel ...