MyBatis
中对事务处理主要有两种方式,一种是使用JDBC
原生事务管理,另一种则是委托Spring
框架来完成对事务的控制。虽然其事务管理实现方式简单,但其中所蕴藏的知识却不简单。
前言
我们平时所谈及的事务
其实是可以看做是数据库
操作的一个逻辑单元。它由一系列的数据库操作组成,为了确保满足数据库ACID
中的一致性
。这些操作要么全部成功提交,要么全部回滚,不能存在部分成功部分失败的状态。
举个简单的例子,在一个银行转账业务操作中,涉及到从一个账户扣款和向另一个账户存款两个操作,这两个操作必须同时成功。只有这样才能有效保证资金的安全和数据库的一致性。
Mybatis
的事务管机制
Mybatis
作为主流的持久层框架,其主要通过JdbcTransaction
和ManagedTransaction
两种方式来实现对事务
的控制

- 使用
JDBC
原生事务管理 :JdbcTransaction
其实是使用JDBC
原生的事务管理方式,通过java.sql.Connection
对象来控制事务。在这种方式下,MyBatis
会从数据源获取一个Connection
对象,然后由开发者手动调用Connection
的commit()
方法来提交事务,调用rollback()
方法来回滚事务。具体源码逻辑如下:
java
public class JdbcTransaction implements Transaction {
// 提交事务
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
// 回滚事务
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
// 关闭事务
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
// ... 省略其他方法
}
- 使用 Spring 框架的事务管理 :
ManagedTransaction
含义为托管事务,即其内部不会对事物进行管理,而是将事务控制托管给其它框架。例如,在Mybatis
整合Spring
框架的项目中,通常会借助Spring
的事务管理机制来管理事务。
java
public class ManagedTransaction implements Transaction {
// 提交事务
public void commit() throws SQLException {
}
// 回滚事务
public void rollback() throws SQLException {
}
// 关闭事务
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
// ... 省略其他方法
}
可以看到,在ManagedTransaction
中它既不会提交也不会回滚一个连接,而是将事务的整个生命周期管理工作交由容器处理,像Spring
或 JEE
应用服务器的上下文环境就可以充当这样的容器。
而当使用 springBoot
项目中当引入mybatis
依赖后其实我们无需手动配置ManagedTransaction
。这是因为在springboot-start
中会根据项目中配置的数据源自动创建合适的事务管理器并注册到 Spring
容器中,进而直接使用 @Transactional
注解来实现事务控制。
事实上,无论是 SqlSession
,还是 Executor
,它们的事务方法,最终都指向了 Transaction
的事务方法,即都是由 Transaction
来完成事务提交、回滚的。

Mybatis
中有关事务的几种特殊场景
理解 Mybatis
中的手动
事务控制
我们前面讲了Mybatis
中的事务管理器,但我们还是要明确一点。那便是在 MyBatis
里,虽然所有的sql
执行都委托于Executor
,但这不意味着Executor
中的执行insert ()、update ()
等方法时,其会显示控制事务
。换言之,在Executor
中执行数据库操纵方法时,其并不会出现所谓的commit,rollback
等操作。
而当我们单纯使用Mybatis
框架时,我们的手动控制事务的方式大致如下:
java
public class ManualTransactionExample {
public static void main(String[] args) {
try {
// 加载 MyBatis 配置文件
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new org.apache.ibatis.session.SqlSessionFactoryBuilder().build(reader);
// 手动创建 SqlSession,关闭自动提交模式
SqlSession sqlSession = sqlSessionFactory.openSession(false);
try {
// 执行 SQL 操作
// 例如调用 mapper 方法
// UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// userMapper.insertUser(user);
// 手动提交事务
sqlSession.commit();
} catch (Exception e) {
// 发生异常时回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally {
// 关闭 SqlSession
sqlSession.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,通过 sqlSessionFactory.openSession(false)
创建 SqlSession
实例,false
表示关闭自动提交模式。之后在执行 SQL
操作后,若操作成功则调用 sqlSession.commit()
提交事务;若发生异常,则调用 sqlSession.rollback()
回滚事务。
再次强调,上述的事务控制其实是我们人为添加的,并不是框架内部处理的。也就是说,在后续我们分析Executor
中有关数据操纵的insert ()、update ()
等方法内部时,需要忘记事务的存在,更不要试图在执行器Executor
中 insert ()
等方法内部寻找有关事务
的任何方法。
sqlSession
生命周期内可以多个事务
事实上,JDBC
中并存在所谓的Session
相关信息。这其实就使得在程序中执行多次insert,update
操作的话,其实就会开启多个事务。
java
// 执行了connection.setAutoCommit(false),并返回
SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("yy");
student.setEmail("[email protected]");
student.setDob(new Date());
student.setPhone(new PhoneNumber("123-2568-8947"));
// 第一次插入
studentMapper.insertStudent(student);
// 提交
sqlSession.commit();
// 第二次插入
studentMapper.insertStudent(student);
// 多次提交
sqlSession.commit();
} catch (Exception e) {
// 回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally {
sqlSession.close();
}
上述代码其实开启两个事务,具体来看:
-
第一次事务:
- 当执行
studentMapper.insertStudent(student);
时,由于setAutoCommit(false)
,此次插入操作被纳入当前事务中。 - 随后调用
sqlSession.commit();
,这使得第一次插入操作所在的事务被提交,第一次事务结束。
- 当执行
-
第二次事务:
- 接着再次执行
studentMapper.insertStudent(student);
,又开启了一个新的事务,该插入操作被包含在这个新事务里。 - 之后调用
sqlSession.commit();
,新事务被提交,第二次事务结束。
- 接着再次执行
那如果在执行sql
操作时出现异常,此时我们rollbak
具体执行逻辑如下:
- 在第一次
insert
之后且第一次commit
之前发生异常
java
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
// ... 初始化 student 对象
// 第一次插入
studentMapper.insertStudent(student);
// 抛出异常
throw new RuntimeException();
// ... 省略后续插入逻辑
} catch (Exception e) {
// 回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally {
sqlSession.close();
}
当在第一次 insert
之后且第一次 commit
之前发生异常时,rollback
会回滚第一次 insert
操作。因为在第一次 commit
之前,第一次 insert
操作处于一个未提交的事务中,调用 rollback
会撤销该事务中的所有操作,也就是撤销第一次 insert
操作。
- 第二次
insert
之后、第二次commit
之前发生异常
java
try {
// 第二次插入
studentMapper.insertStudent(student);
// 模拟异常发生
throw new RuntimeException();
// 多次提交
sqlSession.commit();
} catch (Exception e) {
// 回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally {
sqlSession.close();
}
当、第二次 commit
之前发生异常时,rollback
会回滚第二次 insert
操作。这是因为第一次 insert
操作所在的事务已经通过 commit
提交,而第二次 insert
操作处于一个新的未提交事务中,调用 rollback
会撤销这个未提交事务中的操作,即撤销第二次 insert
操作,而此时并不会撤销第一次提交内容。
- 在第二次
commit
之后发生异常
java
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
// ... 初始化 student 对象
// 第一次插入
studentMapper.insertStudent(student);
// 提交
sqlSession.commit();
// 第二次插入
studentMapper.insertStudent(student);
// 多次提交
sqlSession.commit();
// 假设这里发生异常
} catch (Exception e) {
// 回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally {
sqlSession.close();
}
当在第二次 commit
之后发生异常时,rollback
不会回滚任何操作。因为两次 insert
操作所在的事务都已经通过 commit
提交,此时不存在未提交的事务,调用 rollback
不会有任何效果。
总之,当autoCommit=false 时 ,是自动开启事务的,执行 commit () 后,该事务结束 。以上代码正常情况下,开启了2
个事务,向数据库插入了 2 条数据。
由于JDBC
中不存在 Mybatis
中的 session
的概念,所以并不是说一个SqlSession
整个生命周期只有一个事务,而是执行几次sql
操作就有几个事务。
具体到上述例子,执行几次insert
语句,数据库就会有几条记录。切勿将SqlSession
与Connection
混淆,一个SqlSession
生命周期内其实可以由多个事务
,而而 rollback (),只能回滚当前未提交的事务。而不能回滚之前已提交事务。
关闭自动提交,但未执行Commit
会发生什么
还以上述代码为例,只不过此时我们将SqlSession
中的autoCommit
属性设定为false
,即关闭SqlSession
的自动提交。
java
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
studentMapper.insertStudent(student);
} finally {
sqlSession.close();
}
此时与之前不同的是,我们在代码中并未手动 commit
而仅执行close
关闭当前会话,那如果不执行提交操作,仅调用会话的关闭方法,事务内部究竟会发生什么?
MyBatis
在架构设计时已充分考虑到此类情况。当执行close
方法时,MyBatis
会进行一系列逻辑判断,并根据判断结果决定是否执行rollback
操作。
SqlSession # close
java
public void close() {
try {
// 根据传入的变量判定是否进行回滚操作
executor.close(isCommitOrRollbackRequired(false));
// baseExecutor执行,如果传入true执行回滚操作
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
BaseExecutor # close
java
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
// .... 省略无关代码
}
}
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
//如果为true则执行Transaction中回滚操作
transaction.rollback();
}
}
}
}
这一过程中,可以看到在isCommitOrRollbackRequired
方法判断中autocommit
和dirty
两个关键变量发挥着核心作用。其中,dirty
变量用于标识数据是否为脏数据,其默认值为false
。当执行数据更新、插入等操作后,dirty
的值会相应改变,一旦数据被认定为脏数据,dirty
将返回true
。在后续执行会话close
方法时,若检测到dirty
为true
,执行器便会触发回滚操作,以此避免脏数据写入数据库,确保数据的一致性和完整性。
值得注意的是,在数据插入操作后、关闭会话之前,如果数据库的事务隔离级别设置为read uncommitted
(读未提交),由于该隔离级别允许读取未提交的数据,此时在数据库中能够查询到这条新插入的记录。
然而,当执行sqlSession.close()
时,MyBatis
会根据autocommit
和dirty
等变量的状态进行判断,一旦满足回滚条件,便会自动执行rollback()
操作。随着事务回滚,之前查询到的那条记录也会从数据库中消失,从而维持了数据的最终一致性。
总结
Mybatis 的 JdbcTransaction,和纯粹的 Jdbc 事务,几乎没有差别,它仅是扩展支持了连接池的 connection。 另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何update、delete、insert
,都一定是在事务中进行的,这是数据库的设计规范之一。
同时,我们本文还就Mybatis
中有关事务的两大常见误区,希望本文对你理解Mybatis
事务管理有所帮助。