@Transactional你知道多少?

时间:2024-11-09 12:03:56

本文转载自微信公众号“猪杂汤饭”

在Spring中操作事务可以使用声明式和编程式者两种方式。使用@Transactional是声明式事务,声明式事务使用动态代理,可以降低代码侵入性,让代码更加优雅。以下所讲的事务处理均是声明式事务处理,而非编程式事务处理。

加菲猫、魔鬼筋肉人、魔鬼筋肉人加强版都是经过Spring通过动态代理生成的代理类,而断水流就是我们自己写的类,做番茄炒蛋就是调用方法,加香菜可以看成加事务处理(或其他增强处理)。Spring不会直接让断水流去做番茄炒蛋,而是使用动态代理将我们写的断水流加强成魔鬼筋肉人或魔鬼筋肉人加强版。当魔鬼筋肉人或魔鬼筋肉人加强版去调用其它对象的方法,那么他们也就成为了加菲猫(递归的思想)

在CglibAopProxy$DynamicAdvisedInterceptor#intercept中存在下面代码

//如果目标方法无额外的切面处理(比如@Transactional),即不加香菜
//则chain的size=0。如果存在一个或多个切面处理,即要加香菜
//则size>=1,在下面的if语句就会出现不同的执行路径,这就决定
//是否要将魔鬼筋肉人升级为加强版。
List<Object> chain = (method, targetClass);
Object retVal;
if (() && (())) {
   Object[] argsToUse = (method, args);
  // 调用链为空,直接调用切点的方法
  retVal = (target, argsToUse);
}else {
    // 创建 CglibMethodInvocation 对象
    // 将拦截器链封装到该对象,以便使其 proceed 方法执行时,进行拦截处理
   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}

 

好了,扯了那么多,进入正题。

1、未添加@Transactional注解的方法A里面调用同一个对象中使用了@Transactional注解的方法B,则方法B中的事务失效,除非方法A中也添加了事务处理,而不同对象间的方法调用不受此约束 。Spring使用动态代理调用方法A时,未发现方法A有声明式事务处理,则不采取事务控制,尽管方法B使用@Transactional注解。打个比方,你一开始要不加香菜的番茄炒蛋,那么煮番茄炒蛋时就算你说你要用猛火煮那我也不给你猛火,除非你一开始说要加香菜。

class 断水流大师兄{
    public void 煮番茄炒蛋() {
        打火();
    }
    
     //加猛火失败
    @加猛火煮(代指事务处理)
    public void 打火() {
        //do something interesting;
    }
}
class 断水流大师兄{
    @加香菜(代指事务处理)
    public void 煮番茄炒蛋() {
        打火();
    }
    
    //加猛火成功
    @加猛火煮(代指事务处理)
    public void 打火() {
        //do something interesting;
    }
}

为什么同一个对象中的方法间调用会受此约束,而不同对象间的方法调用又不会?

因为方法在调用本对象中的方法时无须再创建代理对象。当一个对象中的方法要调用另外一个对象中方法,就要创建代理对象,即要被CglibAopProxy$DynamicAdvisedInterceptor#intercept处理。

 

2、在使用@Transactional时,在发生异常时默认仅对RuntimeExceptionError发生回滚,如果要对其他异常进行事务回滚,务必显示声明!

之前在写的代码的时候,组长问我为什么要在@Transactional加rollbackFor属性,我说当抛出SQLException时就回滚啊!组长说@Transactional默认会对所有异常回滚!!!

我半信半疑去看了源码,发现@Transactional默认仅对RuntimeException和 Error发生回滚

If no rules are relevant to the exception,it will be treated like DefaultTransactionAttribute(rolling back on RuntimeException and Error but not on checked exceptions).

@Override
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

 

拓展:

1、对于之前写的《分布式事务-解决单一事务中涉及多数据源的问题》提到的分布式事务问题是针对声明式事务来讲的!如果采用编程式事务,那完全不用考虑什么乱七八糟的分布式事务问题,你都可以实现与分布式事务一样的功能,但你要明白,当你的一个操作中涉及多少个数据源,就要弄多少个事务管理器,即事务管理器和数据源是一对一的关系,代码就会像下面代码一样是十分臃肿,且代码拓展性极差!!!

public void doBiz(){
    try{
        ();
        ();
    }catch(Exception e){
        ();
        ();
    }finally{
        if(noException){
    	();
    	(); 
       }
    }
}

 

2、分享一个开发过程碰到的问题,非常interesting!

事务的配置和代码完全OK,但事务却一直失效。打断点看执行路线也没毛病。然后想了想,会不会数据库引擎的问题,但看了下当前库的引擎是InnoDB,接下来就是使劲地想,哪里出问题了?哪里出问题了?几个小时过去了,后来发现操作的表的引擎是MyISAM,此时只能说一句RLGL!库的引擎都是InnoDB,建表的人当初是怎么想的?

众所周知,MySQL的引擎中只有InnoDB支持事务,而MyISAM不支持事务,更不支持XA协议。