mybatis 事务深度解析:原理、配置与企业级实战
事务是保证数据一致性的核心机制,尤其在多步数据库操作场景中(如 “下单减库存”“转账转账”),事务的 acid 特性(原子性、一致性、隔离性、持久性)能避免出现数据脏读、幻读、部分成功等问题。mybatis 作为持久层框架,本身不直接管理事务,而是依赖底层数据源(如 jdbc)或整合 spring 框架实现事务控制。本文将从事务核心概念出发,深入拆解 mybatis 事务的实现原理、两种核心使用方式(原生 jdbc 事务、spring 声明式事务)、隔离级别配置及企业级实战场景,帮助开发者彻底掌握 mybatis 事务的正确用法。
一、核心基础:mybatis 事务的本质与依赖
1. 事务的核心价值
在无事务控制的场景中,多步数据库操作可能出现 “部分成功” 的风险。例如 “用户下单” 流程需执行两步操作:① 插入订单记录;② 扣减商品库存。若第一步成功、第二步失败,会导致 “订单已创建但库存未扣减” 的脏数据。事务的核心价值就是通过 acid 特性保证:多步操作 “要么全成功,要么全回滚”,确保数据一致性。
2. mybatis 事务的底层依赖
mybatis 本身不提供事务管理器,事务控制依赖以下两种底层机制,核心是通过sqlsession控制数据库连接的事务行为:
- jdbc 事务:依赖 jdbc 的
connection对象,通过setautocommit(false)关闭自动提交,commit()提交事务,rollback()回滚事务,适用于非 spring 环境; - spring 事务:spring 提供统一的事务管理器(
datasourcetransactionmanager),mybatis 通过sqlsessionfactory与 spring 事务管理器整合,支持声明式事务(@transactional注解),是企业级开发的主流方案。
3. mybatis 事务的核心组件
- sqlsession:mybatis 的核心会话对象,每个 sqlsession 绑定一个数据库连接(
connection),事务的提交 / 回滚本质是通过 sqlsession 控制 connection 的事务行为; - transactionfactory:事务工厂,负责创建
transaction对象,默认提供jdbctransactionfactory(适配 jdbc 事务)和managedtransactionfactory(适配容器管理事务); - transaction:事务接口,封装了事务的提交、回滚、关闭等操作,底层委托给 connection 实现;
- executor:mybatis 的执行器,所有数据库操作通过 executor 执行,executor 会通过 transaction 获取数据库连接,确保同一事务内的操作使用同一个 connection。
二、原生 jdbc 事务:mybatis 手动控制事务(非 spring 环境)
在不使用 spring 框架的场景中,mybatis 通过原生 jdbc 事务实现手动控制,核心是通过sqlsession的commit()和rollback()方法管理事务,底层依赖 jdbc 的 connection 对象。
1. 核心原理
- mybatis 默认开启 “自动提交事务”(
autocommit=true),即每个 sql 语句独立成为一个事务,执行后自动提交; - 手动控制事务时,需先通过
sqlsessionfactory.opensession(false)关闭自动提交,此时 sqlsession 绑定的 connection 处于 “事务未提交” 状态; - 多步 sql 操作执行完成后,调用
sqlsession.commit()提交事务;若执行过程中抛出异常,调用sqlsession.rollback()回滚事务; - 事务提交 / 回滚后,需关闭 sqlsession 释放连接。
2. 实战演示:原生 jdbc 事务手动控制
(1)环境准备(非 spring)
- 依赖配置(pom.xml):
<!-- mybatis核心依赖 -->
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis</artifactid>
<version>3.5.13</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupid>com.mysql</groupid>
<artifactid>mysql-connector-j</artifactid>
<version>8.0.33</version>
</dependency>
<!-- 数据源(c3p0) -->
<dependency>
<groupid>com.mchange</groupid>
<artifactid>c3p0</artifactid>
<version>0.9.5.5</version>
</dependency>- mybatis 配置文件(mybatis-config.xml):
<configuration>
<!-- 环境配置:配置事务工厂和数据源 -->
<environments default="development">
<environment id="development">
<!-- 事务工厂:使用jdbc事务工厂 -->
<transactionmanager type="jdbc"/>
<!-- 数据源:c3p0连接池 -->
<datasource type="pooled">
<property name="driver" value="com.mysql.cj.jdbc.driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test_mybatis?usessl=false&servertimezone=asia/shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="poolmaximumactiveconnections" value="10"/>
</datasource>
</environment>
</environments>
<!-- 映射器扫描 -->
<mappers>
<mapper resource="mapper/ordermapper.xml"/>
<mapper resource="mapper/productmapper.xml"/>
</mappers>
</configuration>(2)mapper 接口与 xml(下单减库存场景)
- ordermapper.java(插入订单):
public interface ordermapper {
int insertorder(order order);
}
- productmapper.java(扣减库存):
public interface productmapper {
int decreasestock(@param("productid") long productid, @param("num") integer num);
}
- ordermapper.xml:
<mapper namespace="com.example.mapper.ordermapper">
<insert id="insertorder" parametertype="com.example.entity.order">
insert into `order`(order_no, product_id, num, create_time)
values(#{orderno}, #{productid}, #{num}, now())
</insert>
</mapper>- productmapper.xml:
<mapper namespace="com.example.mapper.productmapper">
<update id="decreasestock">
update product set stock = stock - #{num} where id = #{productid} and stock >= #{num}
</update>
</mapper>(3)手动控制事务代码
public class transactiondemo {
public static void main(string[] args) {
// 1. 加载mybatis配置,创建sqlsessionfactory
string resource = "mybatis-config.xml";
try (inputstream inputstream = resources.getresourceasstream(resource)) {
sqlsessionfactory sqlsessionfactory = new sqlsessionfactorybuilder().build(inputstream);
// 2. 开启sqlsession,关闭自动提交(false表示手动控制事务)
sqlsession sqlsession = sqlsessionfactory.opensession(false);
try {
// 3. 获取mapper接口
ordermapper ordermapper = sqlsession.getmapper(ordermapper.class);
productmapper productmapper = sqlsession.getmapper(productmapper.class);
// 4. 模拟下单流程:① 插入订单;② 扣减库存
order order = new order();
order.setorderno(uuid.randomuuid().tostring());
order.setproductid(1l);
order.setnum(2);
ordermapper.insertorder(order); // 第一步:插入订单
int rows = productmapper.decreasestock(1l, 2); // 第二步:扣减库存
if (rows == 0) {
throw new runtimeexception("库存不足,扣减失败");
}
// 5. 所有操作成功,提交事务
sqlsession.commit();
system.out.println("事务提交成功:下单流程完成");
} catch (exception e) {
// 6. 发生异常,回滚事务
sqlsession.rollback();
system.out.println("事务回滚:" + e.getmessage());
} finally {
// 7. 关闭sqlsession,释放连接
sqlsession.close();
}
} catch (ioexception e) {
e.printstacktrace();
}
}
}3. 核心注意事项
- 必须通过
opensession(false)关闭自动提交,否则事务控制失效; - 同一事务内的所有操作必须使用同一个 sqlsession(确保使用同一个数据库连接);
- 异常捕获后必须手动调用
rollback(),否则事务会一直处于未提交状态,导致连接泄露; - 事务提交 / 回滚后,sqlsession 不可重复使用,需重新创建。
三、spring 声明式事务:mybatis 企业级主流方案
在 spring 框架中,mybatis 与 spring 事务管理器深度整合,支持声明式事务(通过@transactional注解),无需手动控制sqlsession的提交 / 回滚,简化事务代码,是企业级开发的首选方案。
1. 核心原理
- spring 提供
datasourcetransactionmanager作为事务管理器,该管理器通过 mybatis 的sqlsessionfactory获取数据库连接,统一管理事务; - 当方法添加
@transactional注解后,spring 会通过 aop 动态生成代理对象,在方法执行前开启事务(关闭 connection 自动提交),方法执行成功后提交事务,执行失败(抛出异常)后回滚事务; - spring 事务管理器与 mybatis 的
sqlsession无缝协同:同一事务内,spring 会确保所有 mybatis 操作使用同一个sqlsession(绑定同一个 connection),保证事务原子性。
2. 实战演示:spring 声明式事务整合 mybatis
(1)环境准备(spring boot + mybatis)
- 依赖配置(pom.xml):
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>2.7.10</version>
</parent>
<dependencies>
<!-- spring web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- mybatis + spring boot整合依赖 -->
<dependency>
<groupid>org.mybatis.spring.boot</groupid>
<artifactid>mybatis-spring-boot-starter</artifactid>
<version>2.3.1</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupid>com.mysql</groupid>
<artifactid>mysql-connector-j</artifactid>
<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
</dependencies>- 配置文件(application.yml):
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.driver
url: jdbc:mysql://localhost:3306/test_mybatis?usessl=false&servertimezone=asia/shanghai
username: root
password: 123456
# spring事务配置(可选,默认已配置datasourcetransactionmanager)
transaction:
rollback-on-commit-failure: true # 提交失败时回滚
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.stdoutimpl # 打印sql,便于调试(2)service 层添加 @transactional 注解
@service
public class orderservice {
@autowired
private ordermapper ordermapper;
@autowired
private productmapper productmapper;
/**
* 下单流程:插入订单 + 扣减库存
* @transactional:声明式事务,默认 runtimeexception 触发回滚
*/
@transactional(
rollbackfor = exception.class, // 所有异常都回滚(默认仅runtimeexception回滚)
propagation = propagation.required, // 事务传播行为:默认,当前无事务则创建新事务
isolation = isolation.default // 隔离级别:默认,使用数据库默认隔离级别
)
public void createorder(order order) {
// 1. 插入订单
ordermapper.insertorder(order);
// 2. 扣减库存
int rows = productmapper.decreasestock(order.getproductid(), order.getnum());
if (rows == 0) {
throw new runtimeexception("库存不足,下单失败");
}
// 3. 模拟其他业务操作(如记录日志)
system.out.println("下单成功,订单号:" + order.getorderno());
}
}(3)controller 层测试
@restcontroller
@requestmapping("/api/orders")
public class ordercontroller {
@autowired
private orderservice orderservice;
@postmapping
public responseentity<string> createorder(@requestbody order order) {
try {
order.setorderno(uuid.randomuuid().tostring());
orderservice.createorder(order);
return responseentity.ok("下单成功");
} catch (exception e) {
return responseentity.status(httpstatus.bad_request).body("下单失败:" + e.getmessage());
}
}
}3. @transactional 注解核心配置参数
@transactional注解提供丰富的配置参数,可根据业务需求调整事务行为,核心参数如下:
| 参数名 | 作用说明 | 可选值 |
|---|---|---|
rollbackfor | 指定触发回滚的异常类型(默认仅 runtimeexception 及其子类回滚) | 如exception.class(所有异常回滚)、sqlexception.class(特定异常回滚) |
norollbackfor | 指定不触发回滚的异常类型 | 如businessexception.class(业务异常不回滚) |
propagation | 事务传播行为(多事务方法嵌套时的行为) | required(默认)、supports、requires_new、nested 等 |
isolation | 事务隔离级别(解决脏读、不可重复读、幻读问题) | default(默认,数据库隔离级别)、read_uncommitted、read_committed 等 |
timeout | 事务超时时间(秒),超过时间未完成则自动回滚 | 如30(30 秒超时) |
readonly | 是否为只读事务(仅查询操作,设置为 true 可优化性能,不可执行增删改) | true/false(默认 false) |
关键参数详解:
- 事务传播行为:最常用
required(当前无事务则创建新事务,有事务则加入当前事务)和requires_new(无论当前是否有事务,都创建新事务); - 事务隔离级别:企业级常用
read_committed(避免脏读,大多数数据库默认级别),serializable(最高隔离级别,避免所有并发问题,但性能最低); - readonly:纯查询方法建议设置
readonly=true,spring 会优化事务配置(如关闭写操作权限),提升查询性能。
四、mybatis 事务常见问题与解决方案
1. 事务不生效问题(高频问题)
问题现象:
@transactional注解添加后,事务未生效(部分操作成功、部分失败时未回滚)。
常见原因与解决方案:
- 原因 1:方法非 public 修饰:spring aop 仅对 public 方法生成代理,非 public 方法的
@transactional注解无效;解决方案:确保事务方法为 public 修饰; - 原因 2:异常被 catch 捕获未抛出:spring 事务仅在方法抛出指定异常时才回滚,若异常被 try-catch 捕获且未重新抛出,事务不会回滚;解决方案:捕获异常后手动抛出(如
throw new runtimeexception(e)),或使用transactionaspectsupport.currenttransactionstatus().setrollbackonly()手动触发回滚; - 原因 3:数据源未被 spring 管理:mybatis 的 sqlsessionfactory 未使用 spring 的数据源,导致 spring 事务管理器无法控制连接;解决方案:确保数据源通过
spring.datasource配置,由 spring 自动注入; - 原因 4:内部方法调用:同一类中无事务方法调用有事务方法,aop 无法拦截,事务无效;解决方案:将事务方法抽取到其他 service 类,或通过
aopcontext.currentproxy()获取代理对象调用。
2. 事务并发问题(脏读、不可重复读、幻读)
问题说明:
多线程并发操作同一数据时,可能出现以下问题:
- 脏读:一个事务读取到另一个事务未提交的数据;
- 不可重复读:同一事务内多次查询同一数据,结果不一致;
- 幻读:同一事务内多次查询,结果集行数不一致。
解决方案:
- 调整事务隔离级别:如设置
isolation = isolation.read_committed(避免脏读)、isolation.repeatable_read(避免脏读和不可重复读); - 加锁:通过数据库锁(如行锁、表锁)或分布式锁(redis/zookeeper)控制并发访问;
- 避免长事务:长事务会占用数据库连接,增加并发冲突概率,尽量拆分长事务为短事务。
3. 事务提交失败问题
问题现象:
方法执行无异常,但事务提交失败(数据未入库)。
常见原因与解决方案:
- 原因 1:数据库引擎不支持事务:如 mysql 的 myisam 引擎不支持事务,innodb 引擎支持;解决方案:将数据库表引擎改为 innodb;
- 原因 2:事务超时:事务执行时间超过
timeout配置,被 spring 自动回滚;解决方案:优化 sql 执行效率,或适当增大timeout值; - 原因 3:连接池参数不合理:连接池最大连接数不足,导致事务无法获取连接提交;解决方案:调整连接池参数(如
spring.datasource.hikari.max-active)。
五、企业级实战:mybatis 事务最佳实践
1. 分层事务控制原则
- 事务应添加在 service 层:service 层负责业务逻辑整合,多步数据库操作的事务控制应在 service 层统一管理,避免在 controller 或 mapper 层添加事务;
- 粒度适中:事务粒度不宜过大(避免长事务),也不宜过小(避免事务碎片化),以 “一个完整业务场景” 为单位(如 “下单”“转账”)。
2. 高频场景事务配置示例
(1)纯查询方法(只读事务)
// 纯查询方法,设置readonly=true优化性能
@transactional(readonly = true, propagation = propagation.supports)
public list<order> getorderbyuserid(long userid) {
return ordermapper.selectbyuserid(userid);
}(2)转账业务(高一致性要求)
// 转账业务:扣减付款方余额 + 增加收款方余额,要求强一致性
@transactional(
rollbackfor = exception.class,
isolation = isolation.read_committed,
timeout = 30
)
public void transfer(long fromuserid, long touserid, bigdecimal amount) {
// 扣减付款方余额
useraccountmapper.decreasebalance(fromuserid, amount);
// 增加收款方余额
useraccountmapper.increasebalance(touserid, amount);
}(3)嵌套事务(requires_new)
// 主事务:创建订单
@transactional(rollbackfor = exception.class)
public void createorder(order order) {
ordermapper.insertorder(order);
// 调用子事务方法(创建订单日志,独立事务,即使失败不影响主事务)
logservice.recordorderlog(order.getid());
}
// 子事务:记录订单日志(独立事务)
@transactional(propagation = propagation.requires_new, rollbackfor = exception.class)
public void recordorderlog(long orderid) {
orderlog log = new orderlog();
log.setorderid(orderid);
log.setoperatetime(localdatetime.now());
orderlogmapper.insert(log);
}3. 事务监控与排查技巧
- 开启 spring 事务日志:在 application.yml 中配置
logging.level.org.springframework.transaction=debug,可查看事务开启、提交、回滚的详细日志; - 打印 sql 执行日志:mybatis 配置
log-impl: org.apache.ibatis.logging.stdout.stdoutimpl,查看 sql 执行顺序和参数; - 数据库事务日志:如 mysql 的 binlog 日志,可排查事务提交是否写入数据库。
六、总结
mybatis 事务的核心是 “通过 sqlsession 控制数据库连接的事务行为”,底层依赖 jdbc 事务机制,企业级开发中主要与 spring 声明式事务整合,通过@transactional注解简化事务控制。掌握 mybatis 事务的关键在于:
- 理解事务的 acid 特性,明确事务的适用场景(多步数据库操作需保证一致性);
- 区分原生 jdbc 事务和 spring 声明式事务的使用场景(非 spring 环境用原生,spring 环境用声明式);
- 熟练配置
@transactional注解的核心参数(尤其是rollbackfor、propagation、isolation); - 规避常见问题(如事务不生效、并发冲突、提交失败),遵循分层事务控制原则。
在实际开发中,应根据业务场景选择合适的事务策略:简单场景用 spring 默认配置,高一致性场景调整隔离级别和传播行为,并发场景结合锁机制,确保数据一致性和系统性能的平衡。
到此这篇关于mybatis事务原理与实战深入解析的文章就介绍到这了,更多相关mybatis事务原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论