事务管理器
SpringBoot中的@Transctional注解是用的什么事务管理器
在 Spring Boot 中,@Transactional 注解默认使用的事务管理器取决于你引入的依赖(Starter)以及配置的数据源。
Spring Boot 通过自动装配(Auto-configuration)机制,会根据类路径下的 jar 包自动为你注册一个合适的 PlatformTransactionManager 实现类。
1. 最常见的场景:JDBC / MyBatis / MyBatis-Plus
如果你使用的是 spring-boot-starter-jdbc 或 mybatis-spring-boot-starter:
- 默认实现类:
DataSourceTransactionManager - 原理:它通过控制
java.sql.Connection的commit()和rollback()来管理事务。
2. 使用 JPA 的场景
如果你使用的是 spring-boot-starter-data-jpa:
- 默认实现类:
JpaTransactionManager - 原理:它是对
EntityManager的封装。如果底层是 Hibernate,它实际上是在管理 Hibernate 的Session事务。
3. 多种管理器的优先级
如果你的项目中同时存在 JPA 和 JDBC(这种情况较少),Spring Boot 会优先配置 JpaTransactionManager,因为它可以兼容管理普通的 DataSource 事务。
4. 自动装配的底层逻辑
Spring Boot 的自动配置类 TransactionAutoConfiguration 会生效。其核心逻辑大致如下:
- 检查环境:检查是否有
PlatformTransactionManager类型的 Bean。 - 默认创建:如果没有手动配置,它会根据当前环境创建 Bean。
- 如果有
EntityManagerFactory$\rightarrow$ 创建JpaTransactionManager。 - 如果没有 JPA 但有
DataSource$\rightarrow$ 创建DataSourceTransactionManager。
- 如果有
5. 如何查看当前使用的是哪一个?
如果你想在程序中确认,可以注入 ApplicationContext 并获取 Bean,或者在 application.properties 中开启调试日志:
Properties
logging.level.org.springframework.transaction=DEBUG
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG当请求进入 @Transactional 方法时,控制台会打印出类似:
Creating new transaction with name [...]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
从打印的日志类名就能一眼看出具体的管理器。
6. 特殊情况:响应式编程 (WebFlux)
如果你使用的是 Spring WebFlux 配合 R2DBC:
- 事务管理器:
ReactiveTransactionManager(具体实现为R2dbcTransactionManager)。 - 注意:传统的
PlatformTransactionManager是阻塞式的,在响应式环境下是不起作用的。
java.sql.Connection 的 commit() 和 rollback()在提交事务之前数据库会做更改吗
这是一个非常经典的问题,涉及到数据库缓冲区(Buffer Pool)和事务原子性的底层原理。
简单直接的回答是:会做更改,但这些更改处于“中间状态”,对其他事务不可见(取决于隔离级别),且没有持久化到磁盘的数据文件中。
为了让你理解得更透彻,我们需要把这个过程拆解为“内存”和“磁盘”两个维度:
1. 内存层面:更改已经发生了
当你执行 UPDATE 或 INSERT 语句,但还没有调用 commit() 时:
- Buffer Pool(缓冲池):InnoDB 会先将目标行所在的“数据页”加载到内存的 Buffer Pool 中。
- 内存修改:MySQL 会直接在内存中修改这一行的数据。此时,内存中的数据页变成了“脏页”(Dirty Page),因为它和磁盘上的数据不一致了。
- 这就意味着:在内存层面,数据库确实已经根据你的指令做了更改。
2. 日志层面:做了“持久化”的准备
为了保证崩溃恢复(Crash Recovery),在 commit() 之前,MySQL 会写日志:
- Undo Log:在修改内存前,先记录“撤销日志”。如果你之后调用
rollback(),MySQL 就靠它把内存里的数据改回去。 - Redo Log:记录“做了什么修改”。在调用
commit()之前,Redo Log 已经开始顺序写入 Redo Log Buffer 甚至部分刷入磁盘了。
3. 数据库文件的状态
- 磁盘数据文件(.ibd):在
commit()之前,磁盘上的原始数据文件通常不会被修改。MySQL 异步地将内存脏页刷回磁盘,但这通常发生在事务提交之后。 - 可见性:虽然内存改了,但根据 MVCC(多版本并发控制) 机制,其他事务在读取时,会根据
Undo Log构造出修改前的版本,所以它们“看不见”你的更改(在 Read Committed 隔离级别下)。
4. 调用 commit() 和 rollback() 时发生了什么?
| 操作 | 数据库内部动作 |
|---|---|
| commit() | 1. 将 Redo Log 真正刷入磁盘(设置 prepare 和 commit 标志)。 2. 释放该行上的 X 锁(行锁)。 3. 标记该事务修改对其他事务可见。 |
| rollback() | 1. 利用 Undo Log 将内存 Buffer Pool 中的脏页改回原来的样子。 2. 释放持有的所有锁。 3. 丢弃该事务产生的日志信息。 |
总结
在 commit() 之前,数据库已经在内存里动了手术,并且在日志里留了底稿。
如果你突然断电(数据库宕机):
- 如果没有
commit,重启后 MySQL 会发现 Redo Log 里没有提交标记,于是利用 Undo Log 把内存里的残余修改抹掉。 - 如果已经
commit,即使数据还没来得及从内存刷进磁盘文件,MySQL 也能通过 Redo Log 把数据恢复回来。
这就是 ACID 特性中原子性(A)和持久性(D)的底层保障。
你想了解一下在高并发下,频繁 commit 产生的日志 IO 瓶颈该如何优化吗?
aopcontext是什么
简单来说,AopContext 是 Spring AOP 提供的一个工具类,它的核心作用是让目标对象能够获取到指向它自身的代理对象(Proxy)。
在 Spring 中,它最常被用来解决一个经典问题:类内部自调用导致 AOP(如 @Transactional)失效。
1. 为什么需要 AopContext?(自调用失效问题)
Spring AOP 是基于代理模式实现的。当你调用一个被 @Transactional 标注的方法时,你实际上是在调用代理对象的方法,代理对象会在执行真正逻辑前开启事务。
但是,如果你的代码如下:
Java
@Service
public class OrderService {
public void createOrder() {
// 直接调用内部方法,其实是 this.save()
save();
}
@Transactional
public void save() {
// 保存逻辑...
}
}问题点:在 createOrder() 中直接调用 save(),绕过了代理对象,直接通过 this(目标对象本身)调用。此时,@Transactional 里的事务增强逻辑不会生效。
2. AopContext 如何解决?
通过 AopContext.currentProxy(),你可以强制拿到当前的代理对象,从而让内部调用也能走 AOP 增强逻辑。
修改后的代码:
Java
public void createOrder() {
// 显式获取当前代理对象并调用
((OrderService) AopContext.currentProxy()).save();
}3. 如何开启 AopContext?
AopContext 默认是不开启的(因为它会带来额外的性能开销,需要维护一个 ThreadLocal)。如果你直接调用 currentProxy() 会抛出异常。
你需要在 Spring 配置中显式开启:
- 注解方式:
@EnableAspectJAutoProxy(exposeProxy = true) - XML 方式:
<aop:config expose-proxy="true">
4. 加锁与事务中的 AopContext (结合你之前的提问)
回到你最开始问的库存扣减:
如果你的库存扣减逻辑写在 updateStock() 方法里并带了事务,而你又在同一个 Service 的另一个普通方法里调用它,那么:
- 如果不使用 AopContext:事务不生效 $\rightarrow$ MySQL 默认在非事务下执行
UPDATE$\rightarrow$ 语句结束后立即释放行锁。 - 如果使用 AopContext:事务生效 $\rightarrow$ 事务开启 $\rightarrow$ 执行
UPDATE获取行锁 $\rightarrow$ 直到事务提交,锁才会释放。
5. 更好的替代方案
虽然 AopContext 很有用,但它让代码耦合了 Spring API。业内通常更推荐以下做法:
- 自我注入:在 Service 里用
@Autowired注入自己(Spring 4.3+ 支持)。 - 拆分 Service:将事务方法移动到另一个独立的 Service 类中。
- 编程式事务:使用
TransactionTemplate手动控制事务,不依赖 AOP 代理。
你想看看这几种替代方案的具体代码实现对比吗?
Spring AOP 代理模式与自调用失效详解
这个视频详细介绍了 Spring AOP 的基本原理和切面编程的核心概念,有助于你深入理解为什么代理对象在自调用时会“消失”。