深入理解Spring事务机制

一:故事背景

本文将重点分享Spring事务相关知识,通过这篇文章,了解Spring事务的实现原理,让你以后在开发中,使用的有底气,有依据。

二:核心知识

2.1 Spring事务种类

2.2.1 编程式事务

  1. 编程式事务是一种在代码中显式地编写事务管理逻辑的方法,相对于声明式事务来说,更加灵活但也更加繁琐。在编程式事务中,开发者需要通过编写代码来控制事务的开始、提交和回滚。
  2. Spring框架同样提供了编程式事务管理的支持,通常通过编程式事务管理接口来实现,例如PlatformTransactionManager。

例子:

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Component
public class BankService {
    
    

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
    
    
        this.transactionManager = transactionManager;
    }

    public void transferFunds(String fromAccount, String toAccount, double amount) {
    
    
        TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
    
    
            // 执行转账操作,更新账户余额
            // 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额

            updateAccountBalance(fromAccount, -amount);
            updateAccountBalance(toAccount, amount);

            transactionManager.commit(txStatus);
        } catch (Exception e) {
    
    
            transactionManager.rollback(txStatus);
            throw e;
        }
    }

    private void updateAccountBalance(String account, double amount) {
    
    
        // 更新账户余额的具体实现
    }
}

2.2.2 声明式事务

我们最常用的就是声明式事务,这里列出3点使用的注意事项:

  1. 声明式事务使用@Transactional 注解的方式
  2. 声明式事务的管理是建立在AOP上的,本质是通过AOP功能,对方法的前后进行拦截,将事务的处理编织到拦截的方法中去。
  3. 通过@Transaction最细的粒度只能做到方法的级别,在目标方法执行前开启事务,执行完成后提交事务,出现错误回滚事务。

例子:

import org.springframework.transaction.annotation.Transactional;

@Component
public class BankService {
    
    
	
	@Transactional
    public void transferFunds(String fromAccount, String toAccount, double amount) {
    
    
        // 执行转账操作,更新账户余额
        // 省略具体的转账业务逻辑,假设 updateAccountBalance 方法用于更新账户余额

        updateAccountBalance(fromAccount, -amount);
        updateAccountBalance(toAccount, amount);
    }

    private void updateAccountBalance(String account, double amount) {
    
    
        // 更新账户余额的具体实现
    }
}

注意如果@Transactional加到方法上的话,会对指定方法进行事务管理、如果加到类上的话,会对这个类所有的public都进行事务管理。

2.2 Spring事务隔离级别

TransactionDefinition 接口定义了事务的各种属性,如传播行为、隔离级别、超时时间等,其实主要还是对应数据库的事务隔离级别。
我们来看一下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;                // 使用默认的超时时间

    /**
     * 获取事务的传播行为。
     *
     * @return 事务传播行为
     */
    default int getPropagationBehavior() {
    
    
        return 0;
    }

    /**
     * 获取事务的隔离级别。
     *
     * @return 事务隔离级别
     */
    default int getIsolationLevel() {
    
    
        return -1;
    }

    /**
     * 获取事务的超时时间。
     *
     * @return 事务超时时间
     */
    default int getTimeout() {
    
    
        return -1;
    }

    /**
     * 获取事务是否为只读。
     *
     * @return 是否为只读事务
     */
    default boolean isReadOnly() {
    
    
        return false;
    }

    /**
     * 获取事务的名称。
     *
     * @return 事务名称
     */
    @Nullable
    default String getName() {
    
    
        return null;
    }

    /**
     * 创建一个具有默认属性的事务定义。
     *
     * @return 默认的事务定义
     */
    static TransactionDefinition withDefaults() {
    
    
        return StaticTransactionDefinition.INSTANCE;
    }
}

通过上面的TransactionDefinition类对应的属性可以看出主要还是对应数据库的事务隔离级别

2.3 Spring事务传播机制

2.3.1 概念

Spring 事务的传播机制说的是,当多个事务同时存在的时候— —⼀般指的是多个事务⽅法相互调⽤
时,Spring 如何处理这些事务的⾏为。
事务传播机制是使⽤简单的 ThreadLocal 实现的,所以,如果调⽤的⽅法是在新线程调⽤的,事务传
播实际上是会失效的。

2.3.2 七种事务传播机制

事务传播机制通过指定@Transactional的propagation属性进行指定

    @Transactional(propagation = Propagation.REQUIRED)
    public void  test() {
    
    
    
    }

Propagation枚举里面的7个可选项就是7种对应的传播机制

/**
 * 定义了事务的传播行为,描述了在多个事务性方法相互调用时,事务的行为方式。
 */
public enum Propagation {
    
    
    REQUIRED(0),         // 默认 如果当前没有事务,创建一个新事务;如果已存在事务,则加入到当前事务中
    SUPPORTS(1),         // 如果当前存在事务,就在事务中执行;否则以非事务方式执行
    MANDATORY(2),        // 必须在一个已存在的事务中执行,否则抛出异常
    REQUIRES_NEW(3),     // 每次都创建一个新的事务,挂起当前事务(如果存在)
    NOT_SUPPORTED(4),    // 以非事务方式执行,挂起当前事务(如果存在)
    NEVER(5),            // 以非事务方式执行,如果存在事务则抛出异常
    NESTED(6);           // 如果当前存在事务,就在嵌套事务中执行;否则创建一个新事务

    private final int value;

    /**
     * 构造函数,用于设置传播行为对应的数值。
     *
     * @param value 传播行为的数值表示
     */
    private Propagation(int value) {
    
    
        this.value = value;
    }

    /**
     * 获取传播行为对应的数值表示。
     *
     * @return 传播行为的数值表示
     */
    public int value() {
    
    
        return this.value;
    }
}

2.4 Spring声明式事务实现原理

Spring声明式事务的实现原理是通过 AOP+动态代理进行实现的,主要分为以下两个部分:

2.4.1 Bean初始化创建代理对象

Spring 容器在初始化每个单例 bean 的时候:

  1. 遍历容器中的所有 BeanPostProcessor 实现类,并执⾏其 postProcessAfterInitialization ⽅法
  2. 在执⾏AbstractAutoProxyCreator 类的 postProcessAfterInitialization ⽅法时会遍历容器中所有的切⾯,查找与当前实例化 bean 匹配的切⾯,这⾥会获取事务属性切⾯,查找@Transactional 注解及其属性值,然后根据得到的切⾯创建⼀个代理对象,默认是使⽤ JDK 动态代理创建代理,如果⽬标类是接口,则使⽤ JDK 动态代理,否则使⽤ Cglib。

2.4.2 执行目标方法时进行事务增强

在执⾏⽬标⽅法时进⾏事务增强操作:当通过代理对象调⽤ Bean ⽅法的时候,会触发对应的

  1. AOP 增强拦截器,声明式事务是⼀种环绕增强,对应接⼜为 MethodInterceptor ,事务增强对该
    接⼜的实现为 TransactionInterceptor
  2. 事务拦截器 TransactionInterceptor 在 invoke ⽅法中,通过调⽤⽗类 TransactionAspectSupport
    的 invokeWithinTransaction ⽅法进⾏事务处理,包括开启事务、事务提交、异常回滚。

2.5 Spring声明式事务失效场景

2.5.1 应用在非Public的方法上

Spring AOP 代理时,TransactionInterceptor (事务拦截器)在⽬标⽅法执⾏前后进⾏拦
截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept ⽅法 或
JdkDynamicAopProxy 的 invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource
的 computeTransactionAttribute⽅法,获取 Transactional 注解的事务配置信息。
在这里插入图片描述

2.5.2 属性propagation设置错误

上面我们讲了7中事务传播行为,如果选择以下三种可能导致事务失效:
在这里插入图片描述

2.5.3 rollbackFor设置错误

  1. rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承⾃ RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。
  2. 若在⽬标⽅法中抛出的异常是 rollbackFor 指定的异常的⼦类,事务同样会回滚。
// 希 望 ⾃ 定 义 的 异 常可以进⾏回滚
@Transactional( propagation = Propagation .REQUIRED , rollbackFor = MyException . class)

在这里插入图片描述

2.5.4 同一个类中方法调用

是由于使⽤ Spring AOP 代理,只有当事务⽅法被当前类以外的代码调⽤时,才会由 Spring ⽣成的代理对象来管理。如果我们一个类内有两个方法A和方法B。B开启了事务,A调用了B,A没有开启事务,这样的话,B的事务实际是无效的。
例如:

@Service
public class UserService {
    
    

    @Transactional
    public void processUser(User user) {
    
    
        // Some processing logic
    }

    public void createUserAndProcess(String username) {
    
    
        User user = createUser(username);
        processUser(user);
    }
}

如果外部调用的是createUserAndProcess的话processUser的事务就会失效。

三:总结提升

本文系统的总结了Spring中事务的不同种类、隔离级别、传播机制、声明式事务实现原理、还总结了4中可能导致事务失效的场景,希望读者读完之后能明确事务的原理,并且在使用中避开常见的可能导致事务失效的坑。

猜你喜欢

转载自blog.csdn.net/hlzdbk/article/details/132403946