Java的@Transactional事务回滚

2022-07-25 08:46:35 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

@Transactional 基本原理概述

在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。

处理Springboot下提交事务异常,数据库没有回滚的问题

Spring文档中说道,Spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。

什么是检查型异常和非检查型异常?

最简单的判断点有两个: 1、继承自runtimeException或error的是非检查型异常,而继承自exception的是检查型异常。 2、对非检查型异常可以不用捕获,而检查型异常必须用try语句块进行处理或者把异常交给上级方法处理,总之就是必须写代码处理它。所以必须service捕获异常,然后再次抛出,这样事务才能生效。

默认规则:

1、让检查型异常也回滚,@Transactional(rollbackFor=Exception.class),一般只需添加这个即可

2、让非检查型异常不回滚,@Transactional(notRollbackFor=RunTimeException.class)

3、不需要事务管理的(就是只是查询用)方法,@Transactional(propagation=Propagation.NOT_SUPPORTED),或者不添加

4、手动回滚,TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 例如:

代码语言:javascript复制
try {
        String name = (String) list.get(j - 1).get("name");
      } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return ResultUtil.error(500, "文件解析错误");
       }

你需要注意的事

1.@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能 2.Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。 3.当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,还是在具体的类上使用 @Transactional 注解比较好。 4.避免 Spring 的 AOP 的自调用问题:自调用就是方法A内调用本类的另一个加上事务注解的方法B时,方法B中对数据库的操作是不带事务的。

Spring AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。 失效原因:

方法one方法two都是public的:

classA中 ,任意要调用classB的方法,是通过spring代理的方式,那么spring的注解才会生效 classA中,方法one 调用同class内的方法two,即this调用,spring注解不会生效(例如@Cachable,@Transaction)

解决方法

方案一:使用AspectJ代理

代码语言:javascript复制
@Service
public class OrderService { 
   
    private void insert() { 
   
        insertOrder();
    }
@Transactional
    public void insertOrder() { 
   
        //insert log info
        //insertOrder
        //updateAccount
       }
}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,可以使用 AspectJ取代 Spring AOP 代理,但现在有更好的解决方法。

方案二:利用AopContext.currentProxy()方法获得代理

方法的意思是尝试返回当前AOP代理。这种做法非常简洁,但是在默认情况下是不起作用的!因为AopContext中拿不到currentProxy,会报空指针。需要一些额外的配置,但不能对所有的注解拦截都有效,这是因为这些注解不是用的AspectJ代理,如果是@Transactional事务注解的话, 则是生效的,具体细节要翻源码了,这里不推荐使用。

方案三:通过ApplicationContext来获得动态代理对象(推荐)

代码语言:javascript复制
@Component
public class AsyncService implements ApplicationContextAware { 
   

    private ApplicationContext applicationContext;

    public void async1() { 
   
        System.out.println("1:"   Thread.currentThread().getName());
        // 使用AppicationContext来获得动态代理的bean,然后再执行你调用的方法
        this.applicationContext.getBean(AsyncService.class).async2();
    }

    @Async
    public void async2() { 
   
        System.out.println("2:"   Thread.currentThread().getName());
    }

    // 注入ApplicationContext
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
   
        this.applicationContext = applicationContext;
    }
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/127133.html原文链接:https://javaforall.cn

0 人点赞