@Transcation失效原因

1 篇文章 0 订阅
1 篇文章 0 订阅

@Transcation失效原因

前言

最近入职新公司了,毕竟才毕业半年,做的都是一点简单任务,但是测试时候发现,某个接口调用异常居然事务没有回滚?我表示非常诧异,明明已经添加@EnableTransactionManagement注解(事务管理器),并且相应的service也添加了@Transcation,居然还是失效,所以开始探究原因。

认识一下@Transcation

@Transcation本身是spring容器一个注解,配置这个注解能够实现声明式事务。

其实大致上原理就是通过一个aop进行一个切面事务的管理,配合上threadlocal保存连接来进行事务的管理。这里我也不具体讲解原理了,有兴趣可以看看别人的博客。

统计一下常见的事务失效情况

1.注解抛出异常错误

@Transactional(rollbackFor = RuntimeException.class)
public Result<Book> saveBook(Book book) throws Exception {
    bookMapper.insert(book);
    if (!book.getName().equals("老人与海")){
        throw new Exception();
    }else {
        return ResultUtil.success(book);
    }
}

Transactional 只捕获RuntimeException和Error异常和子类异常,但是实际代码抛出的是Exception,所以事务不会回滚

解决方法

@Transactional(rollbackFor = Exception.class)

2.事务的传播行为设置错误

 @Transactional(propagation = Propagation.NEVER)
 public Result<Book> saveBook(Book book) throws Exception {
     bookMapper.insert(book);
     if (!book.getName().equals("老人与海")){
    	 throw new Exception();
     }else {
    	 return ResultUtil.success(book);
     }
 }

propagation传播行为设置了事务不生效,NOT_SUPPORTED ,NEVER,MANDATORY 3种都会让事务不生效

3.同类调用同类其他方法

 public Result<Book> saveBook1(Book book) {
     this.saveBook2(book);
     return ResultUtil.success(book);
 }

@Transactional
public void saveBook2(Book book){
    bookMapper.insert(book);
    int a = 1/0;
}

saveBook1 普通方法去调用有事务的方法,saveBook2是不会回滚的,以为他本身类没经过springAop代理,所以会回滚失败。

解决方法

他没经过代理,我们第一个时间想到的就是通过spring容器去获取它的带来

 public Result<Book> saveBook1(Book book) {
     ((BookServiceImpl)(AopContext.currentProxy())).saveBook2(book);
     return ResultUtil.success(book);
}

然后会发现报错

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

然后我们可以在配置上加入@EnableAspectJAutoProxy(exposeProxy = true)这个注解,主要通过threadlocal保存用户当前的代理类,然后就可以完成了

@Transactional
public Result<Book> saveBook1(Book book) {
    bookMapper.insert(book);
    this.saveBook2(book);
    return ResultUtil.success(book);
}

@Transactional
public void saveBook2(Book book){
    bookMapper.insert(book);
    int a = 1/0;
}

如果saveBook1方法上有事务调用,去调用saveBook2方法是可以实现事务的;

总结:

如果方法A不带事务,方法B/C带有事务,事务是不会生效的

如果方法A带事务,方法B/C带有事务,事务是会生效的

4.内部消化异常

上面提到是通过aop去捕获特定异常,从而进行事务回滚,如果自己内部消化了会怎么样?

 @Transactional
 public Result<Book> saveBook1(Book book) {
 	try {
 		bookMapper.insert(book);
 		int a = 1/0;
    }catch (Exception e){
 		e.printStackTrace();
    }
	return ResultUtil.success(book);
 }

答案是失效的,其实你也可以一步步跟进事务的源码,其实他也是捕获异常来进行决定是否事务回滚,如果你内部已经消化了,人家就捕捉不到了,进而肯定失效了。

5.方法不是public

暂时还不知道,有待探究

关于自己碰到的问题

先上代码(因为是公司内部代码,就自己随便模拟一下)

@Transactional
public Result<Book> saveBook1(Book book) {

    bookMapper.insert(book);
    int a = 1/0;
    book.setName("老人与海");
    bookMapper.insert(book);
    return ResultUtil.success(book);
}

中间a=1/0 模拟一个我代码写错的原因

但是答案是事务不会滚,所以我就跟着异常走了一波

首先是
MethodProxy类中的invoke方法然后
CglibAopProxy类中的proceed()方法然后
就是下面一个自定义的aop切面方法????
这个aop主要是统计下接口时间记录日志收集的 
 
@Aspect
@Component
@Slf4j
public class LogAspect {

    public final static long time_out = 3000L;

    // 配置织入点
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void logPointCut()
    {
    }


    @Around("logPointCut()")
    protected Result around(ProceedingJoinPoint point) throws Throwable
    {
        // 获取类名
        String className = point.getTarget().getClass().getSimpleName();
        // 获取方法名
        String methodName = point.getSignature().getName();
        try {
            StopWatch sw = new StopWatch("test");
            sw.start();
            Result proceed = (Result)point.proceed();
            sw.stop();
            long totalTimeMillis = sw.getTotalTimeMillis();
            if (time_out>totalTimeMillis){
                log.warn("{}类名的{}方法接口超时",className,methodName);
            }
            return proceed;
        }catch (Exception e){
            log.error("{}类名的{}方法接口超时",className,methodName);
            return ResultUtil.error(999,"接口超时");
        }

    }

}



然后我算是明白了,他先去走这个aop的方法,然后直接返回了???

其实这个时候大家都猜到了,多个aop之间如何进行一个先后顺序,我来测一个例子吧

2021-04-26 20:48:00.989  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1       : AOP1前置
2021-04-26 20:48:00.989  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2       : AOP2前置
2021-04-26 20:48:03.172  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2       : AOP2后置
2021-04-26 20:48:03.172  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect2       : AOP2 finally块后置
2021-04-26 20:48:03.172  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1       : AOP1后置
2021-04-26 20:48:03.172  INFO 8440 --- [nio-8080-exec-2] com.example.demo.config.LogAspect1       : AOP1  finally块后置

没什么好说了 就是 类似一个同心圆图片1

那么怎么保证AOP执行先后顺序呢,如果在没加的前提下,我猜测可能是根据名字来进行先后(有待考证),如何想加的话就要通过@Order设置大小了,order越小越线执行

而我那上面那个问题我猜测是由于AOP种事务的顺序和AOP日志输出的顺序先后搞混了(虽然事务的order是最大的,但是由于AOP日志输出也没设置order),因此我这只AOP日志输出的order为1,然后发现居然可以了。虽然还没去验证是不是这个原因,但是至少先解决后在去深入了解原因!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值