MyBatis 自己的事务管理是怎样的?和 Spring 的 @Transactional 有什么关系?
简化版
MyBatis 自己有一套事务管理,由 TransactionFactory + Transaction 抽象,两种内置类型:① JDBC——MyBatis 直接用 JDBC 的 Connection 管理事务(connection.commit()/rollback()),自己控制提交回滚;② MANAGED——MyBatis「不管事务」,把事务交给外部容器(如应用服务器 JTA)管理,它自己什么都不做。在原生 MyBatis 里,事务是通过 SqlSession 手动控制的:openSession() 默认不自动提交,你要自己 session.commit()/rollback(),用的就是 JDBC 事务类型。但在和 Spring 整合后,MyBatis 自己的事务管理「让位」给 Spring——mybatis-spring 用一个 SpringManagedTransaction,事务的开启/提交/回滚全交给 Spring 的 @Transactional(DataSourceTransactionManager)统一管理,MyBatis 只负责用「Spring 事务绑定的那个连接」执行 SQL。所以实际项目里你几乎不直接用 MyBatis 的事务,而是用 Spring 的 @Transactional——MyBatis 的事务管理主要是「原生使用」和「理解底层」时才涉及。
详细版
两种内置事务类型:
| 类型 | 谁管事务 | 怎么工作 | 用在哪 |
|---|---|---|---|
| JDBC | MyBatis 自己 | 直接用 Connection.commit/rollback | 原生 MyBatis、独立使用 |
| MANAGED | 外部容器 | MyBatis 不提交/不回滚(交给容器) | JTA/应用服务器管理事务 |
| (SpringManaged) | Spring | 跟随 Spring 事务同步 | mybatis-spring 整合后 |
// 原生 MyBatis:手动控制事务(JDBC 类型)
SqlSession session = factory.openSession(); // 默认不自动提交
try {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.update(...);
mapper.update(...);
session.commit(); // 手动提交(两个操作在一个事务里)
} catch (Exception e) {
session.rollback(); // 出错回滚
} finally {
session.close();
}
// openSession(true):自动提交(每条 SQL 独立提交,通常不用于业务)
// Spring 整合后:用 @Transactional,不碰 MyBatis 的事务 API
@Transactional
public void transfer() {
userMapper.update(...); // Spring 管理事务,两个操作同事务
userMapper.update(...); // @Transactional 提交/回滚
}
⚠️ 一个高频细节:原生
openSession()默认「不自动提交」——你不commit(),增删改就不会真正写库。很多人第一次用原生 MyBatis 做insert,发现「代码跑了、没报错,但数据库里没数据」,就是忘了session.commit()(默认手动提交模式,操作只在事务里、没提交就等于没生效,close()时还会回滚)。而openSession(true)是自动提交,但自动提交不适合业务(每条 SQL 独立提交,无法保证多个操作的原子性)。到了 Spring 里就没这个坑了——@Transactional帮你管提交回滚。所以要记住:原生 MyBatis 手动提交要自己commit;Spring 里交给@Transactional,两者别混着用(在 Spring 事务里又手动commit会出问题)。
完整版教学
一、MyBatis 的事务抽象
先理解 MyBatis 自己的事务抽象结构:
MyBatis 的事务抽象(两个接口):
TransactionFactory:创建 Transaction 的工厂
Transaction:事务对象,提供
getConnection():拿数据库连接
commit():提交
rollback():回滚
close():关闭
两种内置实现(在 mybatis-config.xml 的 <transactionManager> 配):
<transactionManager type="JDBC"/> → JdbcTransactionFactory
<transactionManager type="MANAGED"/> → ManagedTransactionFactory
SqlSession 内部持有一个 Transaction:
你调 session.commit() → 实际调 transaction.commit()
→ 具体怎么提交,看 Transaction 是哪种实现
所以 MyBatis 把"事务怎么管"抽象成 Transaction 接口
→ 不同实现(JDBC/MANAGED/Spring)用不同方式管事务
MyBatis 的事务抽象是 TransactionFactory(创建工厂)+ Transaction(事务对象,提供 getConnection/commit/rollback/close)。两种内置实现:JDBC(JdbcTransactionFactory)、MANAGED(ManagedTransactionFactory),在配置里选。SqlSession 内部持有一个 Transaction,session.commit() 实际调 transaction.commit()——具体怎么提交看是哪种实现。理解「MyBatis 事务抽象:TransactionFactory+Transaction(getConnection/commit/rollback)、两种内置 JDBC/MANAGED、SqlSession 持有 Transaction、commit 委托给它」,就理解了事务抽象结构。
二、JDBC 事务类型
JDBC 类型是「MyBatis 自己用 JDBC 连接管理事务」:
JDBC 事务类型(JdbcTransaction):
直接用底层的 java.sql.Connection 管理事务:
- 拿到 Connection
- 设 autoCommit=false(关闭自动提交,开启事务)
- commit() → connection.commit()
- rollback() → connection.rollback()
所以 JDBC 类型 = "MyBatis 自己用 Connection 的事务能力"
它掌控着这个连接的提交/回滚
用在哪:
- 独立使用 MyBatis(不整合 Spring)时的默认选择
- 你通过 SqlSession.commit()/rollback() 控制事务
一个 SqlSession = 一个 Transaction = 一个 Connection = 一个事务
这就是为什么"一个 SqlSession 里的多个操作在同一个事务"
(它们共用一个连接,一起 commit 或 rollback)
JDBC 类型是「MyBatis 自己用 Connection 管理事务」——拿到 Connection、设 autoCommit=false、commit/rollback 直接调 connection 的对应方法。它是独立使用 MyBatis 时的默认选择,你通过 SqlSession.commit/rollback 控制。一个 SqlSession = 一个 Transaction = 一个 Connection = 一个事务——所以一个 SqlSession 里的多个操作在同一事务(共用一个连接,一起 commit/rollback)。理解「JDBC 类型=MyBatis 用 Connection 管事务(autoCommit=false,commit 调 connection)、独立使用默认、一个 SqlSession=一个连接=一个事务」,就理解了 JDBC 事务类型。
三、MANAGED 事务类型
MANAGED 类型是「MyBatis 不管事务,交给外部容器」:
MANAGED 事务类型(ManagedTransaction):
MyBatis 自己"什么都不做"——
commit():空操作(不提交)
rollback():空操作(不回滚)
→ 把事务交给"外部容器"管理
用在哪:
- 应用运行在 JEE 应用服务器里,用容器的 JTA 事务
- 事务由容器(如 WebLogic、WebSphere 的 JTA)统一管理
- MyBatis 只负责执行 SQL,事务边界由容器控制
为什么 MyBatis 要"什么都不做":
因为事务由更上层的容器管了(JTA 全局事务、可能跨多个资源)
如果 MyBatis 自己也 commit → 和容器的事务管理冲突
→ 所以 MANAGED 让 MyBatis 退出事务管理,避免冲突
现在用得少:
纯 JEE 容器 JTA 的场景少了;
Spring 整合是主流(下一节),Spring 有自己的方式
MANAGED 类型是「MyBatis 不管事务,交给外部容器」——commit/rollback 都是空操作,事务交给外部容器(如 JEE 应用服务器的 JTA)管理。MyBatis 只执行 SQL、事务边界由容器控制。为什么「什么都不做」——因为事务由更上层容器管了(可能是跨多资源的 JTA 全局事务),MyBatis 自己 commit 会冲突。现在用得少(纯 JEE 容器 JTA 场景少,Spring 整合是主流)。理解「MANAGED 类型=MyBatis 不管事务(commit/rollback 空操作)交给外部容器(JTA)、避免和容器事务冲突、现在用得少」,就理解了 MANAGED 事务类型。
四、Spring 整合:SpringManagedTransaction
和 Spring 整合后,MyBatis 事务「让位」给 Spring:
mybatis-spring 引入 SpringManagedTransaction:
它是 Transaction 的一个特殊实现,行为是"跟随 Spring 事务":
- getConnection():从 Spring 的事务同步管理器
(DataSourceUtils.getConnection) 拿"当前 Spring 事务绑定的连接"
- commit()/rollback():★通常"不主动做"——
因为事务的提交/回滚由 Spring 的事务管理器统一控制
(@Transactional 结束时,Spring 提交/回滚那个连接)
效果:
MyBatis 用的连接 = Spring 事务绑定的连接
MyBatis 不自己提交/回滚 → 全听 Spring 的 @Transactional
→ MyBatis 的操作天然纳入 Spring 事务
所以整合后:
- 你用 @Transactional 声明事务边界
- MyBatis 只管用"Spring 给的连接"执行 SQL
- 提交/回滚由 Spring 统一做
→ MyBatis 自己的 JDBC/MANAGED 都不用了,用 SpringManagedTransaction
和 Spring 整合后,mybatis-spring 用 SpringManagedTransaction——它「跟随 Spring 事务」:getConnection 从 Spring 事务同步管理器拿「当前 Spring 事务绑定的连接」,commit/rollback 通常不主动做(由 Spring 事务管理器统一控制)。效果是 MyBatis 用 Spring 事务绑定的连接、自己不提交回滚、全听 @Transactional——MyBatis 操作天然纳入 Spring 事务。所以整合后用 @Transactional 声明边界、MyBatis 只管执行 SQL、Spring 统一提交回滚(见 spring-integration 题)。理解「Spring 整合用 SpringManagedTransaction 跟随 Spring 事务:用 Spring 绑定的连接、自己不提交回滚、听 @Transactional、MyBatis 操作纳入 Spring 事务」,就理解了整合后的事务管理。
五、原生使用的坑:默认不自动提交
原生使用 MyBatis 有个高频坑——openSession() 默认不自动提交:
openSession() 的两种模式:
openSession() → autoCommit=false(手动提交,默认)
openSession(true) → autoCommit=true(自动提交)
手动提交模式(默认)的坑:
session.getMapper(UserMapper.class).insert(user);
// 忘了 session.commit()!
session.close();
→ 结果:数据库里没有这条数据!
原因:手动提交模式下,操作只在事务里、没 commit 就没生效
close() 时如果没 commit,会 rollback(丢弃未提交的操作)
所以原生 MyBatis 做增删改,必须 commit:
try (SqlSession s = factory.openSession()) {
s.getMapper(...).insert(...);
s.commit(); // ★ 别忘了
}
自动提交模式(openSession(true)):
每条 SQL 自动提交,但不适合业务——
多个操作无法组成一个原子事务(一条成功一条失败无法一起回滚)
到了 Spring:@Transactional 帮你管,没这个坑
原生使用的高频坑:openSession() 默认不自动提交(手动提交模式)——做 insert 后忘了 session.commit(),会「代码跑了没报错、但数据库没数据」(未提交的操作在 close() 时被回滚)。所以原生 MyBatis 增删改必须 commit。openSession(true) 是自动提交但不适合业务(每条 SQL 独立提交、无法保证多操作原子性)。到 Spring 里 @Transactional 帮你管、没这个坑。理解「openSession() 默认手动提交、忘 commit 会数据没写入(close 时回滚)、增删改必须 commit、openSession(true)自动提交不适合业务、Spring 里 @Transactional 管」,就避开了原生使用的高频坑。
六、实践总结:几乎总是用 Spring 事务
总结:实际项目里事务怎么用:
实际项目的事务实践:
① 99% 的场景:用 Spring 的 @Transactional
- MyBatis 整合 Spring 后,事务交给 Spring
- 你不碰 MyBatis 的事务 API,只标 @Transactional
- Spring 统一管理(还支持传播行为、隔离级别、回滚规则)
② 什么时候涉及 MyBatis 自己的事务:
- 独立使用 MyBatis(不用 Spring,少见)
- 理解底层原理(面试、排查问题)
@Transactional 的能力(远超 MyBatis 自带的):
- 传播行为(REQUIRED/REQUIRES_NEW 等)
- 隔离级别
- 回滚规则(默认 RuntimeException 回滚)
- 声明式(注解,不用手写 commit/rollback)
- 和其他事务资源统一(不只 MyBatis)
所以结论:
实际开发用 @Transactional(Spring)
MyBatis 的 JDBC/MANAGED 是"底层知识"和"独立使用"时才涉及
别在 Spring 事务里手动 session.commit()(会破坏 Spring 的事务管理)
实践结论:99% 的场景用 Spring 的 @Transactional——MyBatis 整合 Spring 后事务交给 Spring,你只标 @Transactional(还支持传播行为、隔离级别、回滚规则、声明式,远超 MyBatis 自带)。只有独立使用 MyBatis 或理解底层时才涉及 MyBatis 自己的事务。别在 Spring 事务里手动 session.commit()(破坏 Spring 的事务管理)。理解「实际开发 99% 用 @Transactional(功能远超 MyBatis 自带:传播/隔离/回滚规则/声明式)、MyBatis 的 JDBC/MANAGED 是底层知识、别在 Spring 事务里手动 commit」,就掌握了事务的实践结论。
记忆钩子:「MyBatis 事务抽象:TransactionFactory+Transaction(getConnection/commit/rollback);两种内置:①JDBC(MyBatis 自己用 Connection 管事务,autoCommit=false,独立使用默认,一个 SqlSession=一个连接=一个事务)②MANAGED(MyBatis 不管,commit/rollback 空操作,交给外部容器 JTA,现在用得少);Spring 整合用 SpringManagedTransaction(跟随 Spring 事务:用 Spring 绑定的连接、自己不提交回滚、听 @Transactional);★原生坑:openSession()默认手动提交,忘 commit 数据没写入(close 时回滚),增删改必须 commit;实际 99%用 @Transactional(功能远超),别在 Spring 事务里手动 commit」。
七、常见误区与追问
- 误区:MyBatis 没有自己的事务管理。 有——MyBatis 有 TransactionFactory + Transaction 抽象,两种内置类型 JDBC(自己用 Connection 管)和 MANAGED(交给外部容器);只是整合 Spring 后让位给 Spring 的事务管理,所以平时感觉不到。
- 误区:原生 MyBatis 的 insert 执行了就会写入数据库。 openSession() 默认是手动提交模式——insert 后不 commit,操作只在事务里、没生效,close() 时还会回滚(数据没写入);原生做增删改必须 session.commit(),这是高频坑。
- 误区:openSession(true) 自动提交适合业务。 不适合——自动提交模式每条 SQL 独立提交,多个操作无法组成一个原子事务(一条成功一条失败无法一起回滚);业务事务要用手动提交(自己 commit)或 Spring 的 @Transactional。
- 误区:Spring 里也要调 session.commit()。 不要——Spring 整合后事务由 @Transactional/DataSourceTransactionManager 统一管理(SpringManagedTransaction 跟随 Spring 事务);在 Spring 事务里手动 session.commit() 会破坏 Spring 的事务管理、导致混乱。
- 追问:MyBatis 的 JDBC 和 MANAGED 事务类型有什么区别? JDBC:MyBatis 自己用底层 Connection 管理事务(设 autoCommit=false,commit/rollback 直接调 connection),独立使用时的默认;MANAGED:MyBatis 什么都不做(commit/rollback 空操作),把事务交给外部容器(JEE 应用服务器的 JTA)管理,避免和容器事务冲突。
- 追问:整合 Spring 后 MyBatis 的事务是怎么工作的? 用 SpringManagedTransaction——它从 Spring 的事务同步管理器拿「当前 Spring 事务绑定的连接」执行 SQL,自己不主动提交/回滚,事务的提交/回滚由 Spring 的事务管理器在 @Transactional 结束时统一控制;MyBatis 的操作因此天然纳入 Spring 事务。
- 追问:为什么实际项目几乎都用 @Transactional 而不用 MyBatis 自己的事务? 因为 @Transactional 功能强大得多——声明式(不用手写 commit/rollback)、支持传播行为(REQUIRED/REQUIRES_NEW)、隔离级别、回滚规则,还能统一管理多种事务资源;MyBatis 自带的只能手动 commit/rollback 单个 SqlSession,能力有限,只在独立使用或理解底层时才用。
八、加强记忆
MyBatis 自己有事务管理,由 TransactionFactory + Transaction(getConnection/commit/rollback/close)抽象,两种内置类型:① JDBC——MyBatis 自己用 Connection 管事务(autoCommit=false,commit/rollback 直接调 connection),是独立使用时的默认(一个 SqlSession = 一个 Transaction = 一个 Connection = 一个事务);② MANAGED——MyBatis 不管事务(commit/rollback 空操作),交给外部容器(JTA)管理,现在用得少。原生使用通过 SqlSession 手动控制事务:openSession() 默认手动提交,忘 commit() 会「数据没写入」(未提交操作在 close 时回滚),增删改必须 commit;openSession(true) 自动提交但不适合业务。和 Spring 整合后,MyBatis 事务让位给 Spring——用 SpringManagedTransaction(跟随 Spring 事务:用 Spring 事务绑定的连接、自己不提交回滚、全听 @Transactional),MyBatis 操作天然纳入 Spring 事务。实际项目 99% 用 Spring 的 @Transactional(功能远超 MyBatis 自带:传播行为、隔离级别、回滚规则、声明式),MyBatis 的 JDBC/MANAGED 只在独立使用或理解底层时涉及;别在 Spring 事务里手动 session.commit()(破坏 Spring 事务管理)。一句话「MyBatis 事务:JDBC(自己用 Connection 管,独立使用默认)/MANAGED(不管交给容器);原生 openSession()默认手动提交、忘 commit 数据没写入;整合 Spring 用 SpringManagedTransaction 跟随 @Transactional;实际 99%用 @Transactional,别在 Spring 事务里手动 commit」。