MyBatis-Plus 本身不提供事务管理功能,而是完全依赖 Spring 的事务管理机制,通过 @Transactional 注解实现声明式事务控制。在实际开发中,结合 IService 的批量操作、逻辑删除、自动填充等特性,正确使用 @Transactional 是保证数据一致性的关键。


一、核心概念

1. 什么是事务?

事务(Transaction)是数据库操作的逻辑工作单元,具有 ACID 特性:

  • A(原子性):操作要么全部成功,要么全部失败回滚。
  • C(一致性):事务前后数据状态保持一致。
  • I(隔离性):并发事务之间互不干扰。
  • D(持久性):事务提交后数据永久保存。

2. MyBatis-Plus 与 Spring 事务的关系

  • MyBatis-Plus 基于 MyBatis 构建,而 MyBatis 的 SqlSession 由 Spring 管理。
  • @Transactional 是 Spring 提供的声明式事务注解,作用于方法或类
  • 当方法被 @Transactional 修饰时,Spring 会:
    • 开启数据库事务
    • 绑定 SqlSession 到当前线程(ThreadLocal
    • 方法执行完毕后提交事务(无异常)或回滚(抛出未捕获异常)

3. 事务传播行为(Propagation)

传播行为 说明
REQUIRED(默认) 有事务则加入,无则新建
REQUIRES_NEW 每次都新建事务,挂起当前事务
SUPPORTS 支持当前事务,无则非事务执行
NOT_SUPPORTED 不支持事务,总是非事务执行
NEVER 不支持事务,有事务则抛异常
MANDATORY 必须有事务,否则抛异常

二、操作步骤(非常详细)

步骤 1:确保项目集成 Spring 与 MyBatis-Plus

<!-- Spring Boot 项目 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

步骤 2:启用事务支持(Spring Boot 默认开启)

@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

✅ Spring Boot 默认启用 @EnableTransactionManagement,无需手动添加。


步骤 3:定义实体类与 Mapper

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private Integer status;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {}

步骤 4:创建 Service 层并使用 @Transactional

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private OrderService orderService; // 假设有订单服务

示例 1:基础事务方法(用户注册)

/**
 * 用户注册:插入用户 + 初始化订单
 * 两个操作必须同时成功或失败
 */
@Transactional  // 开启事务
public void registerUser(String name, Integer age) {
    // 1. 插入用户
    User user = new User();
    user.setName(name);
    user.setAge(age);
    user.setStatus(1);
    userMapper.insert(user); // 使用 MyBatis-Plus 方法

    // 2. 初始化默认订单(调用其他 Service)
    orderService.createDefaultOrder(user.getId());

    // 如果 createDefaultOrder 抛异常,整个事务回滚
}

示例 2:事务传播行为 REQUIRES_NEW(日志记录)

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService; // 日志服务,独立事务
@Transactional
public void updateUserWithLog(Long id, String name) {
    // 主事务:更新用户
    User user = userMapper.selectById(id);
    user.setName(name);
    userMapper.updateById(user);

    try {
        // 独立事务:记录操作日志,即使失败也不影响主事务
        logService.saveOperationLog("update_user", id, "name changed");
    } catch (Exception e) {
        // 忽略日志异常
        log.warn("Failed to save log", e);
    }
}
@Service
public class LogService {

    @Autowired
    private LogMapper logMapper;

    /**
     * 日志记录使用独立事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveOperationLog(String op, Long targetId, String desc) {
        Log log = new Log();
        log.setOp(op);
        log.setTargetId(targetId);
        log.setDesc(desc);
        logMapper.insert(log);
    }
}

示例 3:批量操作事务(MyBatis-Plus 自动支持)

@Transactional
public void batchImportUsers(List<User> users) {
    // MyBatis-Plus saveBatch 默认在事务中执行
    boolean result = userService.saveBatch(users, 100);
    if (!result) {
        throw new RuntimeException("批量导入失败");
    }
}

saveBatch 内部使用 SqlSessionTemplate,天然支持 Spring 事务。


步骤 5:在 Controller 中调用

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/register")
    public R<String> register(@RequestBody RegisterDTO dto) {
        userService.registerUser(dto.getName(), dto.getAge());
        return R.ok("注册成功");
    }
}

三、常见错误与解决方案

错误现象 原因 解决方案
事务不生效 方法为 privatefinal 改为 public
事务不回滚 捕获了异常但未抛出 throw 异常或 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
同一类中方法调用失效 自调用绕过代理 使用 AopContext.currentProxy() 或拆分到不同类
REQUIRES_NEW 不生效 异常被捕获 确保内层方法抛出异常
事务超时 长时间操作 设置 @Transactional(timeout = 30)
死锁 并发更新顺序不一致 统一资源访问顺序

四、注意事项

  1. @Transactional 必须作用于 public 方法
    Spring AOP 代理无法拦截非 public 方法。

  2. 异常必须抛出到事务方法外部
    try-catch 捕获且不抛出,事务不会回滚。

  3. 避免在事务方法中做耗时操作(如 HTTP 调用)
    可能导致事务超时、连接占用。

  4. this.method() 调用不触发事务
    因为绕过了代理对象,应拆分到不同 Service 类。

  5. 读操作可使用 @Transactional(readOnly = true)
    提示数据库优化,如使用只读事务。

  6. 事务方法应尽量短小
    减少锁持有时间,提升并发性能。


五、使用技巧

技巧 1:手动控制回滚

@Transactional
public void manualRollback() {
    try {
        userMapper.insert(user);
        if (someError()) {
            // 手动标记回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("业务规则校验失败,事务将回滚");
            return;
        }
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw e;
    }
}

技巧 2:只读事务优化查询

@Transactional(readOnly = true)
public List<User> getUsers() {
    return userMapper.selectList(null);
}

技巧 3:设置事务超时

@Transactional(timeout = 15) // 15 秒超时
public void processOrder() {
    // 防止长时间阻塞
}

技巧 4:指定回滚异常类型

@Transactional(rollbackFor = BusinessException.class)
public void businessMethod() {
    // 遇到 BusinessException 也回滚(默认只对 RuntimeException 回滚)
}

六、最佳实践

实践 说明
✅ 事务控制在 Service 层 Controller 不应加 @Transactional
✅ 方法粒度要合理 避免大事务,拆分逻辑
✅ 优先使用 REQUIRES_NEW 处理日志、审计 保证主流程不受影响
✅ 避免循环内数据库操作 改为批量操作
✅ 使用 readOnly = true 标记查询方法 性能与语义更清晰
✅ 异常统一处理 结合 @ControllerAdvice 统一回滚策略

七、性能优化

优化点 说明
减少事务范围 只包裹必要的数据库操作
避免长事务 设置 timeout,避免阻塞
批量操作 使用 saveBatchupdateBatchById 减少交互次数
连接池配置 合理设置 maxPoolSizeconnectionTimeout
索引优化 减少行锁持有时间
读写分离 查询走从库,减轻主库压力(需配合中间件)

八、总结

项目 内容
核心机制 Spring @Transactional + MyBatis SqlSession 绑定
作用位置 Service 层 public 方法
默认传播 REQUIRED
回滚条件 抛出未捕获的 RuntimeExceptionError
批量支持 saveBatch 等方法天然支持事务
关键原则 短事务、少锁、快提交

一句话总结
MyBatis-Plus 的事务管理完全依赖 Spring 的 @Transactional,通过合理使用传播行为、异常控制、批量操作,可在保证数据一致性的同时提升系统性能。