@Transactional的底层原理

1,053 阅读3分钟

@Transactional 是 Spring 框架中用于声明式事务管理的注解,其核心原理基于 Spring AOP(面向切面编程)和动态代理机制。下面详细解释其工作原理:

1. 核心机制:AOP 与动态代理

  • AOP(面向切面编程) :Spring 通过 AOP 在目标方法执行前后插入事务管理逻辑(如开启、提交或回滚事务),而不需要修改业务代码。

  • 动态代理:Spring 支持两种代理方式:

    • JDK 动态代理:基于接口实现,代理对象必须实现至少一个接口。
    • CGLIB 代理:基于继承实现,通过生成子类覆盖目标方法。

2. 事务管理器(PlatformTransactionManager)

  • Spring 通过 PlatformTransactionManager 接口管理事务,不同的数据访问技术有不同的实现:

    • JDBC:使用 DataSourceTransactionManager
    • JPA:使用 JpaTransactionManager
    • Hibernate:使用 HibernateTransactionManager

3. 工作流程

  1. 解析注解

    • Spring 在启动时通过 @EnableTransactionManagement 或 XML 配置启用事务支持。
    • 通过 @Transactional 注解标记需要事务管理的方法或类(类级别表示所有方法默认有事务)。
  2. 创建代理对象

    • Spring 扫描到 @Transactional 注解后,为目标类创建代理对象。
  3. 拦截方法调用

    • 当调用被 @Transactional 标记的方法时,代理对象会拦截该调用。
  4. 事务管理逻辑

    • 开启事务:获取数据库连接,设置隔离级别和传播行为。

    • 执行目标方法:调用实际业务逻辑。

    • 提交或回滚

      • 若方法正常返回,提交事务。
      • 若抛出异常,根据 rollbackFor 和 noRollbackFor 配置决定是否回滚(默认回滚 RuntimeException 和 Error)。

4. 关键属性

  • propagation(传播行为) :定义事务如何传播,例如:

    • REQUIRED(默认):如果当前存在事务,则加入;否则创建新事务。
    • REQUIRES_NEW:总是创建新事务,挂起当前事务(如果存在)。
    • SUPPORTS:支持当前事务,若无则以非事务方式执行。
  • isolation(隔离级别) :定义事务的隔离程度,如 READ_COMMITTEDSERIALIZABLE 等。

  • rollbackFor:指定哪些异常触发回滚(默认仅运行时异常)。

  • timeout:事务超时时间。

  • readOnly:标记为只读事务,可优化性能。

5. 示例代码

java

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        // 从账户扣款
        User fromUser = userRepository.findById(fromId).orElseThrow();
        fromUser.setBalance(fromUser.getBalance().subtract(amount));
        userRepository.save(fromUser);

        // 模拟异常
        if (true) throw new RuntimeException("模拟转账失败");

        // 转入账户
        User toUser = userRepository.findById(toId).orElseThrow();
        toUser.setBalance(toUser.getBalance().add(amount));
        userRepository.save(toUser);
    }
}
  • 执行结果:由于抛出 RuntimeException,整个事务回滚,两个账户的余额均不变。

6. 注意事项

  1. 自调用问题

    • 同一个类中方法 A 调用带 @Transactional 的方法 B,事务不会生效,因为没有通过代理对象调用。
    • 解决方案:通过 ApplicationContext 或 AopContext 获取代理对象。
  2. 异常处理

    • 若方法内部捕获异常但未重新抛出,事务不会回滚。
    • 需确保异常传播到代理对象,触发事务回滚逻辑。
  3. 代理模式选择

    • 若目标类未实现接口,Spring 默认使用 CGLIB 代理。
    • 若需强制使用 JDK 代理,可配置 proxyTargetClass = false

7. 底层实现简化示例

下面是 Spring 代理对象的简化逻辑(仅作原理示意):

java

public class TransactionalProxy {
    private final Object target; // 目标对象
    private final PlatformTransactionManager transactionManager;

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 检查方法是否有 @Transactional 注解
        if (method.isAnnotationPresent(Transactional.class)) {
            TransactionStatus status = null;
            try {
                // 2. 开启事务
                status = transactionManager.getTransaction(new DefaultTransactionDefinition());
                
                // 3. 执行目标方法
                Object result = method.invoke(target, args);
                
                // 4. 提交事务
                transactionManager.commit(status);
                return result;
            } catch (Exception e) {
                // 5. 回滚事务(根据异常类型)
                if (shouldRollbackOn(e)) {
                    transactionManager.rollback(status);
                }
                throw e;
            }
        }
        // 无 @Transactional 注解,直接调用目标方法
        return method.invoke(target, args);
    }
}

总结

@Transactional 通过 Spring AOP 和动态代理,在方法执行前后自动管理事务,实现了事务管理与业务逻辑的解耦。其核心在于代理对象拦截方法调用,根据注解配置控制事务的开启、提交和回滚。使用时需注意自调用问题和异常处理,确保事务按预期生效。