分布式事务(一)—本地事务及@Transactional使用、失效原因及解决方案

一、本地事务与分布式事务

先说事务,事务是逻辑上的一组sql语句,在一个事务中的sql语句要么一起成功,要么一起失败。

本地事务就是这一组sql语句在一个数据库连接中执行,即我们常说的事务就是一个本地事务,遵循ACID的。

分布式事务就是这一组sql语句在不同的数据库连接执行。比如小明给小红转钱,在业务代码中这样写

@Transactional
public void test(){
	//执行sql语句给小明-20
	String sql="update account set money=money-20 where name='小明'"
	//远程调用方法给小红余额+20
	httpclient.addXiaoMoney();
}

此时test方法中"给小明-20"和"给小红+20"就不在一个数据库连接中,此时如果test方法发生异常,也只能让"小明-20"的sql回滚。所以@Transactional是本地事务的实现方式

二、本地事务实现方式—@Transactional注解

1.基本介绍

在Spring中实现本地事务的。一共两种,一个是编程式事务,一个是声明式事务,如下

  • 编程式事务:begin,commit手动提交事务
  • 声明式事务:加上@Transactional注解

现在基本上都是使用的@Transactionsal注解

2.@Transactional注解属性

transactionManager

  • 指定事务管理器,值为bean的名称,这个主要用于多事务管理器情况下指定。比如多数据源配置的情况下

isolation

  • 事务的隔离级别,默认是isolation.DEFAULT。这个和数据库事务隔离级别对应,一共四个

propagation(Spring事务的传播行为类型)

事务传播行为是说一个被调用方法事务是否和调用方法事务合为一个大事务。如果合,那么调用方法的所有事务设置传播到被调用方法中
在这里插入图片描述

timeout

  • 事务的超时时间,单位为秒。

readOnly

  • 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。如果一个事务只涉及到只读,可以设置为true。

rollbackFor

  • 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。 默认是在RuntimeException和Error上回滚。

noRollbackFor

  • 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

3.失效原因

1.底层数据库引擎不支持事务

如果数据不支持事务,则Spring自然无法支持事务。如我们常用的mysql,如果引擎采用的是MyISAM,是不支持事务操作的。改成InnoDB才可以。

2.在非public修饰的方法使用

@Transacational注解使用的是AOP,在使用动态代理的时候只能针对public方法进行代理,源码依据在AbsractFallbackTransactionAttributeSource类中的computeTransactionAttribute方法中,如下图所示,注释写着不允许不是public方法。
在这里插入图片描述

3.异常被catch掉了

在整个事务方法中使用try-catch,导致异常无法抛出,自然会导致事务失效。伪代码如下:

@Transactional
public void test(){
    try {
        //数据库操作语句
    }catch (Exception e){
        return;
    }
}
4.方法中调用同类方法

简单来说就是一个类中的A方法(未标注声明式事务)在内部调用了B方法(标注了声明式事务),这样会导致B方法中的事务失效

public class Test{
  public void A(){
    //插入一条数据
    //调用B方法
    B();
  }
  
  @Transactional
  public void B(){
    //插入数据
  }
}

为什么会失效呢?其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

5.rollbackFor(noRollbackFor)属性设置错误

@Transactional(rollbackFor = ServiceException.class),如果在方法中没有抛出该异常则事务失效。反过来对noRollbackFor也一样

6.Spring事务的传播行为类型配置错误

事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED,但是一旦配置了错误的传播属性,也是会导致事务失效,如下三种配置将会导致事务失效:

  • Propagation.SUPPORTS
  • Propagation.NOT_SUPPORTED
  • Propagation.NEVER

4.失效原因总结

将上面的失效原因进行结构化,结构化的角度是从事务的使用到事务回滚过程中可能将发生事务失效。下面分为四个步骤

  • 事务使用
  • 事务执行
  • 抛出异常
  • 事务回滚

四、失效的解决方案

我们上面说到在类中直接调用方法是不走代理类事务的逻辑的,那么如何解决呢?如果是自己本类注入本类实例,可能会有循环依赖。我们采取另一个方式

使用代理类调用方法就可以了

@Transactional
public void a() {
    //this.b();
    TestService service = (TestService)AopContext.currentProxy();
    service.b();
}

@Transactional
public void b(){}

引入依赖spring-boot-starter-aop并在启动类开启代理类注解@EnableAspectJAutoProxy(exposeProxy = true),该注解会使用AspectJ方式取代jdk动态代理,exposeProxy属性是对外暴露代理对象


参考资料:https://blog.csdn.net/nbdebuyaobuyao/article/details/107998258

@Transactional 注解用于指定一个方法或类应该被包装在一个事务中执行。当事务失效时,可能会出现以下几种情况: 1. 配置错误:请确保在 Spring 配置文件中正确配置了事务管理器和事务切面。检查是否在配置文件中添加了 `<tx:annotation-driven>` 标签以启用注解驱动的事务管理。 2. 代理问题:Spring 使用动态代理来管理事务。如果你使用的是基于接口的代理(JDK 动态代理),那么只有通过接口调用的方法才会被事务管理。如果你使用的是基于类的代理(CGLIB 代理),那么所有的方法都会被事务管理。请确保你正确地使用了代理。 3. 异常处理:当方法抛出一个未检查异常(RuntimeException)时,事务会回滚。如果你捕获了该异常并进行了处理,事务将不会回滚。请确保你在方法中正确地处理异常。 4. 方法调用问题:当一个带有事务注解的方法被另一个带有事务注解的方法内部调用时,事务可能会失效。这是因为 Spring 默认只对外部方法应用事务。你可以尝试将事务注解放在类级别而不是方法级别,或者使用 AspectJ 代理模式来解决此问题。 5. 数据库支持问题:某些数据库可能不支持事务或者配置不正确。请确保你使用的数据库和驱动程序支持事务,并且已正确配置。 如果以上解决方法都无效,建议检查日志以获取更多详细信息,并在问题描述中提供更多上下文和代码示例,以便更好地帮助你解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿成长轨迹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值