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配置决定是否关闭连接),完全依赖外部容器控制事务边界
完整示例
场景说明
乐途公司员工管理系统有两套部署方式:
- 独立控制台程序:使用
JDBC事务管理,MyBatis 自己控制事务边界 - 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()直接操作 JDBCConnection,开发者必须显式调用,否则连接关闭时可能自动提交(取决于数据库驱动)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类型直接使用 JDBCConnection的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,剖析连接池的工作原理与调优参数。