java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis事务原理

MyBatis事务原理与实战深入解析

作者:故渊ZY

本文详细解析了MyBatis事务的原理、配置与企业级实战应用,从核心基础到原生JDBC事务和Spring声明式事务的实现,再到常见问题与解决方案,全面涵盖了MyBatis事务的各个方面,帮助开发者深入理解并正确使用MyBatis事务,感兴趣的朋友跟随小编一起看看吧

MyBatis 事务深度解析:原理、配置与企业级实战

事务是保证数据一致性的核心机制,尤其在多步数据库操作场景中(如 “下单减库存”“转账转账”),事务的 ACID 特性(原子性、一致性、隔离性、持久性)能避免出现数据脏读、幻读、部分成功等问题。MyBatis 作为持久层框架,本身不直接管理事务,而是依赖底层数据源(如 JDBC)或整合 Spring 框架实现事务控制。本文将从事务核心概念出发,深入拆解 MyBatis 事务的实现原理、两种核心使用方式(原生 JDBC 事务、Spring 声明式事务)、隔离级别配置及企业级实战场景,帮助开发者彻底掌握 MyBatis 事务的正确用法。

一、核心基础:MyBatis 事务的本质与依赖

1. 事务的核心价值

在无事务控制的场景中,多步数据库操作可能出现 “部分成功” 的风险。例如 “用户下单” 流程需执行两步操作:① 插入订单记录;② 扣减商品库存。若第一步成功、第二步失败,会导致 “订单已创建但库存未扣减” 的脏数据。事务的核心价值就是通过 ACID 特性保证:多步操作 “要么全成功,要么全回滚”,确保数据一致性。

2. MyBatis 事务的底层依赖

MyBatis 本身不提供事务管理器,事务控制依赖以下两种底层机制,核心是通过SqlSession控制数据库连接的事务行为:

3. MyBatis 事务的核心组件

二、原生 JDBC 事务:MyBatis 手动控制事务(非 Spring 环境)

在不使用 Spring 框架的场景中,MyBatis 通过原生 JDBC 事务实现手动控制,核心是通过SqlSessioncommit()rollback()方法管理事务,底层依赖 JDBC 的 Connection 对象。

1. 核心原理

2. 实战演示:原生 JDBC 事务手动控制

(1)环境准备(非 Spring)
<!-- 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>
<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(下单减库存场景)
public interface OrderMapper {
    int insertOrder(Order order);
}
public interface ProductMapper {
    int decreaseStock(@Param("productId") Long productId, @Param("num") Integer num);
}
<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>
<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. 核心注意事项

三、Spring 声明式事务:MyBatis 企业级主流方案

在 Spring 框架中,MyBatis 与 Spring 事务管理器深度整合,支持声明式事务(通过@Transactional注解),无需手动控制SqlSession的提交 / 回滚,简化事务代码,是企业级开发的首选方案。

1. 核心原理

2. 实战演示:Spring 声明式事务整合 MyBatis

(1)环境准备(Spring Boot + MyBatis)
<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>
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)
关键参数详解:

四、MyBatis 事务常见问题与解决方案

1. 事务不生效问题(高频问题)

问题现象:

@Transactional注解添加后,事务未生效(部分操作成功、部分失败时未回滚)。

常见原因与解决方案:

2. 事务并发问题(脏读、不可重复读、幻读)

问题说明:

多线程并发操作同一数据时,可能出现以下问题:

解决方案:

3. 事务提交失败问题

问题现象:

方法执行无异常,但事务提交失败(数据未入库)。

常见原因与解决方案:

五、企业级实战:MyBatis 事务最佳实践

1. 分层事务控制原则

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. 事务监控与排查技巧

六、总结

MyBatis 事务的核心是 “通过 SqlSession 控制数据库连接的事务行为”,底层依赖 JDBC 事务机制,企业级开发中主要与 Spring 声明式事务整合,通过@Transactional注解简化事务控制。掌握 MyBatis 事务的关键在于:

  1. 理解事务的 ACID 特性,明确事务的适用场景(多步数据库操作需保证一致性);
  2. 区分原生 JDBC 事务和 Spring 声明式事务的使用场景(非 Spring 环境用原生,Spring 环境用声明式);
  3. 熟练配置@Transactional注解的核心参数(尤其是rollbackForpropagationisolation);
  4. 规避常见问题(如事务不生效、并发冲突、提交失败),遵循分层事务控制原则。

在实际开发中,应根据业务场景选择合适的事务策略:简单场景用 Spring 默认配置,高一致性场景调整隔离级别和传播行为,并发场景结合锁机制,确保数据一致性和系统性能的平衡。

到此这篇关于MyBatis事务原理与实战深入解析的文章就介绍到这了,更多相关MyBatis事务原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文