@Transactional 不能用在非Public方法上吗? 浅谈Spring中的事务管理

Spring中的事务管理

事务的属性

事务的属性是进行事务管理必不可少的一部分,事务管理会根据这些属性执行不同的操作,因此在了解事务管理之间,需要先简单知道一下有哪些属性。

1. 传播行为(Propagation Behavior)

-PROPAGATION_REQUIRED: 如果当前有事务,加入该事务;如果没有事务,新建一个事务(这是默认设置)。

  • PROPAGATION_REQUIRES_NEW: 总是新建一个事务,如果当前有事务,则挂起当前事务。
  • PROPAGATION_SUPPORTS: 支持当前事务,如果没有事务,则以非事务方式执行。
  • PROPAGATION_NOT_SUPPORTED: 总是以非事务方式执行,如果当前有事务,则挂起当前事务。
  • PROPAGATION_MANDATORY: 必须在已有事务中执行,否则抛出异常。
  • PROPAGATION_NEVER: 必须在非事务环境中执行,如果当前有事务,则抛出异常。
  • PROPAGATION_NESTED: 如果当前有事务,则在嵌套事务中执行。没有就新建一个

2. 隔离级别(Isolation level)

与数据库相同

  • ISOLATION_DEFAULT: 使用底层数据库的默认隔离级别(这是默认设置)。
  • ISOLATION_READ_UNCOMMITTED: 允许读取未提交的数据,可能会导致脏读。
  • ISOLATION_READ_COMMITTED: 只能读取已提交的数据,避免脏读。
  • ISOLATION_REPEATABLE_READ: 多次读取相同数据会返回相同结果,避免不可重复读。
  • ISOLATION_SERIALIZABLE: 完全串行化的读取,确保事务之间完全隔离,代价最高。

3. 超时时间(Time out)

设置事务的超时时间(单位为秒)。如果事务在指定时间内未完成,将会被回滚。
默认值为-1,表示没有超时限制

4. 只读模式(Read-Only Mode)

readOnly: 如果设置为 true,则提示底层数据库进行只读优化。适用于只进行查询操作的事务。4
默认为false

封装类:

Spring中使用TransactionDefinition 封装这些属性,具体如下:
在这里插入图片描述

编程式事务

编程式事务,用来手动管理事务的边界,直接调用Spring的事务管理api来启动,提交,或回滚事务
这种方式通常是使用transactionTemplateplatformTransactionManager

platformTransactionManager

1. 开启事务:

要开启事务,首先需要调用 PlatformTransactionManager 的 getTransaction 方法。这个方法会返回一个 TransactionStatus 对象,表示当前事务的状态。getTransaction 需要传入一个TransactionDefinition,可以自定义,也可以传一个默认的配置DefaultTransactionDefinition

2. 提交事务:

如果事务操作成功,可以调用 PlatformTransactionManager 的 commit 方法来提交事务。提交后,所有在事务中执行的操作都会被持久化。

3. 回滚事务:

如果事务操作失败或者发生了异常,可以调用 PlatformTransactionManager 的 rollback 方法来回滚事务。回滚后,事务中的所有操作都会被撤销。

@Autowired
private PlatformTransactionManager transactionManager;

public void updateSaleByManager(int id, int stock) {
    TransactionStatus status = transactionManager.getTransaction(
                               new DefaultTransactionDefinition());
    try {
        bookDao.updateSale(id, stock);
        bookDao.updateStock(id, -stock);
        int i = 1/0;
        transactionManager.commit(status);
    }catch (Exception e) {
        transactionManager.rollback(status);
    }
}

TransactionTemplate

对platformTransactionManager 进行了封装,实际还是调用的其方法,只不过简化了一些操作,供开发人员使用
在execute中开启事务,
通过status.setRollbackOnly()回滚操作,否则会自动提交

实例代码
@Autowired
private TransactionTemplate template;
  
public void updateSaleByTemplate(int id, int stock) {

     template.execute(status -> {
         bookDao.updateSale(id, stock);
         bookDao.updateStock(id, -stock);
//          status.setRollbackOnly();
        return null;
    });
}

这是execut的源码

@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    //先判空
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
    //获取到 ptm
    PlatformTransactionManager var3 = this.transactionManager;
    /*CallbackPreferringPlatformTransactionManager  
    主要用于需要异步或事件驱动的事务管理场景。
    这种方式允许开发者将事务逻辑封装在回调中,减少显式事务管理的复杂性。
    不在本次讨论返回内,可以直接略过
    */
    if (var3 instanceof CallbackPreferringPlatformTransactionManager cpptm) {
        return cpptm.execute(this, action);
    } 
    //主要逻辑!!
    else {
        //开启事务
        TransactionStatus status = this.transactionManager.getTransaction(this);

        Object result; //定义结果变量
        try {
            result = action.doInTransaction(status); //执行我们刚刚写的代码  
        } 
        //捕获异常,并回滚
        catch (Error | RuntimeException var6) {
            this.rollbackOnException(status, var6); 
            throw var6;
        } catch (Throwable var7) {
            this.rollbackOnException(status, var7);
            throw new UndeclaredThrowableException(var7, "TransactionCallback threw undeclared checked exception");
        }
        //提交事务
        this.transactionManager.commit(status);
        return result;
    }
}

声明式事务

声明式事务管理是 Spring 中更为常用的事务处理方式,利用 AOP(面向切面编程)来处理事务管理。
@Transactional 是最常用的方式,只需将其应用到类或方法上,Spring 就会自动为你管理事务。注解中可以定义事务的属性,此外,声明式还可以配置只对某些特性的异常进行回滚,而忽略其他异常

注解失效的情况

  1. 首先事务是需要数据库支持的,mysql中 nnoDB支持事务,如果使用其他的引擎 如MyISAM,那么不支持事务,自然就会失效
  2. 注解生效的前提是Spring通过代理,对其进行增强,那么,只要没有被代理,就会失效,以下是几种代理失效的情况
  • 直接在本类中调用方法,不会走代理对象,那就不会生效,解决的话可以考虑AspectJ
  • 静态方法不会被代理
  • 没有被Spring管理 ,没有被Spring管理的对象,无法被检查事务注解的拦截器拦截,那就无法对Bean进行代理增强,
  1. 没有配置事务管理器
  2. 事务传播方式为 NOT_SUPPORTED || NEVER
  3. 异常被吃掉,如果在方法体里手动处理了异常而没有抛出,那么就不会被代理后对象方法检查到,自然无法回滚
  4. 异常类型错误:注解默认回滚的异常是RunTimeException,如果想触发其他异常回滚,需要手动定义rollbackFor

@Transactional在非Public方法上注解会失效?

如果你之前看过有关注解的失效场景的话,会发现几乎都提到了,注解写到非Public方法上会失效,因为JDK动态代理只能代理实现接口的方法,而接口的访问修饰符就是public
此外 ,在检查事务属性时,也会判断方法是否为public如果是非public 会返回null
检查事务属性的方法在AbstractFallbackTransactionAttributeSource类中

    @Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
       //allowPublicMethodsOnly()会返回true,从而会检查方法是否为public
        if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } else {
        		……
            }
        }
    

但在SpringBoot 3 中,已经将这个方法的返回值改为false,那么就不会执行后面的校验,因此,非public方法也可以被代理了

默认实现方法 在 AbstractFallbackTransactionAttributeSource

    protected boolean allowPublicMethodsOnly() {
        return false;
    }

在继承类中的实现,其中publicMethodsOnly为成员变量,在初始化的时候被赋值

    protected boolean allowPublicMethodsOnly() {
        return this.publicMethodsOnly; //这个publicMethodsOnly是成员变量,在初始化时就被设为false
    }

通过查阅资料与询问gpt,可以发现,在SpringBoot的自动配置导入过程中,会执行这么一段代码

    @Bean
    @Role(2)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource(false);//在Spring2中,使用的是无参构造,而无参构造 publicMethodsOnly 默认为真
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值