Spring事务传播机制精讲

0 阅读17分钟

概览

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 T1T1
直接调用无事务方法无事务执行

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 的核心区别

特性NESTEDREQUIRES_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_NEWNOT_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 不会为它创建独立的事务代理,所以它的所有代码(saveOrUpdateSubMonitorServiceserviceStandardRepository.updateById() 等)直接在调用者的事务上下文中执行。

appService.update() 通过代理对象调用 standardService.update() 时:

  1. 代理检测到 appService.update()@Transactional,开启事务 T1
  2. standardService.update() 没有 @Transactional,不会创建新事务,直接 JOIN T1
  3. 其内部所有操作(Repository 调用、私有方法)都继承 T1 的上下文

私有方法同理,它们没有自己的事务声明,继承调用者的事务。

Q3: REQUIRES_NEW 和 NESTED 的区别?

  • REQUIRES_NEW:完全独立的新事务,外层事务被挂起。两者互不影响,但内层抛出异常会中断外层执行。外层事务等待内层完成后恢复。
  • NESTED:共享同一个事务,但在内层创建保存点。内层回滚时可以只回滚到保存点,不影响外层。需要数据库支持保存点(JDBC Savepoint)。
特性NESTEDREQUIRES_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 属性。解析时按以下优先级查找:

  1. 具体方法method.getAnnotation(Transactional.class)
  2. 类上的方法method.getDeclaringClass().getAnnotation(Transactional.class)
  3. 接口上的方法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(独立)
}

最佳实践

  1. 默认用 REQUIRED — 适合绝大多数业务场景,大多数情况只需要一个事务覆盖所有操作
  2. 审计/日志用 REQUIRES_NEW — 确保日志不受主事务回滚影响,审计应该独立记录
  3. 外部调用用 NOT_SUPPORTED — 避免外部系统失败影响本地事务,隔离外部依赖
  4. 强制事务用 MANDATORY — 防止误用,确保关键方法必须在事务内被调用
  5. 可选子操作用 NESTED — 需要部分回滚能力时,如可选的积分扣减、优惠码验证
  6. 避免同类内部调用 — 会导致事务注解失效,这是最常见的事务问题
  7. 不要在事务中处理耗时操作 — 如远程调用、文件IO,会导致数据库连接占用过久
  8. catch 了异常后如果需要回滚必须手动抛出 — 吞掉异常会默认提交事务
  9. 读写分离场景使用 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 源码:TransactionInterceptorTransactionSynchronizationManager
  • MyBatis-Plus:IService 默认使用 SqlSession 自动参与当前事务