1 引言
在企业级应用开发中,事务管理是保证数据一致性与完整性的核心机制。无论是银行转账、订单支付,还是库存扣减,这些操作都需要保证要么全部成功,要么全部失败,否则就会产生数据不一致的严重问题。
在 java 生态中,mybatis 作为一款轻量级 orm 框架,因其灵活的 sql 定制能力和较低的学习成本被广泛应用。然而,mybatis 本身并不是一个“全栈”持久层框架,它并不直接管理数据源的生命周期,而是通过事务管理机制协调 jdbc 连接的提交与回滚。
理解 mybatis 的事务管理机制不仅有助于我们编写正确、健壮的代码,还能帮助我们在与 spring 等框架整合时,精准定位事务相关问题(如事务未生效、连接泄漏、跨数据源事务失败等)。
本系列文章的目标是从概念、实现、源码、整合、优化五个维度,深入剖析 mybatis 的事务管理机制,并给出实战中的最佳实践。
1.1 为什么事务管理如此重要
数据库事务(database transaction)是保证acid 特性(原子性 atomicity、一致性 consistency、隔离性 isolation、持久性 durability)的基础。在分布式和高并发系统中,事务问题会被放大:
- 原子性失效:转账扣款成功但入账失败,导致资金丢失。
- 隔离性不足:并发读写导致脏读、不可重复读、幻读。
- 持久性丢失:系统崩溃后事务未持久化的数据丢失。
mybatis 的事务管理器相当于一个交通指挥员,它决定何时让 sql 执行、何时提交或回滚,保证数据的一致性与安全性。
1.2 本文的核心关注点
本博客将重点分析以下几个方面:
- mybatis 核心事务接口与实现类:transaction、transactionfactory、jdbctransaction、managedtransaction 等。
- 两种事务管理机制:jdbc 事务与 managed 事务的差异与适用场景。
- 与 spring 的整合:@transactional 注解、transactionsynchronizationmanager 的作用。
- 源码解析:通过调用链路、类图和流程图揭示 mybatis 事务的底层实现逻辑。
- 常见问题与解决方案:事务未生效、连接泄漏、多数据源事务。
- 性能优化与最佳实践:事务粒度控制、连接复用、隔离级别调整。
2 核心概念
在深入源码分析之前,我们需要先厘清 mybatis 事务管理的几个核心概念。这不仅包括数据库层面的事务定义,还涉及 mybatis 在架构上对事务的抽象与实现。
2.1 事务的定义与特性
事务(transaction)是数据库操作的一个逻辑单元,由一组 sql 语句组成。这些语句要么全部成功提交,要么全部回滚撤销,确保数据的一致性。事务必须满足 acid 四大特性:
- 原子性(atomicity):事务中的所有操作要么全部成功,要么全部失败回滚。
- 一致性(consistency):事务执行前后,数据必须处于一致状态。
- 隔离性(isolation):并发事务之间相互隔离,互不干扰。
- 持久性(durability):事务提交后,对数据的修改是永久性的。
2.2 mybatis 中的事务抽象
mybatis 将事务管理功能抽象为一个核心接口和若干实现类,主要包括:
- transaction 接口:定义了事务的基本操作方法,如
commit()、rollback()、close()、getconnection()等。 - transactionfactory 接口:用于创建具体的 transaction 实例,支持通过不同配置生成不同类型的事务管理器。
- jdbctransaction:基于 jdbc 原生 api 实现的事务管理器,直接调用
java.sql.connection的commit()和rollback()方法。 - managedtransaction:受外部容器(如 jee 容器、spring)管理的事务管理器,不直接控制事务提交与回滚。
public interface transaction {
connection getconnection() throws sqlexception;
void commit() throws sqlexception;
void rollback() throws sqlexception;
void close() throws sqlexception;
integer gettimeout() throws sqlexception;
}上面的 transaction 接口就是 mybatis 的事务核心抽象,所有事务管理器都必须实现该接口,以统一事务操作方式。
2.3 mybatis 中的事务边界
在 mybatis 中,事务边界主要由 sqlsession 控制:
- 开启事务:获取
sqlsession并连接数据库。 - 提交事务:调用
sqlsession.commit()。 - 回滚事务:调用
sqlsession.rollback()。 - 关闭事务:调用
sqlsession.close(),释放连接。
如果与 spring 整合,事务边界的控制权则由 spring 的 platformtransactionmanager 及其实现类(如 datasourcetransactionmanager)接管。
2.4 概念小结
从架构设计来看,mybatis 的事务管理分为三个层次:
- 接口层:定义统一的事务操作标准(
transaction、transactionfactory)。 - 实现层:提供不同类型事务的具体实现(
jdbctransaction、managedtransaction)。 - 调用层:由
sqlsession或 spring 框架驱动事务的开启、提交、回滚与关闭。
3 mybatis 事务管理机制
mybatis 提供了两种核心事务管理机制:jdbc 事务 与 managed 事务。它们的区别主要在于事务控制权的归属以及事务生命周期的管理方式。
3.1 jdbc 事务
jdbc 事务 是 mybatis 默认的事务管理方式,由 mybatis 直接通过 jdbc api 控制事务的提交与回滚。
- 实现类:
jdbctransaction - 控制权:mybatis 内部
- 适用场景:独立使用 mybatis 或不依赖外部容器时。
工作机制:
- mybatis 获取连接后,会将
autocommit设置为false。 - 执行 sql 后,由调用方显式触发
commit()或rollback()。 - 事务结束后释放连接回连接池。
示例代码:
transaction tx = new jdbctransaction(datasource, level, false);
try (connection conn = tx.getconnection()) {
// 执行 sql
tx.commit();
} catch (sqlexception e) {
tx.rollback();
} finally {
tx.close();
}优点:简单直接。 缺点:无法与外部事务资源(如 jta)协作。
3.2 managed 事务
managed 事务 不直接控制事务提交与回滚,交由外部容器(如 spring、jee 容器)管理。
- 实现类:
managedtransaction - 控制权:外部容器
- 适用场景:spring 管理事务、jee 容器环境。
工作机制:
- mybatis 从外部容器获取连接。
- 不在 mybatis 内部执行
commit()和rollback()。 - 可通过
closeconnection配置决定是否由 mybatis 关闭连接。
配置示例:
<transactionmanager type="managed">
<property name="closeconnection" value="false"/>
</transactionmanager>3.3 配置对比
<!-- jdbc 事务配置 -->
<transactionmanager type="jdbc"/>
<!-- managed 事务配置 -->
<transactionmanager type="managed">
<property name="closeconnection" value="false"/>
</transactionmanager>3.4 差异总结
| 对比项 | jdbc 事务 | managed 事务 |
|---|---|---|
| 控制权 | mybatis 内部 | 外部容器 |
| 提交/回滚 | mybatis 调用 commit()/rollback() | 外部容器负责 |
| 连接关闭 | mybatis 内部 | 可由外部容器 |
| 场景 | 独立 mybatis 项目 | spring、jee 容器环境 |
4 mybatis 与 spring 的事务整合
在实际开发中,mybatis 很少单独使用,更多是在 spring 或 spring boot 框架中与其他数据访问技术(如 jpa、jdbc template)共存。为了在多种数据访问方式间实现统一的事务控制,spring 接管了 mybatis 的事务管理。
4.1 整合的核心思想
mybatis 原本通过 transaction 接口及其实现类(如 jdbctransaction、managedtransaction)来管理事务。在与 spring 整合后,事务的开启、提交、回滚等生命周期由 spring 的 platformtransactionmanager(通常是 datasourcetransactionmanager)全权负责。
4.2 核心组件
- sqlsessionfactorybean:spring 提供的工厂类,用于生成
sqlsessionfactory,并配置数据源、事务工厂等。 - springmanagedtransactionfactory:spring 对 mybatis 事务工厂的实现,用于生成
springmanagedtransaction,它会委托 spring 管理事务。 - transactionsynchronizationmanager:spring 的事务同步管理器,用于将数据库连接绑定到当前线程,实现事务上下文的共享。
4.3 事务接管流程
- 当业务方法被
@transactional标记时,spring 的事务拦截器(transactioninterceptor)会启动事务(通过platformtransactionmanager)。 - 事务启动时,
datasourcetransactionmanager会从连接池获取连接,并绑定到transactionsynchronizationmanager。 - 当 mybatis 执行 sql 时,通过
springmanagedtransaction从transactionsynchronizationmanager获取当前线程绑定的连接,而不是自己创建连接。 - 提交或回滚事务的时机由 spring 控制,在方法执行结束时统一处理。
流程图示意:
@transactional 方法调用
↓
transactioninterceptor 拦截
↓
platformtransactionmanager 启动事务
↓
绑定连接到 transactionsynchronizationmanager
↓
mybatis 执行 sql(springmanagedtransaction 获取连接)
↓
方法结束,统一提交或回滚事务
4.4 配置示例(spring boot)
@configuration
@mapperscan("com.example.mapper")
public class mybatisconfig {
@bean
public sqlsessionfactory sqlsessionfactory(datasource datasource) throws exception {
sqlsessionfactorybean factorybean = new sqlsessionfactorybean();
factorybean.setdatasource(datasource);
factorybean.settransactionfactory(new springmanagedtransactionfactory());
return factorybean.getobject();
}
}4.5 关键点总结
- spring 整合 mybatis 时,
transactionfactory必须设置为springmanagedtransactionfactory。 - 所有事务边界(开始、提交、回滚)都由 spring 控制。
- mybatis 在 spring 环境下不会主动关闭连接,而是由 spring 在事务结束时统一管理。
5 mybatis 事务管理源码解析
本节从源码角度系统拆解 mybatis 事务管理的关键接口与实现,并补充与 spring 整合时的调用链路。示例以 mybatis 3.x 与 mybatis-spring 2.x 为蓝本,代码片段做了少量删减与注释以便理解。
5.1 总览:类关系与模块边界
从宏观上看,事务相关模块可以分为四层:
- 接口抽象层:
transaction、transactionfactory - mybatis 内部实现层:
jdbctransaction、managedtransaction、对应的jdbctransactionfactory、managedtransactionfactory - 执行器交互层:
executor(baseexecutor、simpleexecutor等)在执行前后调用commit/rollback/close - spring 整合层:
springmanagedtransaction、sqlsessiontemplate、sqlsessionutils、datasourcetransactionmanager、transactionsynchronizationmanager
类图(简化版):
classdiagram
interface transaction {
+getconnection() connection
+commit()
+rollback()
+close()
+gettimeout() integer
}
class transactionfactory {
<<interface>>
+newtransaction(connection)
+newtransaction(datasource, level, autocommit)
}
class jdbctransaction
class managedtransaction
class jdbctransactionfactory
class managedtransactionfactory
class springmanagedtransaction
transaction <|.. jdbctransaction
transaction <|.. managedtransaction
transaction <|.. springmanagedtransaction
transactionfactory <|.. jdbctransactionfactory
transactionfactory <|.. managedtransactionfactory5.2transaction接口逐行解读
public interface transaction {
connection getconnection() throws sqlexception; // 懒加载或直接返回当前连接
void commit() throws sqlexception; // 提交当前连接的事务
void rollback() throws sqlexception; // 回滚当前连接的事务
void close() throws sqlexception; // 归还连接或实际关闭
integer gettimeout() throws sqlexception; // 可选的事务超时
}设计要点:
- 连接懒加载:mybatis 常在首次需要时创建/获取连接,减少无谓占用。
- 统一生命周期:不关心连接来自何处(直连或容器),一律通过
transaction抽象进行提交/回滚/关闭。
5.3jdbctransaction源码要点
jdbctransaction 直接基于 datasource 与 connection 控制事务:
public class jdbctransaction implements transaction {
private final datasource datasource;
private connection connection;
private final transactionisolationlevel level;
private final boolean autocommit;
@override
public connection getconnection() throws sqlexception {
if (connection == null) {
connection = datasource.getconnection();
if (level != null) connection.settransactionisolation(level.getlevel());
setdesiredautocommit(autocommit);
}
return connection;
}
private void setdesiredautocommit(boolean desired) throws sqlexception {
if (connection.getautocommit() != desired) {
connection.setautocommit(desired); // jdbc 事务关键:autocommit=false 以启用显式提交
}
}
@override
public void commit() throws sqlexception {
if (connection != null && !connection.getautocommit()) connection.commit();
}
@override
public void rollback() throws sqlexception {
if (connection != null && !connection.getautocommit()) connection.rollback();
}
@override
public void close() throws sqlexception {
if (connection != null) {
resetautocommit(); // 归还连接前恢复状态(对连接池很重要)
connection.close();
}
}
}关键点:
- 隔离级别设置:仅在首次获取连接时设置,避免重复调用带来的开销。
- autocommit 管控:通过
autocommit=false启用显式事务边界。 - 资源归还:
close()前恢复autocommit,防止影响连接池中后续租户。
示例:独立 mybatis(非 spring)使用
sqlsessionfactory factory = ...;
try (sqlsession session = factory.opensession(false)) { // 关闭自动提交
usermapper mapper = session.getmapper(usermapper.class);
mapper.insert(u1);
mapper.insert(u2);
session.commit();
} catch (exception ex) {
session.rollback();
}5.4managedtransaction源码要点
managedtransaction 交由外部容器管理提交与回滚:
public class managedtransaction implements transaction {
private final datasource datasource;
private connection connection;
private final boolean closeconnection; // 是否在 close() 时关闭连接
@override
public connection getconnection() throws sqlexception {
if (connection == null) {
connection = datasource.getconnection(); // 来自容器/代理
}
return connection;
}
@override public void commit() {} // 空实现,由外部容器负责
@override public void rollback() {}
@override public void close() throws sqlexception {
if (closeconnection && connection != null) connection.close();
}
}关键点:
- 不触碰事务边界:
commit/rollback空实现,避免与容器冲突。 - 连接关闭策略:通过
closeconnection决定是否交回容器或由容器统一回收。
5.5transactionfactory与具体工厂
transactionfactory 把选择权在配置期就确定下来:
public interface transactionfactory {
default void setproperties(properties props) {}
transaction newtransaction(connection conn);
transaction newtransaction(datasource ds, transactionisolationlevel level, boolean autocommit);
}两个常用实现:
jdbctransactionfactory:创建jdbctransactionmanagedtransactionfactory:创建managedtransaction
xml 配置示例:
<environments default="dev">
<environment id="dev">
<transactionmanager type="jdbc"/>
<datasource type="pooled">...</datasource>
</environment>
</environments>5.6 执行器如何驱动事务:baseexecutor
mybatis 的 executor 负责发起 sql 执行,并在恰当时机触发事务提交/回滚/关闭:
public abstract class baseexecutor implements executor {
protected final transaction transaction;
@override
public void commit(boolean required) throws sqlexception {
if (closed) throw new executorexception("closed");
clearlocalcache();
if (required) {
transaction.commit(); // 委托给 transaction 实现
}
}
@override
public void rollback(boolean required) throws sqlexception {
if (!closed) {
clearlocalcache();
if (required) {
transaction.rollback();
}
}
}
@override
public void close(boolean forcerollback) {
try {
try {
if (forcerollback) rollback(true);
} finally {
transaction.close();
}
} catch (sqlexception e) { ... }
}
}要点:
- 职责单一:执行器并不直接操作
connection,一切通过transaction完成,保证可替换性。 - 缓存清理:提交/回滚前清空本地缓存,确保一致性。
5.7 与 spring 整合的源码链路
当引入 mybatis-spring 后,调用链整合如下:
@transactional→ aop 代理 →transactioninterceptortransactioninterceptor使用platformtransactionmanager(常见:datasourcetransactionmanager)开始事务datasourcetransactionmanager获取连接并绑定到transactionsynchronizationmanager(线程上下文)- mybatis 侧:
sqlsessiontemplate→sqlsessionutils.getsqlsession→ 检索/创建sqlsession springmanagedtransaction调用datasourceutils.getconnection(datasource)→ 从transactionsynchronizationmanager取出线程绑定连接- 方法结束时由 spring 统一提交/回滚,最后释放/归还连接
关键类片段:
// mybatis-spring: springmanagedtransaction#getconnection
public connection getconnection() throws sqlexception {
if (this.connection == null) {
this.connection = datasourceutils.getconnection(this.datasource);
}
return this.connection;
}
// spring: datasourceutils.getconnection
public static connection getconnection(datasource ds) {
connectionholder holder = (connectionholder)
transactionsynchronizationmanager.getresource(ds);
if (holder != null && holder.hasconnection()) {
return holder.getconnection();
}
connection con = ds.getconnection();
// 如果处于事务中,包装为 connectionholder 并绑定到线程
// ...
return con;
}sqlsession 获取与释放(sqlsessionutils):
public static sqlsession getsqlsession(sqlsessionfactory factory, executortype type, persistenceexceptiontranslator translator) {
sqlsessionholder holder = (sqlsessionholder) transactionsynchronizationmanager.getresource(factory);
if (holder != null) {
return holder.getsqlsession(); // 复用当前事务内的 sqlsession
}
sqlsession session = factory.opensession(type);
// 如果存在事务,则注册同步器,在事务完成时关闭 sqlsession
// transactionsynchronizationmanager.registersynchronization(...)
return session;
}5.8 时序图:@transactional 方法的一次完整调用
sequencediagram participant c as client participant s as @transactional service participant ti as transactioninterceptor participant tm as datasourcetransactionmanager participant tsm as transactionsynchronizationmanager participant sut as sqlsessiontemplate participant smt as springmanagedtransaction participant ds as datasource/pool participant db as database c->>s: 调用业务方法 s->>ti: aop 拦截 ti->>tm: begin() 开启事务 tm->>ds: getconnection() ds-->>tm: connection tm->>tsm: 绑定 connectionholder ti->>s: 继续执行业务逻辑 s->>sut: 执行 mapper 方法 sut->>smt: getconnection() smt->>tsm: 查询线程绑定 connection tsm-->>smt: 返回同一 connection smt->>db: 执行 sql sut-->>s: 返回结果 s->>ti: 方法结束 ti->>tm: commit()/rollback() tm->>tsm: 清理并释放连接 tm->>ds: 归还连接到连接池
5.9 边界条件与细节
- 多数据源:spring 以
datasource为 key 进行资源绑定,若同一事务中使用多个数据源,会分别绑定多个connectionholder。务必确保 mapper 使用正确的数据源。 - 超时控制:
transaction.gettimeout()可与 spring 超时策略协同(如@transactional(timeout=...))。 - 只读事务:spring 在只读场景可能降低隔离或优化执行计划,但数据库是否真正利用只读标志取决于驱动/方言。
sqlsession复用:在事务内,同一sqlsessionfactory对应单个sqlsession,跨线程不共享。
5.10 调试与定位建议
打开日志:
# mybatis 执行与 jdbc 交互 logging.level.org.apache.ibatis=debug logging.level.jdbc.sqlonly=debug # spring 事务与同步 logging.level.org.springframework.jdbc.datasource=debug logging.level.org.springframework.transaction=debug logging.level.org.mybatis.spring=debug
打印连接标识:通过拦截器或日志模式打印 connection#hashcode(),确认同一事务内是否为同一连接。
异常边界:务必在 @transactional 入口层抛出(或显式标注)需要回滚的异常类型,否则可能出现“方法抛异常但未回滚”的错觉。
6 mybatis 事务管理常见问题与解决方案
在实际项目中,mybatis 的事务管理并非总是“开箱即用”,很多问题往往与配置、整合方式、事务传播等相关。下面我们列出常见问题、成因以及对应解决方案。
6.1 事务未生效
问题现象:@transactional 注解标记的方法中执行多条 sql,但数据库最终只部分提交,或回滚未生效。
常见原因:
- 事务未被 spring 管理:mybatis 直接使用
sqlsessionfactory.opensession()获取会话,绕过了 spring 事务管理器。 - 方法调用未经过代理:同类内部方法调用不会触发 spring aop 拦截,事务逻辑失效。
- 事务传播行为不匹配:如外层事务为
requires_new,内层事务的回滚不影响外层事务。
解决方案:
- 确保通过 mapper 接口由 spring 管理的 bean 调用。
- 检查
@transactional注解位置,确保是public方法且通过代理调用。 - 根据业务需求正确配置
propagation属性。
6.2 数据库连接泄漏
问题现象:系统长时间运行后,连接池中的连接耗尽,出现 java.sql.sqltransientconnectionexception: hikaripool-1 - connection is not available。
常见原因:
- 事务或会话未正常关闭,连接未释放回连接池。
- managed 事务模式下
closeconnection配置错误。
解决方案:
- 确保
sqlsession使用try-with-resources或在finally块中关闭。 - 使用 spring 时推荐
springmanagedtransaction以自动管理连接释放。
6.3 多数据源事务问题
问题现象:跨多个数据源的操作无法在同一事务中提交或回滚。
常见原因:
- spring 默认的
datasourcetransactionmanager只支持单数据源。 - 不同数据源的连接未统一在同一事务上下文中。
解决方案:
- 使用
jtatransactionmanager或第三方分布式事务框架(如 seata、atomikos)。 - 对于强一致性要求不高的场景,可拆分为独立事务处理。
6.4 嵌套事务与传播行为异常
问题现象:方法调用链中,内层事务的回滚影响了外层事务,或者预期的独立事务没有生效。
常见原因:
spring 事务传播属性未按需求配置,如使用
required导致内外事务合并。
解决方案:
使用
requires_new保证内层事务独立执行。使用
nested在支持 savepoint 的数据库中实现部分回滚。
6.5 mybatis 缓存与事务提交
问题现象:事务提交后,查询结果仍是旧数据。
常见原因:
mybatis 一级缓存、二级缓存未在事务提交时刷新。
解决方案:
在事务提交后调用
sqlsession.clearcache()刷新缓存。合理配置
flushcache属性确保更新语句刷新缓存。
到此这篇关于mybatis事务管理模块详解的文章就介绍到这了,更多相关mybatis事务管理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论