乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

transactionManager

导学

本节学习目标:

  • 理解 transactionManager 在 MyBatis 事务体系中的核心地位
  • 掌握 JDBC 和 MANAGED 两种内置事务管理类型的行为差异与适用场景
  • 能够在不同部署环境(独立应用 vs 容器)中选择正确的事务管理策略
  • 了解 MyBatis 事务管理的源码实现与生命周期

定义

数据库操作离不开事务控制:插入员工记录时,如果同时需要插入员工档案和初始化薪资记录,这三项操作必须要么全部成功,要么全部回滚。MyBatis 作为持久层框架,必须提供事务管理能力。

transactionManager 元素的作用就是指定 MyBatis 如何获取和管理数据库事务。它决定了 SqlSession.commit() 和 SqlSession.rollback() 时,真正执行事务控制的是 MyBatis 自身,还是交由外部容器(如 Spring、Java EE 容器)。


适用位置与核心属性

transactionManager 位于 mybatis-config.xml 中每个 environment 内部,且必须是 environment 的第一个子元素。

<environment id="development">
  <transactionManager type="JDBC"/>
  <dataSource type="POOLED">
    <!-- 数据源配置 -->
  </dataSource>
</environment>
属性是否必填说明
type必填事务管理器类型。内置支持 JDBC 和 MANAGED,也可指定自定义事务管理器的全限定类名

两种内置类型详解

类型行为描述适用场景
JDBC直接使用 JDBC 的 Connection.commit() 和 Connection.rollback() 管理事务。MyBatis 自己负责事务的开启、提交和回滚独立运行的 Java 应用、不依赖容器的普通项目
MANAGED几乎不做任何事,不主动提交或回滚连接。事务由外部容器(如 Spring 的 @Transactional、Java EE 的 JTA)管理运行在 Spring、Spring Boot、Java EE 容器中的项目

核心原理

事务管理流程图(JDBC vs MANAGED)

源码层面:

  • JDBC 类型对应 JdbcTransaction 类,它在 openConnection() 时将 autoCommit 设为 false,在 commit() 时调用 Connection.commit(),在 rollback() 时调用 Connection.rollback()
  • MANAGED 类型对应 ManagedTransaction 类,它的 commit() 和 rollback() 方法为空实现(或仅根据 closeConnection 配置决定是否关闭连接),完全依赖外部容器控制事务边界

完整示例

场景说明

乐途公司员工管理系统有两套部署方式:

  1. 独立控制台程序:使用 JDBC 事务管理,MyBatis 自己控制事务边界
  2. Spring Boot Web 应用:使用 MANAGED 事务管理,事务由 Spring 的 @Transactional 控制

需要分别演示两种模式下的事务行为差异。

操作前的状态

数据库表:

CREATE TABLE employee (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    employee_name VARCHAR(50) NOT NULL,
    department_code VARCHAR(20),
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

CREATE TABLE salary_record (
    record_id INT PRIMARY KEY AUTO_INCREMENT,
    employee_id INT NOT NULL,
    base_salary DECIMAL(10,2),
    effective_date DATE
) ENGINE=InnoDB;

完整配置代码

配置一:JDBC 模式(独立应用)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <properties resource="db.properties"/>
  <typeAliases>
    <package name="com.feixiang.entity"/>
  </typeAliases>

  <environments default="development">
    <environment id="development">
      <!-- JDBC 模式:MyBatis 自己管理事务 -->
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
    <mapper resource="mapper/SalaryMapper.xml"/>
  </mappers>

</configuration>

JDBC 模式 Java 代码:

public class JdbcTransactionDemo {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

        SqlSession session = factory.openSession();
        try {
            EmployeeMapper empMapper = session.getMapper(EmployeeMapper.class);
            SalaryMapper salMapper = session.getMapper(SalaryMapper.class);

            // 插入员工
            Employee emp = new Employee();
            emp.setEmployeeName("赵六");
            emp.setDepartmentCode("FIN001");
            empMapper.insert(emp);

            // 插入薪资记录
            SalaryRecord record = new SalaryRecord();
            record.setEmployeeId(emp.getEmployeeId());
            record.setBaseSalary(new BigDecimal("15000.00"));
            record.setEffectiveDate(new Date());
            salMapper.insert(record);

            // 模拟异常
            if (true) {
                throw new RuntimeException("模拟业务异常");
            }

            session.commit(); // 正常时提交
            System.out.println("事务已提交");
        } catch (Exception e) {
            session.rollback(); // 异常时回滚
            System.out.println("事务已回滚: " + e.getMessage());
        } finally {
            session.close();
        }
    }
}

配置二:MANAGED 模式(Spring 环境)

<environments default="spring">
  <environment id="spring">
    <!-- MANAGED 模式:容器管理事务 -->
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

MANAGED 模式 Spring 代码:

@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;
    
    @Autowired
    private SalaryMapper salaryMapper;

    @Transactional(rollbackFor = Exception.class)
    public void hireEmployee(String name, String dept, BigDecimal salary) {
        Employee emp = new Employee();
        emp.setEmployeeName(name);
        emp.setDepartmentCode(dept);
        employeeMapper.insert(emp);

        SalaryRecord record = new SalaryRecord();
        record.setEmployeeId(emp.getEmployeeId());
        record.setBaseSalary(salary);
        record.setEffectiveDate(new Date());
        salaryMapper.insert(record);

        // Spring 检测到异常会自动回滚整个事务
        if (salary.compareTo(new BigDecimal("50000")) > 0) {
            throw new IllegalArgumentException("薪资超出上限");
        }
    }
}

实际效果/结果

JDBC 模式输出:

事务已回滚: 模拟业务异常

验证数据库:employee 表和 salary_record 表均无新增记录,说明 session.rollback() 成功回滚了 Connection 上的所有操作。

MANAGED 模式输出:

ERROR o.s.t.i.TransactionInterceptor - Application exception overridden by rollback exception
java.lang.IllegalArgumentException: 薪资超出上限

验证数据库:同样无新增记录,但事务回滚是由 Spring 的 TransactionManager 触发,而非 MyBatis 自身。

分析

  • JDBC 模式下,SqlSession.commit() 和 rollback() 直接操作 JDBC Connection,开发者必须显式调用,否则连接关闭时可能自动提交(取决于数据库驱动)
  • MANAGED 模式下,MyBatis 的 commit()/rollback() 是空操作,事务边界完全由 Spring 的 @Transactional 或 JTA 控制。closeConnection=false 表示连接归还 Spring 管理,不由 MyBatis 关闭
  • 在 Spring Boot 项目中,即使 transactionManager 配置为 JDBC,Spring 的 SqlSessionTemplate 也会覆盖其行为,强制使用 Spring 事务管理。因此 Spring 环境下通常直接配置 MANAGED

易错场景/常见误区

误区错误表现正解
Spring 项目使用 JDBC 事务管理发现 @Transactional 不生效,数据已提交无法回滚Spring 项目应使用 MANAGED,或让 Spring 完全接管 SqlSessionFactory 的事务配置
MANAGED 模式下手动调用 session.commit()误以为提交了事务,实际上事务由容器控制MANAGED 模式下,session.commit() 是空操作。事务控制应交给容器的声明式事务
JDBC 模式下忘记调用 session.commit()插入数据后查询不到,或连接关闭时自动提交导致数据不一致JDBC 模式下必须显式调用 session.commit() 提交事务,或在 openSession(true) 中开启自动提交
认为 MANAGED 模式下 MyBatis 完全不管理连接配置了 MANAGED 但连接泄漏MANAGED 的 closeConnection 属性默认为 true,MyBatis 仍会关闭连接。Spring 环境下应设为 false
混合使用 JDBC 和 MANAGED 管理同一连接事务行为不可预期,可能部分提交一个 SqlSession 的生命周期内,事务管理策略由创建时的 transactionManager 决定,不可中途切换

面试考点

Q1:MyBatis 的 JDBC 和 MANAGED 事务管理有什么区别?

A:JDBC 类型直接使用 JDBC Connection 的 commit() 和 rollback() 方法管理事务,MyBatis 自己控制事务边界,适用于独立应用。MANAGED 类型几乎不执行任何事务操作,将事务管理完全交给外部容器(如 Spring、JTA),适用于容器托管环境。

Q2:在 Spring 集成 MyBatis 时,transactionManager 应该配置为什么?

A:推荐配置为 MANAGED。因为 Spring 通过 SqlSessionTemplate 和 SpringManagedTransaction 接管了事务管理,MyBatis 自身不应再干预事务。即使配置为 JDBC,Spring 的事务管理器也会覆盖其行为。配置 MANAGED 语义更清晰,表明事务由容器控制。

Q3:SqlSession.openSession() 和 openSession(true) 有什么区别?

A:openSession() 创建的 SqlSession 不会自动提交事务,需要手动调用 commit()。openSession(true) 创建的 SqlSession 会在每次执行后自动提交(autoCommit=true),但会牺牲事务一致性,仅适合简单的查询场景或测试。在 JDBC 模式下,前者将 autoCommit 设为 false,后者设为 true。


小结

transactionManager 是 MyBatis 事务体系的"方向盘",它决定了事务控制权的归属。独立应用选择 JDBC,自己掌握事务边界;容器环境选择 MANAGED,将事务交给 Spring 或 JTA 统一管理。理解两者的行为差异,是避免"事务不生效"、"数据不一致"等问题的关键。


下一章引子

事务管理器需要依赖数据源获取连接,而数据源的配置直接决定了应用的性能与稳定性。MyBatis 内置了三种数据源类型:UNPOOLED、POOLED 和 JNDI。其中 POOLED 连接池是生产环境的标配。接下来,我们将深入 dataSource,剖析连接池的工作原理与调优参数。

上一页
environments
下一页
dataSource