概览
Spring 事务传播定义了多个事务方法调用时,事务如何决定。当一个事务方法调用另一个事务方法时,被调用方法的事务行为由传播行为决定。
7 种传播行为
| 传播行为 | 说明 | 典型场景 |
|---|---|---|
REQUIRED | 有就加入,没有就创建 | 默认,大多数业务方法 |
REQUIRES_NEW | 总是创建新事务,挂起旧的 | 日志、审计、邮件发送 |
SUPPORTS | 有就加入,没有就算了 | 只读查询 |
NOT_SUPPORTED | 以非事务执行,有就挂起 | 外部集成、异步任务 |
MANDATORY | 必须有事务,否则抛异常 | 强制事务边界 |
NEVER | 必须没事务,否则抛异常 | 禁止事务的方法 |
NESTED | 在现有事务内嵌套(保存点) | 部分回滚场景 |
1. REQUIRED
有事务就加入,没有就创建新的。
这是默认的传播行为。
@Service
class OrderService {
@Transactional
public void createOrder(Order order) {
orderRepository.save(order); // JOIN 主事务
this.saveItems(order.getItems()); // 没有 @Transactional,也在主事务中
this.sendNotification(order); // 没有 @Transactional,也在主事务中
}
// 即使没有 @Transactional,也会在 createOrder 的事务中执行
public void saveItems(List<OrderItem> items) {
items.forEach(orderRepository::saveItem);
}
public void sendNotification(Order order) {
// 同样 JOIN 主事务
notificationService.send(order);
}
}
场景:绝大多数业务操作应该使用 REQUIRED。
| 调用方式 | 是否有外层事务 | 行为 | 实际事务 |
|---|---|---|---|
a.requiredMethod() | 无 | 创建新事务 | method 的事务 |
txMethod() 中调用 requiredMethod() | 有(T1) | JOIN T1 | T1 |
| 直接调用无事务方法 | 无 | 无事务执行 | 无 |
2. REQUIRES_NEW
总是创建新事务,挂起所有现有事务。
@Service
class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String operation, String detail) {
// 始终在独立事务中执行
// 即使调用者的事务回滚,审计日志也不受影响
auditRepository.save(new AuditLog(operation, detail));
}
}
事务挂起图解
txMethod() [创建 T1]
│
└── auditService.log() [REQUIRES_NEW]
│
├── T1 被挂起
├── 创建新事务 T2
├── T2 执行并提交/回滚
└── T1 恢复继续执行
实际效果
@Transactional
public void transfer(Account from, Account to, BigDecimal amount) {
accountRepository.debit(from, amount); // T1
accountRepository.credit(to, amount); // T1
try {
auditService.log("transfer", "从 " + from + " 到 " + to); // 独立 T2
} catch (Exception e) {
// 即使审计失败,转账也不受影响
log.warn("审计记录失败", e);
}
}
| 场景 | T1 (transfer) | T2 (audit) | 结果 |
|---|---|---|---|
| audit 成功 | 继续 | 提交 | 两者都提交 |
| audit 失败 | 继续执行 | 回滚 | transfer 正常完成 |
| transfer 失败 | 回滚 | 不受影响 | audit 已提交不受影响 |
注意:REQUIRES_NEW 会挂起外层事务,意味着外层事务在 REQUIRES_NEW 方法执行期间是暂停的,直到新事务完成。
3. SUPPORTS
有事务就 JOIN,没有就不创建。
@Transactional(propagation = Propagation.SUPPORTS)
public List<Order> queryOrders(Long userId) {
// 如果在事务内调用,保证读一致性(可重复读)
// 如果无事务调用,就以非事务执行
return orderRepository.findByUserId(userId);
}
场景:查询方法既可以在事务内调用(保证一致性),也可以无事务直接调用(提高性能)。
4. NOT_SUPPORTED
以非事务方式执行,如果当前有事务则挂起它。
@Service
class NotificationService {
// 不应该被业务事务包裹,邮件发送失败不应该导致业务回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendEmail(String to, String subject, String body) {
emailClient.send(to, subject, body);
}
}
挂起效果
@Transactional
public void submitOrder(Order order) {
orderRepository.save(order); // T1 执行
this.generateInvoice(order); // T1 被挂起,无事务执行
this.sendEmailConfirm(order); // 无事务执行
// T1 恢复继续执行
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void generateInvoice(Order order) {
// 不在任何事务中
// 即使抛异常也不会导致 order 保存的事务回滚
invoiceService.create(order);
}
场景:
- 发送邮件/短信
- 调用外部系统(不希望被外部事务影响)
- 某些不需要事务的后台任务
5. MANDATORY
必须有现有事务,否则抛
IllegalStateException。
@Service
class PaymentService {
// 必须在已有事务内调用,防止直接调用导致数据不一致
@Transactional(propagation = Propagation.MANDATORY)
public void processPaymentCallback(PaymentCallback callback) {
// 只有在 controller 层开启的事务内才能执行
paymentRepository.updateStatus(callback.getOrderId(), callback.getStatus());
}
}
调用示例:
// 正确:controller 开启事务后调用
@Transactional
public void handlePaymentReturn() {
paymentService.processPaymentCallback(callback); // OK
}
// 错误:直接调用会抛异常
paymentService.processPaymentCallback(callback); // 抛异常:No existing transaction found
场景:强制方法只能在事务内被调用,用于 API 契约约束。
6. NEVER
必须当前没有事务,否则抛异常。
@Service
class LegacyService {
// 该方法不能接受事务,否则会破坏某些外部系统的状态
@Transactional(propagation = Propagation.NEVER)
public void callExternalApi() {
// 如果外层有事务,直接抛异常
}
}
场景:很少使用,通常用于与不支持事务的旧系统交互。
7. NESTED
在现有事务内创建保存点,形成嵌套事务。
与 REQUIRES_NEW 的核心区别
| 特性 | NESTED | REQUIRES_NEW |
|---|---|---|
| 事务数量 | 1 个(共享连接) | 2 个(独立连接) |
| 回滚影响 | 可能只回滚嵌套部分 | 外层不受影响 |
| 性能 | 更好(共享连接) | 更差(独立连接) |
| 数据库要求 | 需要 JDBC 保存点支持 | 所有数据库支持 |
图解
outer() [创建 T1]
│
└── nested() [NESTED → 在 T1 内创建保存点 SP1]
│
├── insertData() ← 在 SP1 之后
│
└── 如果这里失败 → 回滚到 SP1(只回滚 nested 内的操作)
│
└── outer() 继续执行(order 已保存)
代码示例
@Transactional
public void createOrder() {
Order order = new Order();
orderRepository.save(order); // T1,订单已生成
try {
this.deductPoints(); // NESTED,失败只回滚积分部分
} catch (InsufficientPointsException e) {
log.warn("积分不足,继续下单");
// 不影响订单,积分扣减已回滚到保存点
}
paymentRepository.save(payment); // 继续 T1
}
@Transactional(propagation = Propagation.NESTED)
public void deductPoints() {
// 在 T1 内创建保存点
pointsRepository.deduct(userId, points); // 失败回滚到这里
pointsHistoryRepository.save(new PointsHistory(...));
}
场景:
- 部分操作失败但不影响主业务
- 需要在同一个业务中支持可选的子操作
- 嵌套优惠券、积分等可选业务逻辑
实际代码中的事务传播分析
update() 调用链
MonitorServiceAppServiceImpl.update() [创建 T1]
│
├── validationUtils.validateAndThrow() // 无事务,非事务执行
│
├── standardService.update() // 无注解 → REQUIRED → JOIN T1
│ │
│ ├── serviceStandardRepository.updateById() // JOIN T1
│ │
│ ├── DbSyncUtils.syncData() (分发逻辑编码)
│ │ ├── logicCodeRepository.updateBatchById() // JOIN T1
│ │ ├── logicCodeRepository.removeBatchByIds() // JOIN T1
│ │ └── logicCodeRepository.saveBatch() // JOIN T1
│ │
│ ├── DbSyncUtils.syncData() (事件信息)
│ │ ├── eventRepository.updateBatchById() // JOIN T1
│ │ ├── eventRepository.removeBatchByIds() // JOIN T1
│ │ └── eventRepository.saveBatch() // JOIN T1
│ │
│ ├── DbSyncUtils.syncData() (产品信息)
│ │ ├── productRepository.updateBatchById() // JOIN T1
│ │ ├── productRepository.removeBatchByIds() // JOIN T1
│ │ └── productRepository.saveBatch() // JOIN T1
│ │
│ └── saveOrUpdateSubMonitorService() // 私有方法,继承 T1
│ ├── 查询已有子服务
│ ├── 遍历 expectedChildMap
│ │ ├── update(childBO) // 递归 → JOIN T1
│ │ └── add(childBO) // 标准服务 → JOIN T1
│ └── deleteSubMonitorService() // JOIN T1
│
├── nonstandardService.update() // 无注解 → JOIN T1
│ ├── nonstandardRepository.updateById() // JOIN T1
│ └── productRepository.saveBatch() // JOIN T1
│
├── contractService.update() // 无注解 → JOIN T1
│ └── repository.updateById() // JOIN T1
│
└── billConfigService.updateBatch() // 无注解 → JOIN T1
├── billConfigRepository.list() // JOIN T1
├── billConfigRepository.removeByIds() // JOIN T1
├── itemDetailRepository.remove() // JOIN T1
├── freeConfigRepository.remove() // JOIN T1
├── billConfigRepository.save() // JOIN T1
├── billConfigRepository.updateById() // JOIN T1
├── itemDetailRepository.saveBatch() // JOIN T1
└── freeConfigRepository.saveBatch() // JOIN T1
结论
整个 update() 方法在一个事务中(T1),所有子服务调用和 Repository 操作都 JOIN 该事务。rollbackFor = Exception.class 确保任何异常都会触发整体回滚。
Spring 事务实现原理
核心机制:代理模式
Spring 事务是通过 AOP(面向切面编程) 实现的,核心是基于 代理模式。当类加有 @Transactional 注解时,Spring 会为该类创建一个代理对象,所有对该类方法的调用都通过这个代理对象。
正常调用(无代理)
┌─────────────┐
│ Caller │
└──────┬──────┘
│直接调用
▼
┌─────────────┐
│ TargetBean │ ← 直接执行,没有任何拦截
└─────────────┘
代理调用(有 @Transactional)
┌─────────────┐
│ Caller │
└──────┬──────┘
│调用代理
▼
┌─────────────┐
│ Proxy │ ← 判断方法是否有 @Transactional
│ │ - 有:开启/加入事务
│ │ - 无:直接转发
└──────┬──────┘
│
▼
┌─────────────┐
│ TargetBean │ ← 实际业务逻辑
└─────────────┘
JDK 动态代理 vs CGLIB
Spring 创建代理有两种方式:
| 代理类型 | 条件 | 实现方式 | 限制 |
|---|---|---|---|
| JDK 动态代理 | 实现接口的类 | java.lang.reflect.Proxy | 只能代理实现了接口的类 |
| CGLIB 代理 | 无接口或强制使用 | 继承目标类生成子类 | 无法代理 final 类/方法 |
Spring 默认策略:
- 有接口 → JDK 动态代理
- 无接口 → CGLIB 代理
- 通过配置可强制使用 CGLIB:
proxy-target-class="true"
// JDK 动态代理示例
public class JdkDynamicProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的拦截逻辑(事务开启)
if (method.isAnnotationPresent(Transactional.class)) {
beginTransaction();
}
Object result = method.invoke(target, args); // 执行业务逻辑
// 方法调用后的处理(事务提交/回滚)
if (method.isAnnotationPresent(Transactional.class)) {
commitTransaction();
}
return result;
}
}
@Transactional 注解的解析流程
1. Spring 启动时扫描所有 Bean
2. 发现带有 @Transactional 的类/方法
3. 为其创建代理对象(Proxy)
4. 将代理对象注入到依赖该 Bean 的地方
调用时:
┌─────────────────────────────────────────────────────────────┐
│ caller.method() │
│ ↓ │
│ 代理对象拦截 → 检查方法/类是否有 @Transactional │
│ ↓ │
│ 有 → 获取 TransactionManager → 开启/加入事务 │
│ ↓ │
│ 执行业务逻辑(真实方法调用) │
│ ↓ │
│ 正常完成 → 提交事务 │
│ 异常退出 → 根据 rollbackFor 判断是否回滚 │
└─────────────────────────────────────────────────────────────┘
关键组件
1. TransactionInterceptor(事务拦截器)
public class TransactionInterceptor {
private TransactionManager transactionManager;
public Object invoke(MethodInvocation invocation) {
// 1. 获取事务属性
TransactionAttribute txAttr = getTransactionAttribute(invocation);
// 2. 获取/创建事务
TransactionInfo txInfo = createTransaction(txAttr);
try {
// 3. 执行目标方法
Object result = invocation.proceed();
// 4. 正常结束,提交事务
commitTransaction(txInfo);
return result;
} catch (Throwable ex) {
// 5. 异常,根据 rollbackFor 判断是否回滚
if (shouldRollback(ex, txAttr)) {
rollbackTransaction(txInfo, ex);
} else {
commitTransaction(txInfo); // 即使异常也不回滚
}
throw ex;
} finally {
// 6. 清理事务信息
cleanupTransactionInfo(txInfo);
}
}
}
2. TransactionInfo(事务信息)
public class TransactionInfo {
private final TransactionStatus status; // 事务状态对象
private final TransactionAttribute attr; // 事务属性(传播行为等)
private final String name; // 事务名称
// 保存旧的事务信息(用于嵌套时恢复)
private final TransactionInfo oldTransactionInfo;
}
3. TransactionStatus(事务状态)
public interface TransactionStatus {
boolean isNewTransaction(); // 是否是新事务
void setRollbackOnly(); // 标记为只能回滚
boolean isRollbackOnly(); // 是否标记为只能回滚
boolean isCompleted(); // 是否已结束
boolean hasSavepoint(); // 是否有保存点(NESTED)
}
4. TransactionSynchronizationManager(事务同步管理器)
每个线程持有自己的事务上下文:
public abstract class TransactionSynchronizationManager {
// 核心:ThreadLocal 存储当前线程的事务信息
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transaction resources");
public static Object getResource(Object key) {
return resources.get().get(key);
}
public static void bindResource(Object key, Object value) {
resources.get().put(key, value);
}
public static void unbindResource(Object key) {
resources.remove().bind();
}
}
传播行为的实现原理
Spring 通过 TransactionInterceptor 根据传播行为决定如何处理事务:
// 伪代码展示传播行为判断逻辑
public TransactionInfo createTransaction(TransactionAttribute txAttr) {
String name = txAttr.getName();
Propagation propagation = txAttr.getPropagation();
if (propagation == Propagation.REQUIRED) {
// REQUIRED: 查找当前是否有事务
TransactionInfo existingTx = TransactionSynchronizationManager.getCurrentTransaction();
if (existingTx != null) {
// 有事务 → JOIN
return existingTx;
} else {
// 无事务 → 创建新事务
return startNewTransaction();
}
}
if (propagation == Propagation.REQUIRES_NEW) {
// REQUIRES_NEW: 总是创建新事务,挂起旧的
TransactionInfo suspendedTx = suspendCurrentTransaction();
TransactionInfo newTx = startNewTransaction();
return newTx; // 注意:suspendedTx 会在后续恢复
}
if (propagation == Propagation.NESTED) {
// NESTED: 使用保存点
if (hasTransaction()) {
return createSavepoint(); // 创建保存点
} else {
return startNewTransaction();
}
}
// ... 其他传播行为类似
}
事务挂起(Suspend)原理
当使用 REQUIRES_NEW 或 NOT_SUPPORTED 时,外层事务会被挂起:
// 挂起事务
private TransactionInfo suspendTransaction() {
if (isTransactionExists()) {
// 1. 暂停当前线程的同步器
List<TransactionSynchronization> suspendedSyncs = suspendSynchronizations();
// 2. 解绑当前事务的资源(DataSource 等)
Object suspendedResource = unbindResource();
// 3. 创建挂起状态对象保存所有信息
SuspendedResourcesHolder holder = new SuspendedResourcesHolder(
suspendedSyncs, suspendedResource, ...
);
// 4. 返回挂起信息,后续恢复时使用
return new TransactionInfo(null, null, null, holder);
}
return null;
}
// 恢复事务
private void resumeTransaction(TransactionInfo txInfo) {
if (txInfo.getSuspendedResourcesHolder() != null) {
// 1. 重新绑定资源
rebindResource(txInfo.getSuspendedResourcesHolder().getResource());
// 2. 恢复同步器
resumeSynchronizations(txInfo.getSuspendedResourcesHolder().getSynchronizations());
// 3. 清理挂起状态
}
}
事务同步(Transaction Synchronization)
事务同步用于在事务提交/回滚后执行回调:
@Transactional
public void someMethod() {
// 注册事务同步回调
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 事务提交后执行(如发送消息)
}
@Override
public void afterRollback() {
// 事务回滚后执行
}
}
);
// 业务逻辑...
}
常见陷阱:自调用(Self-Invocation)
这是 Spring 事务最常见的失效场景。
@Service
class OrderService {
@Transactional
public void createOrder() {
this.saveOrder(); // ❌ this 调用不走代理
this.saveItems(); // ❌ 同样不走代理
}
@Transactional
public void saveOrder() {
// 不会开启新事务!
// 因为是 this.saveOrder(),绕过了代理
}
@Transactional
public void saveItems() {
// 同样不会开启新事务
}
}
原因:
正常代理调用(走代理)
┌─────────────┐
│ Caller │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Proxy │ ──→ │ TargetBean │ ← 代理拦截,开启事务
└─────────────┘ └─────────────┘
自调用(绕过的代理)
┌─────────────┐
│ Caller │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Proxy │ │ TargetBean │ ← 绕过了代理,直接调用
│ (未被调用) │ └─────────────┘
└─────────────┘
解决方案:
// 方案 1:注入自身
@Service
class OrderService {
@Resource
private OrderService self; // 注入代理后的自身
@Transactional
public void createOrder() {
self.saveOrder(); // 通过代理调用
self.saveItems();
}
}
// 方案 2:通过 ApplicationContext 获取代理
@Service
class OrderService {
@Resource
private ApplicationContext ctx;
@Transactional
public void createOrder() {
ctx.getBean(OrderService.class).saveOrder(); // 强制走代理
}
}
// 方案 3:AopContext.currentProxy()
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyService {
public void createOrder() {
((MyService) AopContext.currentProxy()).saveOrder();
}
}
同类内部调用的真实执行流程
// 假设调用链:controller → orderService.createOrder()
controller.orderService.createOrder() // 1. 从容器获取的是代理对象
│
└── 代理对象拦截
│
├── 检测到 @Transactional
├── 开启事务 T1
│
└── this.createOrder() // 真实对象上的方法(不是代理)
│
└── this.saveOrder() // 又是 this 调用,不走代理
│
└── 实际执行业务代码(无事务)
为什么 Service 内部的无注解方法也能参与主事务?
@Service
class StandardService {
public String update(ImplBO bo) { // 无 @Transactional
this.saveOrUpdate(); // 无 @Transactional
this.repository.update(); // 直接调 Repository
}
private void saveOrUpdate() { // 私有方法
// 所有操作都在 update() 的事务上下文中
}
}
原因:当 standardService.update() 被 MonitorServiceAppServiceImpl 通过代理对象调用时,整个 update() 方法的执行都在已开启的事务 T1 中。内部私有方法 saveOrUpdate() 是被 update() 直接调用的,不是通过代理,所以它继承了 update() 所在的事务上下文。
调用链(带代理)
appService.update() [代理创建 T1]
│
└── standardService.update() [代理检查:无 @Transactional → JOIN T1]
│
└── update() 方法体执行(真实对象上)
│
└── saveOrUpdate() [无 @Transactional → 继承 T1]
│
└── repository.update() [JOIN T1]
常见面试问题
Q1: Spring 事务是通过什么机制实现的?
是通过 AOP 代理 实现的。当类或方法标注 @Transactional 时,Spring 会为其创建代理对象。所有外部调用该类的方法都会经过代理,代理负责在方法调用前开启/加入事务,方法返回后提交或回滚。
核心组件:
TransactionInterceptor:拦截方法调用,执行事务逻辑TransactionSynchronizationManager:以 ThreadLocal 形式保存当前线程的事务上下文TransactionInfo:包含事务状态、属性等信息
Q2: 为什么子方法没有 @Transactional 也能在主事务中执行?
因为 standardService.update() 没有 @Transactional,Spring 不会为它创建独立的事务代理,所以它的所有代码(saveOrUpdateSubMonitorService、serviceStandardRepository.updateById() 等)直接在调用者的事务上下文中执行。
当 appService.update() 通过代理对象调用 standardService.update() 时:
- 代理检测到
appService.update()有@Transactional,开启事务 T1 standardService.update()没有@Transactional,不会创建新事务,直接 JOIN T1- 其内部所有操作(Repository 调用、私有方法)都继承 T1 的上下文
私有方法同理,它们没有自己的事务声明,继承调用者的事务。
Q3: REQUIRES_NEW 和 NESTED 的区别?
- REQUIRES_NEW:完全独立的新事务,外层事务被挂起。两者互不影响,但内层抛出异常会中断外层执行。外层事务等待内层完成后恢复。
- NESTED:共享同一个事务,但在内层创建保存点。内层回滚时可以只回滚到保存点,不影响外层。需要数据库支持保存点(JDBC Savepoint)。
| 特性 | NESTED | REQUIRES_NEW |
|---|---|---|
| 事务数量 | 1 个(共享连接) | 2 个(独立连接) |
| 回滚影响 | 可能只回滚嵌套部分 | 外层不受影响 |
| 性能 | 更好(共享连接) | 更差(独立连接) |
| 数据库要求 | 需要 JDBC 保存点支持 | 所有数据库支持 |
Q4: 如何让某个方法绝对不参与当前事务?
使用 REQUIRES_NEW,它会挂起现有事务并创建全新事务。即使外层事务回滚,新事务中的操作也不会回滚(因为是独立的)。
Q5: REQUIRED 嵌套 REQUIRED 会怎样?
@Transactional
public void outer() {
this.inner(); // inner 没有注解,默认 REQUIRED
}
@Transactional
public void inner() {
// 不会创建新事务,会直接 JOIN outer 的事务
}
两者在同一个事务中,任意抛异常都会回滚整个事务。
Q6: 事务传播失效的场景?
同一个类内部方法调用(this.xxx())不走代理,所以 @Transactional 注解会失效:
@Service
class A {
@Transactional
public void outer() {
this.inner(); // this 调用不走代理,inner 的事务注解失效!
}
@Transactional
public void inner() {
// 不会开启新事务,也不会 JOIN outer 的事务
// 因为是直接调用真实对象的方法,不是通过代理
}
}
解决方案:注入自身代理或通过 ApplicationContext 获取。
Q7: JDK 动态代理和 CGLIB 代理的区别?
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现方式 | 实现接口,通过 Proxy 类生成 | 继承目标类生成子类 |
| 限制 | 只能代理实现了接口的类 | 无法代理 final 类/方法 |
| 性能 | 稍慢(反射调用) | 更快(直接调用) |
| Spring 默认 | 有接口时使用 | 无接口或强制配置时使用 |
Q8: 事务挂起(Suspend)是如何实现的?
通过 TransactionSynchronizationManager.unbindResource() 将当前事务的资源(DataSource 的 Connection)从当前线程解绑,并保存到一个 SuspendedResourcesHolder 对象中。新事务创建自己的资源并绑定到线程。恢复时,将保存的资源重新绑定到线程。
挂起不是物理上暂停事务,而是逻辑上暂停外层事务的执行,内层 REQUIRES_NEW 事务完成后,外层事务继续执行。
Q9: TransactionSynchronization 有什么用?
用于在事务提交/回滚后执行回调,常见场景:
- 事务提交后发送消息
- 事务回滚后清理缓存
- 资源释放
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
messageService.send("order-created");
}
}
);
Q10: @Transactional 的 readOnly 属性有什么用?
标记为只读事务,Spring 会优化查询性能:
- 数据库会尝试使用只读锁(如 MySQL 的
SELECT ... LOCK IN SHARE MODE变为无锁) - 可能启用数据库优化(如 Oracle 的闪回查询)
- JDBC 的
setReadOnly(true)帮助数据库识别只读操作
但注意:只有事务性查询才有意义,非事务查询直接执行不受影响。
面试深度追问
Q11: Spring 如何判断一个方法是否有 @Transactional?
Spring 通过 AbstractBeanFactory.transactionAttributeCache 缓存已解析的 @Transactional 属性。解析时按以下优先级查找:
- 具体方法:
method.getAnnotation(Transactional.class) - 类上的方法:
method.getDeclaringClass().getAnnotation(Transactional.class) - 接口上的方法:
interface.getAnnotation(Transactional.class)
如果方法或类都没有,则认为没有事务。
Q12: 事务的 name 属性有什么用?
@Transactional(name = "myTransaction")
事务的 name 用于在日志和调试中标识事务。但 Spring 默认使用 类的全限定名.方法名 作为事务名称,一般不需要显式指定。
Q13: isolation 属性隔离级别和数据库隔离级别有什么区别?
@Transactional(isolation = Isolation.REPEATABLE_READ) 是请求的隔离级别,实际效果取决于数据库:
- MySQL 默认是
REPEATABLE_READ - Oracle 默认是
READ_COMMITTED
如果请求的隔离级别数据库不支持,会使用数据库支持的更高级别(更严格)。
Q14: noRollbackFor 和 noRollbackForClassName 有什么用?
指定某些异常时不回滚事务:
@Transactional(noRollbackFor = BusinessException.class)
public void process() {
// 如果抛 BusinessException,不会回滚
// 但会提交事务
}
注意:指定了 noRollbackFor 的异常如果不在 rollbackFor 范围内,Spring 默认只对 RuntimeException 回滚,所以这个场景必须是 RuntimeException 子类才有效。
Q15: 如何让多个事务方法按顺序执行而不互相干扰?
确保每个方法都是 REQUIRES_NEW,每个方法拥有独立事务:
public void workflow() {
step1(); // T1
step2(); // T2(独立)
step3(); // T3(独立)
}
最佳实践
- 默认用 REQUIRED — 适合绝大多数业务场景,大多数情况只需要一个事务覆盖所有操作
- 审计/日志用 REQUIRES_NEW — 确保日志不受主事务回滚影响,审计应该独立记录
- 外部调用用 NOT_SUPPORTED — 避免外部系统失败影响本地事务,隔离外部依赖
- 强制事务用 MANDATORY — 防止误用,确保关键方法必须在事务内被调用
- 可选子操作用 NESTED — 需要部分回滚能力时,如可选的积分扣减、优惠码验证
- 避免同类内部调用 — 会导致事务注解失效,这是最常见的事务问题
- 不要在事务中处理耗时操作 — 如远程调用、文件IO,会导致数据库连接占用过久
- catch 了异常后如果需要回滚必须手动抛出 — 吞掉异常会默认提交事务
- 读写分离场景使用 readOnly — 查询方法标记只读可获得数据库优化
// 好实践
@Transactional
public void createOrder() {
orderRepository.save(order); // 在事务中
try {
this.deductPoints(); // REQUIRES_NEW,失败不影响订单
} catch (PointsException e) {
log.warn("积分扣减失败", e);
}
this.sendNotification(); // NOT_SUPPORTED,不影响事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductPoints() { ... }
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendNotification() { ... }
// 坏实践
@Transactional
public void badExample() {
try {
this.internalMethod(); // ❌ 自调用,事务失效
} catch (Exception e) {
// 吞掉异常,事务不会回滚!
}
}
@Transactional
public void internalMethod() {
// 不会开启事务
}
完整事务流程图
外部调用 orderService.createOrder()
│
▼
┌─────────────────────────┐
│ Proxy Object │
│ (TransactionInterceptor)│
└───────────┬─────────────┘
│
▼ 检查 @Transactional 注解
│
┌────────┴────────┐
│ 有注解 │ 无注解
▼ ▼
开始事务 直接执行
│
▼
TransactionSynchronizationManager.bindResource()
│
▼
┌─────────────────────────┐
│ 调用 Target 方法 │
│ (在事务上下文中执行) │
└───────────┬─────────────┘
│
▼
┌────────┴────────┐
│ 正常完成 │ 异常抛出
▼ ▼
提交事务 检查 rollbackFor
│ │
│ ┌───────┴───────┐
│ ▼ ▼
│ 需要回滚 不需要回滚
│ │ │
│ ▼ ▼
│ 回滚 提交
│ │ │
└──────────────┴───────────────┘
│
▼
TransactionSynchronizationManager.unbindResource()
🎁 专属福利:9 折优惠邀请码
Minimax Token Plan,可享 9 折优惠!
👉 立即参与:MiniMax
智普GLM5.1 code Plan,可享 9 折优惠!
👉 立即参与:GLM
参考资料
- Spring Framework Documentation: Transaction Management
- 《Spring 实战》- Craig Walls
- Spring 源码:
TransactionInterceptor、TransactionSynchronizationManager - MyBatis-Plus:
IService默认使用SqlSession自动参与当前事务