首页 > 其他分享 >spring事务失效的场景

spring事务失效的场景

时间:2023-06-01 17:34:52浏览次数:50  
标签:事务 场景 spring 失效 userModel 上下文 方法 public

spring事务失效的场景

图片

1.访问权限

AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是 public,则TransactionAttribute返回 null,即不支持事务。

image-20230531111348052

也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是public,而是 private、default 或 protected 的话,spring 则不会提供事务功能。

2.final修饰

spring的事务基于AOP,而AOP基于cglib ,而cglib动态代理的本质是基于继承实现的,在java中一旦方法被final修饰,则不能被重写,类被final修饰不能被继承。

对于final修饰方法:下面这段话摘自《Java编程思想》第四版第143页:

  “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

3.方法内部调用

在同一个类中,方法直接内部的调用,会导致事务失效

实例代码:

@Service
public class UserService {

   @Autowired
   private UserMapper userMapper;

 
   public void add(UserModel userModel) {
       userMapper.insertUser(userModel);
       updateStatus(userModel);
  }

   @Transactional
   public void updateStatus(UserModel userModel) {
       doSameThing();
  }
}

具体原因:在于调用的updateStatus方法不是代理对象的updateStatus方法

在我们看到在事务方法 add 中,直接调用事务方法 updateStatus,updateStatus 方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。

解决:通过依赖注入获取代理对象,通过代理对象调用其updateStatus方法就可。

或者通过AopContext类获取到代理对象,再通过代理对象调用

4.未被spring管理

也就是未注入到spring,事务未生效

5.多线程调用

spring事务是通过数据库链接实现的,同一事务是同一数据链接,不同线程涉及的事务是属于不同的数据库链接,只有拥有同一数据库链接,事务才可以提交和回滚。

实例问题代码:

@Slf4j
@Service
public class UserService {

   @Autowired
   private UserMapper userMapper;
   @Autowired
   private RoleService roleService;

   @Transactional
   public void add(UserModel userModel) throws Exception {
       userMapper.insertUser(userModel);
       new Thread(() -> {
           roleService.doOtherThing();
      }).start();
  }
}

@Service
public class RoleService {

   @Transactional
   public void doOtherThing() {
       System.out.println("保存role表数据");
  }
}
/**
从上面的例子中,我们可以看到事务方法 add 中,调用了事务方法 doOtherThing,但是事务方法 doOtherThing 是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想 doOtherThing 方法中抛了异常,add 方法也回滚是不可能的
**/

6.表是否支持事务

在myisam 数据库引擎中是不支持事务的

扩展:myisam 不支持事务,不支持行锁,不支持外键

7.事务是否开启

传统spring项目需要在配置文件 applicationContext.xml 中开启事务,事务才可以生效

对于springboot项目 springboot 通过DataSourceTransactionManagerAutoConfiguration类,已经默默地帮你开启了事务。只需要配置spring.datasource相关参数即可。

8.错误的传播特性

使用@Transactional注解时,是可以指定propagation参数的。

该参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:

  • REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。

  • SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

  • MANDATORY 当前上下文中必须存在事务,否则抛出异常。

  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

9.手动捕获处理了异常

程序员将操作数据库方法发生的异常自己捕获并处理,让spring认为该代码未发生异常,则便不会发生事务回滚

实例问题代码:

@Slf4j
@Service
public class UserService {
   
   @Transactional
   public void add(UserModel userModel) {
       try {
           saveData(userModel);
           updateData(userModel);
      } catch (Exception e) {
           log.error(e.getMessage(), e);
      }
  }
}
//如果想要 spring 事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则 spring 认为程序是正常的。

10.手动抛出spring事务不捕获的异常

对于spring事务来说,默认只捕获RuntimeException和Error

默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。

实例问题代码:

@Slf4j
@Service
public class UserService {
   
   @Transactional
   public void add(UserModel userModel) throws Exception {
       try {
            saveData(userModel);
            updateData(userModel);
      } catch (Exception e) {
           log.error(e.getMessage(), e);
           throw new Exception(e);
      }
  }
}

11.自定义了回滚异常

@Transactional 注解声明事务时,有时我们想自定义回滚的异常,通过设置rollbackFor参数

实例代码:

@Slf4j
@Service
public class UserService {
   
   @Transactional(rollbackFor = BusinessException.class)
   public void add(UserModel userModel) throws Exception {
      saveData(userModel);
      updateData(userModel);
  }
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。

即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。 这是为什么呢? 因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成: Exception或Throwable

12.嵌套事务异常未捕获导致未必要回滚

问题代码:

public class UserService {

   @Autowired
   private UserMapper userMapper;

   @Autowired
   private RoleService roleService;

   @Transactional
   public void add(UserModel userModel) throws Exception {
       userMapper.insertUser(userModel);
       roleService.doOtherThing();
  }
}

@Service
public class RoleService {

   @Transactional(propagation = Propagation.NESTED)
   public void doOtherThing() {
       System.out.println("保存role表数据");
  }
}

doOtherThing发生异常,该异常未捕获,会继续向上抛出到add方法,导致在add方法内正常更新操作的insertUser方法也发生不必要回滚

解决办法便是通过手动捕获doOtherThing发生的异常,使异常不再向上抛出。就不会使add方法捕获到异常发生updateUser的回滚。

扩展:其他注意点

大事务问题(声明式事务的问题)

直接在类或方法上加上@Transational注解,使得不必要使用事务的方法加上了事务,造成整个程序事务非常耗时

推荐使用编程式事务

编程式事务:spring 还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务。

 @Autowired
  private TransactionTemplate transactionTemplate;
 
  ...
 
  public void save(final User user) {
        queryData1();
        queryData2();
        transactionTemplate.execute((status) => {
           addData1();
           updateData2();
           return Boolean.TRUE;
        })
  }

在 spring 中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的 execute 方法中,就实现了事务的功能。

相较于@Transactional注解声明式事务,我更建议大家使用基于TransactionTemplate的编程式事务。主要原因如下:

避免由于 spring aop 问题导致事务失效的问题。

能够更小粒度地控制事务的范围,更直观。

建议在项目中少使用 @Transactional 注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用 @Transactional 注解开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。

 

复习:事务的传播特性

事务的传播特性,spring 目前支持 7 种传播特性:

  • REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。

  • SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

  • MANDATORY 当前上下文中必须存在事务,否则抛出异常。

  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

参考文章,原文地址:https://blog.csdn.net/hanjiaqian/article/details/120501741

标签:事务,场景,spring,失效,userModel,上下文,方法,public
From: https://www.cnblogs.com/boyxiaokang/p/17449673.html

相关文章

  • Spring的AOP复习
     连接点:所有业务方法切入点:被挖掉共性功能的业务方法 通知:共性功能构成的方法通知类型:前面还是后面  切面:描述切入点和通知的关系 目标对象 织入:将共性功能放回去 代理引入......
  • spring StateMachine简易使用
    SpringStateMachine是一个状态机框架,在Spring框架项目中,开发者可以通过简单的配置就能获得一个业务状态机,而不需要自己去管理状态机的定义、初始化等过程。今天这篇文章,我们通过一个案例学习下SpringStateMachine框架的用法。 pom依赖中添加<parent><groupId>org.......
  • SpringBoot项目中实现读写分离
    背景介绍面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极......
  • 前后端分离的架构,前端使用Vue2.6.10,后端使用SpringBoot2.0.0的ERP实现
    技术架构技术框架:SpringBoot2.0.0+Mybatis1.3.2+SLF4J1.7+Vue2.6.10+Ant-Design-Vue1.5.2+Mysql5.7+Redis运行环境:jdk8+IntelliJIDEA+maven+宝塔面板本地部署:1.小皮面板创建一个数据库,导入jsh_erp.sql文件至数据库中,该文件在后端程序的docs文件夹下。2.使用......
  • Spring AOP 使用介绍,从前世到今生
    SpringAOP使用介绍,从前世到今生 https://www.javadoop.com/post/spring-aop-intro @Before("execution(*com.javadoop.dao.*.*(..))")Tips:上面匹配中,通常"."代表一个包名,".."代表包及其子包,方法参数任意匹配使用两个点".."。......
  • M2M场景之客户端凭证模式|OIDC & OAuth2.0 认证协议最佳实践系列 【4】
    在前两篇文章中,我们介绍了 OIDC 授权码以及授权码增强的PKCE模式,本次我们将重点围绕(ClientCredentials)模式进行讲解,ClientCredentials模式是OIDC授权模式之一,它是一种用于客户端(应用程序)以自己的名义向OIDC服务端获取访问令牌(accesstoken)的认证授权模式,常用于保护A......
  • 院士到访百度,共同探讨智慧水务及AI4S创新应用场景
    5月24日,美国国家工程院院士、中国工程院外籍院士、密歇根大学格伦·托马斯·戴格尔教授(Prof.GlenDaigger)和清华大学环境学院刘艳臣研究员到访百度,并与飞桨团队、智能云水务业务部和工业产品部专家进行了亲切交流。Prof.GlenDaigger教授是智慧水务创新公司OneWaterSoluti......
  • springcloud的configserver配置中心默认中文乱码的解决方法
    springcloud的configserver配置中心默认中文乱码的解决方法先表明我的springcloud版本(2021.0.6)和对应springboot版本(2.6.14)ReleaseTrainVersion: 2021.0.6SupportedBootVersion: 2.6.14https://docs.spring.io/spring-cloud/docs/2021.0.6/reference/html/ 问题描述:当从......
  • Springboot实现ENC加密jasypt-spring-boot-starter
    依赖:<!--配置文件加密--><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.4</version>&l......
  • golang实现设计模式之构建者模式总结-代码、优缺点、适用场景
    构建者模式也是一种创建型的设计模式,该模式将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的对象。大白话就是,构建者模式,从输出角度看依然是创建个对象实例,但是构建者模式更关注创建的细节,或者说一个对象的创建可以拆分为多个步骤,所有的步骤完成才创建出这个对......