当前位置:   article > 正文

Spring事务@Transactional注解原理及其失效的几种场景_@transactional 性能 影响

@transactional 性能 影响

目录

Spring 事务管理类型

一、声明式事务

1.1 基于@Transactional注解

1.2@Transactional实现原理

二、事务的隔离级别

如何指定隔离级别

三、事务传播行为

支持当前事务的情况:

不支持当前事务的情况:

其他情况:

如何指定事务传播行为

四、Spring事务回滚规则

五、spring事务失效

六、常见事务失效实用的场景

1.1当事务在调用方法方法上,且表操作也是在该方法中

1.2当事务没有加调用该方法上,而是加在被调用方法上,事务失效

1.3但事务加在调用方法上,被调用方法不加事务

1.4 使用try...catch...捕获被调用方法异常,异常出现在调用方法上,被调用方法不开启事务

1.5,使用try...catch...捕获异常,调用方法开启了事务,异常出现在被调用方法上且被调用方法上开启事务

1.6 使用try...catch...捕获异常,异常出现在被调用方法上 ,被调用方法上不开启事务


Spring 事务管理类型

Spring 事务管理分为编程式和声明式两种。编程式事务编程式事务编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。

一、声明式事务

1.1 基于@Transactional注解

@Transactional实质是使用了JDBC的事务来进行事务控制的 ,基于Spring的动态代理的机制

1.2@Transactional实现原理

A、事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。

B、在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)

C、事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

二、事务的隔离级别

1. DEFAULT(默认):使用底层数据源的默认隔离级别。

2. READ_UNCOMMITTED(读取未提交数据):事务可以读取未提交的更改,可能导致脏读(读取未提交的数据)问题。

3. READ_COMMITTED(读取已提交数据):事务只能读取已提交的更改,可以避免脏读问题,但仍可能出现不可重复读(读取的数据发生变化)和幻读(读取到新增的数据)问题。

4. REPEATABLE_READ(可重复读):事务可以重复读取之前读取的数据,可以避免脏读和不可重复读问题,但仍可能出现幻读问题。行锁

5. SERIALIZABLE(串行化):事务在完全隔离的情况下运行,可以避免脏读、不可重复读和幻读问题,但可能导致性能问题,因为事务必须完全串行化执行。表锁

如何指定隔离级别

  1. //可以在@Transactional注解中指定隔离级别
  2. @Override
  3. @Transactional(isolation = Isolation.DEFAULT)
  4. public ResponseResult saveInfo(UserReq userReq) {
  5. log.info("注解中指定隔离级别");
  6. }

也可以在配置文件中配置全局的默认隔离级别

spring.datasource.hikari.isolation=READ_COMMITTED

三、事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

支持当前事务的情况:

1、 REQUIRED(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

2、 SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。

3、MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。

不支持当前事务的情况:

4、REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。

5、 NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

6、NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。

其他情况:

7、NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

如何指定事务传播行为

  1. //可以在@Transactional注解中指定事务传播行为
  2. @Override
  3. @Transactional(propagation = Propagation.REQUIRED)
  4. public ResponseResult saveInfo(UserReq userReq) {
  5. log.info("注解中指定隔离级别");
  6. }

四、Spring事务回滚规则

指Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。

Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。 用Spring事务管理器,由Spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .

如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

五、spring事务失效

A. 如果数据库不支持事务,则失效 B. Service类没有被Spring管理

比如类上没有标注@Service注解,实例没有加载到Spring IOC容器中,就会造成类中方法的事务在Spring中失效;

C. 内部调用

例如:如果同一个类中的两个方法分别为A和B,方法A上没有添加事务注解,方法B上添加了 @Transactional事务注解,方法A调用方法B,则方法B的事务会失效;

D. 使用默认的事务处理方式

E. 事务只能应用于 public 方法,这是由 Spring AOP 的本质决定的。在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常;

F. 方法的事务传播类型不支持事务

如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。

事务传播类型为NOT_SUPPORTED,不支持事务,则相应方法的事务会在Spring中失效。

G.不正确的捕获异常

使用try...catch...捕获异常

六、常见事务失效实用的场景

1.1当事务在调用方法方法上,且表操作也是在该方法中

  1. @Override
  2. @Transactional
  3. public ResponseResult relieveInfo(UserRelieveReq userRelieveReq) {
  4. userMapper.updateTestById(userId);
  5. schoolUserMapper.updateTestById(userId,schoolId);
  6. //异常
  7. if(true){
  8. throw new CustomException("事务回滚");
  9. }
  10. return ResponseResult.success("更改成功")
  11. }

分析总结:

开启了事务后,事务生效,当返回成功之后数据才会插入数据库中,如果在此期间抛了异常,数据库中数据无变动。

1.2当事务没有加调用该方法上,而是加在被调用方法上,事务失效

声明事务加在SchoolService层方法上schoolRelieve(此方法进行更改数据库操作)

userService层relieveInfo方法做逻辑判断,不进行表数据插入或是更改(不加声明事务)

  1. @Service
  2. @Slf4j
  3. public class SaoDeSengServiceImpl extends ServiceImpl<SaoDeSengMapper, SaoDeSeng> implements SaoDeSengService {
  4. @Autowired
  5. private SaoSchoolService schoolService;
  6. @Override
  7. // @Transactional
  8. public ResponseResult relieve(SaoDeSengRelieveReq userRelieveReq) {
  9. //获取当前用户ID
  10. Long currentUserId = getCurrentUserId();
  11. SaoDeSeng user = (SaoDeSeng) selectUserById(currentUserId).getData();
  12. //获取审批记录
  13. ResponseResult<SchoolCertificationInfoResp> schoolCertInfoResp = schoolService.schoolCertInfo();
  14. if (schoolCertInfoResp.getData().getSscId() == null) {
  15. throw new CustomException("学校认证数据为空,解绑失败");
  16. }
  17. schoolService.schoolRelieve(SaoSchool.getSchoolId(), currentUserId, schoolCertInfoResp.getData().getSscId());
  18. //异常位置
  19. if(true){
  20. throw new CustomException("事务回滚");
  21. }
  22. return ResponseResult.success("解绑成功");
  23. }
  24. }
  1. @Service
  2. @Slf4j
  3. public class SaoDeSengSchoolServiceImpl extends ServiceImpl<SaoDeSengSchoolMapper, SaoDeSengSchool> implements SaoDeSengSchoolService {
  4. @Autowired
  5. private SaoDeSengSchoolUserMapper schoolUserMapper;
  6. @Autowired
  7. private SaoDeSengSchoolCertificationMapper schoolCertMapper;
  8. @Autowired
  9. private OpenMapper openMapper;
  10. @Override
  11. @Transactional
  12. public void schoolRelieve(Long schoolId, Long userId, Long sscId) {
  13. LambdaQueryWrapper<SaoDeSengSchoolUser> idQuery = new LambdaQueryWrapper<>();
  14. idQuery.eq(SaoDeSengSchoolUser::getSchoolId, schoolId);
  15. idQuery.eq(SaoDeSengSchoolUser::getUserId, userId);
  16. schoolUserMapper.delete(idQuery);
  17. LambdaQueryWrapper<SaoDeSengSchoolCertification> sscIdQuery = new LambdaQueryWrapper<>();
  18. sscIdQuery.eq(SaoDeSengSchoolCertification::getSscId, sscId);
  19. schoolCertMapper.delete(sscIdQuery);
  20. LambdaQueryWrapper<Open> userIdQuery = new LambdaQueryWrapper<>();
  21. userIdQuery.eq(Open::getUserId, userId);
  22. userIdQuery.eq(Open::getDelFlag, DelFlagEnum.NORMAL.getvalue());
  23. openMapper.delete(userIdQuery);
  24. }
  25. }

分析

当事务没有加调用该方法上,而是加在被调用方法上,事务失效,数据不回滚  

1.3但事务加在调用方法上,被调用方法不加事务

具体代码如上1.2,只是事务开启位置做了变更

  1. // 被调用方法开启声明式事务@Transactional
  2. @Override
  3. @Transactional
  4. public ResponseResult relieveInfo(UserRelieveReq userRelieveReq) {}
  1. //被调用方法上关闭事务
  2. @Override
  3. // @Transactional
  4. public void schoolRelieve(Long schoolId, Long userId, Long sscId) {
  5. // 解绑学校用户表
  6. }

分析

当事务加调用该方法上,不加在被调用方法上,事务生效,数据回滚

1.4 使用try...catch...捕获被调用方法异常,异常出现在调用方法上,被调用方法不开启事务


代码变动位置如截图:

 分析:使用try...catch...捕获异常自己处理,导致异常不被抛出,从而事务失效,数据不回滚

1.5,使用try...catch...捕获异常,调用方法开启了事务,异常出现在被调用方法上且被调用方法上开启事务

分析:调用方法和被调用方法都开启了事务,当异常出现被调用方法里面,事务生效,出现回滚

1.6 使用try...catch...捕获异常,异常出现在被调用方法上 ,被调用方法上不开启事务

分析: 异常出现在被调用方法上 ,且被调用方法上不开启事务,事务失效,数据不回滚

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/525719
推荐阅读
相关标签
  

闽ICP备14008679号