学习目标:
- 掌握 Spring 整合 MyBatis 的配置方法
- 理解声明式事务的使用和原理
- 掌握事务传播行为的使用场景
- 能解释为什么事务本质上也是基于 AOP 代理实现的
本章重点:
- DataSource、SqlSessionFactoryBean、MapperScan 的整合链路
- @Transactional 的执行原理与常见坑点
- REQUIRED / REQUIRES_NEW / NESTED 等传播行为的区别
- 声明式事务配置、传播行为与常见事务边界问题
1. Spring整合MyBatis
1.1 引入依赖
<dependencies>
<!-- Spring 6.x 对应使用 MyBatis-Spring 3.0.x -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.5</version>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.2.15</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.14</version>
</dependency>
</dependencies>1.2 配置类
@Configuration
@ComponentScan("com.example")
@MapperScan("com.example.mapper") // 扫描Mapper接口
public class AppConfiguration {
/**
* 注册数据源
*/
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
/**
* 注册SqlSessionFactory
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean;
}
}如果你不想直接在配置类上使用 @MapperScan,也可以把扫描器本身注册为容器中的组件。这样做的本质,是把“扫描哪个包下的 Mapper 接口”这件事交给一个专门的 MapperScannerConfigurer 组件来完成。
@Configuration
@ComponentScan("com.example")
public class AppConfiguration {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.mapper");
return configurer;
}
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
return factoryBean;
}
}两种写法的目标是一致的:都是把 Mapper 接口注册为 Spring 容器中的代理对象。区别只是一个更偏注解声明,一个更偏显式配置。
1.3 Mapper接口与使用
// Mapper接口
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Integer id);
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
int insert(User user);
}
// Service直接使用
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper; // 直接注入使用
@Override
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
}1.4 整合原理
最底层先准备 DataSource,它负责提供数据库连接;在它之上,SqlSessionFactoryBean 负责把 MyBatis 的会话工厂交给 Spring 管理;再往上,Mapper 接口会被扫描并注册成可注入的代理对象;最后 Service 层直接注入这些 Mapper 代理对象完成数据库访问。也就是说,业务层并不会直接操作 JDBC 或 SqlSession,而是通过 Spring 容器把整条持久层调用链串接起来。
本章小结
本章核心概念:Spring 整合 MyBatis 的关键,不是零散记住几个注解,而是理解数据源、会话工厂、Mapper 代理对象和 Service 调用之间是如何被 Spring 串起来的。
你现在应该掌握:
- 能说清
DataSource、SqlSessionFactoryBean、@MapperScan各自负责什么 - 能理解为什么 Mapper 接口可以直接被
@Autowired注入使用 - 能画出从 Spring 容器到 Mapper 代理对象再到 Service 的基本整合链路
2. Spring核心接口
- PlatformTransactionManager 平台事务管理器
- TransactionStatus 事务状态
- TransactionDefinition 事务定义
2.1 事务基础回顾
| 阶段 | JDBC代码 | MySQL命令 |
|---|---|---|
| 开启事务 | conn.setAutoCommit(false) |
START TRANSACTION |
| 提交事务 | conn.commit() |
COMMIT |
| 回滚事务 | conn.rollback() |
ROLLBACK |
后续在连接池的内容中,是DataSource来管理connection,也就是说谁管理了DataSource谁就管理了事务。
那么谁管理了DataSource呢,我们接下来继续来看事务的核心接口
2.2 PlatformTransactionManager 平台事务管理器
平台事务管理器,Spring要管理事务,必须使用事务管理器 有多种实现,通过实现此接口,Spring可以管理任何实现了这些接口的事务。 开发人员可以使用统一的编程模型来控制管理事务。
常见的事务管理器的实现 DataSourceTransactionManager,jdbc开发时事务管理器,使用JdbcTemplate、MyBatis(SSM) HibernateTransactionManager,Hibernate开发时事务管理器,整合Hibernate(SSH)
public interface PlatformTransactionManager extends TransactionManager {
// 开启事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}2.3 TransactionStatus 事务状态
获取事务的状态(回滚点、是否完成、是否新事务、是否回滚)属性,是一个过程值
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
boolean hasSavepoint();
void flush();
}提供了关于事务状态的方法

2.4 TransactionDefinition 事务定义
定义事务的名称、隔离级别、传播行为、超时时间长短、只读属性等
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
default int getPropagationBehavior() {
return 0;
}
default int getIsolationLevel() {
return -1;
}
default int getTimeout() {
return -1;
}
default boolean isReadOnly() {
return false;
}
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}手动管理事务
DataSource管理了事务,而PlatformTransactionManager依赖于DataSource组件,其实是PlatformTransactionManager对应的实例来管理事务。
我们向容器中注册一个DataSourceTransactionManager(它是PlatformTransactionManager的实现类),注册这个组件的时候需要依赖于DataSource组件。
// 平台事务管理器,Spring要管理事务,必须要使用到这个组件
// dataSource就是连接池
// 管理事务,其实就是管理连接
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}手动管理事务也就意味着使用PlatformTransactionManager提供的方法来管理事务
@Service
public class MarketAccountServiceImpl implements MarketAccountService {
@Autowired
AccountMapper accountMapper;
// 从容器中获取平台事务管理器组件(DataSourceTransactionManager)
@Autowired
PlatformTransactionManager transactionManager;
@Override
public int transfer(Integer fromId, Integer destId, Integer money) {
// 先查询出来信息
MarketAccount fromAccount = accountMapper.selectByPrimaryKey(fromId);
MarketAccount destAccount = accountMapper.selectByPrimaryKey(destId);
// fromAccount它的钱 少了
fromAccount.setMoney(fromAccount.getMoney() - money);
fromAccount.setUpdateTime(LocalDateTime.now());
// destAccount它的钱 多了
destAccount.setMoney(destAccount.getMoney() + money);
destAccount.setUpdateTime(LocalDateTime.now());
TransactionStatus transactionStatus = null;
try {
// 开启事务
transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
// 执行更新操作
accountMapper.updateByPrimaryKeySelective(fromAccount);
// 制造异常
int i = 1 / 0;
accountMapper.updateByPrimaryKeySelective(destAccount);
// 提交事务
transactionManager.commit(transactionStatus);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(transactionStatus);
throw new RuntimeException(e);
}
return 0;
}
}那么如果说其他的方法也做类似的事情呢
也在执行业务代码之前开启事务,然后对业务代码增加try-catch,如果执行成功提交事务,失败则回滚事务
TransactionStatus transactionStatus = null;
try {
transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
// 核心业务
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
throw new RuntimeException(e);
}比如下面的一个修改业务
@Override
public int modify(Integer id, Integer money) {
TransactionStatus transactionStatus = null;
try {
transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
MarketAccount marketAccount = new MarketAccount();
marketAccount.setId(id);
marketAccount.setMoney(money);
accountMapper.updateByPrimaryKeySelective(marketAccount);
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
throw new RuntimeException(e);
}
return 0;
}这里的方法都使用相同的业务,这时候我们要想要AOP技术
要增加事务的方法 放入到切入点
事务的业务 就是通知
Spring其实也提供了对应的方案,那就是Spring事务\
3. Spring声明式事务
3.1 启用声明式事务
@Configuration
@ComponentScan("com.example")
@MapperScan("com.example.mapper")
@EnableTransactionManagement // 开启声明式事务
public class AppConfiguration {
// ... DataSource配置
// ... SqlSessionFactory配置
/**
* 注册事务管理器(必须)
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
}3.2 @Transactional 使用
@Service
public class TransferServiceImpl implements TransferService {
@Autowired
private AccountMapper accountMapper;
/**
* @Transactional让方法在事务中执行
* - 方法执行前:开启事务
* - 方法正常结束:提交事务
* - 方法抛出异常:回滚事务
*/
@Transactional
@Override
public void transfer(String from, String to, Double amount) {
// 扣款
accountMapper.decreaseBalance(from, amount);
// 模拟异常
// int i = 1 / 0; // 这行会导致事务回滚
// 收款
accountMapper.increaseBalance(to, amount);
}
}3.3 声明式事务原理
┌──────────────────────────────────────────┐ │ @Transactional方法 │ │ │ │ 调用时实际执行的是代理对象的方法: │ │ │ │ 1. 开启事务 │ │ ↓ │ │ 2. 执行业务方法 │ │ ↓ │ │ 3. 判断结果: │ │ ├─ 正常结束 → 提交事务 │ │ └─ 抛出异常 → 回滚事务 │ │ │ └──────────────────────────────────────────┘
这张图的关键不是记住三个步骤,而是先理解:当方法被 @Transactional 标记后,外部调用时真正进入的并不是原始业务对象,而是 Spring 生成的代理对象。代理对象会先决定是否开启事务,再去调用真实业务方法;方法返回后,根据执行结果决定提交还是回滚。也就是说,事务控制逻辑始终包裹在业务方法外围,而不是写死在业务代码内部。
Spring通过AOP生成代理对象,对@Transactional方法做环绕增强:
- 方法执行前:调用
PlatformTransactionManager.getTransaction()开启事务 - 方法执行后:调用
commitTransactionAfterReturning()提交事务 - 抛出异常时:调用
completeTransactionAfterThrowing()回滚事务
3.4 @Transactional 属性
| 属性 | 说明 | 默认值 |
|---|---|---|
propagation |
事务传播行为 | Propagation.REQUIRED |
isolation |
事务隔离级别 | Isolation.DEFAULT |
readOnly |
是否只读事务 | false |
timeout |
超时时间(秒) | -1(无限制) |
rollbackFor |
哪些异常触发回滚 | RuntimeException、Error |
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
readOnly = false,
timeout = 30,
rollbackFor = Exception.class
)
public void transfer(...) { }本章小结
本章核心概念:声明式事务的价值在于把“开启事务、提交事务、回滚事务”这套固定流程从业务代码中抽离出来,让开发者只关注业务边界。
你现在应该掌握:
- 能说明
@EnableTransactionManagement和PlatformTransactionManager为什么缺一不可 - 能理解
@Transactional方法为什么最终还是依赖 AOP 代理生效 - 能根据常见属性判断事务隔离级别、传播行为和回滚规则的作用
4. 事务传播行为
4.1 什么是传播行为
事务传播行为定义了:当一个事务方法调用另一个事务方法时,事务如何传播。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional // 开启事务T1
public void methodA() {
// ... 操作数据库
serviceB.methodB(); // 调用另一个事务方法
}
}
@Service
public class ServiceB {
@Transactional // 怎么处理?加入T1?新建T2?
public void methodB() {
// ... 操作数据库
}
}4.2 7种传播行为
| 传播行为 | 含义 | 使用场景 |
|---|---|---|
| REQUIRED(默认) | 当前有事务则加入,无则新建 | 大多数场景 |
| REQUIRES_NEW | 始终新建事务,挂起当前事务 | 需要独立提交/回滚 |
| NESTED | 当前有事务则创建嵌套事务(保存点) | 子事务可单独回滚 |
| SUPPORTS | 有事务则加入,无则以非事务执行 | 不强制要求事务 |
| NOT_SUPPORTED | 以非事务执行,挂起当前事务 | 不需要事务的方法 |
| MANDATORY | 强制要求存在事务,无则抛异常 | 必须在事务中执行 |
| NEVER | 强制要求无事务,有则抛异常 | 不允许在事务中执行 |
4.3 REQUIRED(默认)
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional // 默认REQUIRED,新建事务T1
public void methodA() {
serviceB.methodB(); // 加入T1,不新建事务
}
}
@Service
public class ServiceB {
@Transactional // 默认REQUIRED
public void methodB() {
// 复用methodA的事务T1
}
}特点:
- methodA和methodB共用同一个事务
- 任一方法抛出异常,整个事务回滚
4.4 REQUIRES_NEW
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// 在事务T1中
serviceB.methodB(); // 挂起T1,新建T2
// 恢复T1
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 在独立的事务T2中
}
}特点:
- methodB在独立事务T2中执行
- T1和T2互不影响,各自提交或回滚
- 适用场景:日志记录,无论主业务成功与否都要记录
4.5 NESTED
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// 外层事务
serviceB.methodB(); // 创建嵌套事务(保存点)
// 继续外层事务
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
// 嵌套事务
}
}特点:
- 子事务回滚不影响父事务
- 父事务回滚,子事务也回滚
- 基于JDBC保存点(Savepoint)实现
三者对比:
REQUIRED: REQUIRES_NEW: NESTED:
┌─────────┐ ┌─────────┐ ┌─────────┐
│methodA │ │methodA │ │methodA │
│(事务T1) │ │(事务T1) │ │(事务T1) │
│ ↓ │ │ ↓ │ │ ↓ │
│methodB │ │挂起T1 │ │保存点1 │
│(共用T1) │ │ ↓ │ │ ↓ │
└─────────┘ │methodB │ │methodB │
│(事务T2) │ │(嵌套事务)│
│ ↓ │ │ ↓ │
│恢复T1 │ │回滚到 │
└─────────┘ │保存点1 │
└─────────┘本章小结
本章核心概念:传播行为本质上是在回答“一个事务方法调用另一个事务方法时,到底共用原事务、挂起原事务,还是在原事务内部建立保存点”。
你现在应该掌握:
- 能区分
REQUIRED、REQUIRES_NEW、NESTED三种最常见传播行为 - 能根据业务目标判断是共用事务还是拆成独立事务
- 能理解传播行为选择错误时为什么会带来回滚范围失控的问题
附录:核心注解速查表
| 注解 | 作用 | 使用位置 |
|---|---|---|
@MapperScan |
扫描MyBatis Mapper接口 | 配置类 |
@EnableTransactionManagement |
开启声明式事务 | 配置类 |
@Transactional |
标记事务方法 | 类/方法 |
@Mapper |
标记Mapper接口 | 接口 |
实战练习
<!-- 实战练习内容已分离到 ../practices/markdown/13-spring-mybatis-practice.md -->
建议先完成本章重点内容复习,再进入配套练习。 练习建议按照:基础 → 进阶 → 综合挑战 的顺序完成。