一、注意事项
1、不要在接口上声明@Transactional,而要在具体类的方法上使用 @Transactional 注解,否则注解可能无效。
2、不要图省事,将@Transactional放置在类级的声明中,放在类声明,会使得所有方法都有事务。故@Transactional应该放在方法级别,不需要使用事务的方法,就不要放置事务,比如查询方法。否则对性能是有影响的。
3、@Transactional 的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的 。
4、用REQUIRES_NEW的时候,发现没有起作用
分析了一下,原因是A方法(有事务)调用B方法(要独立新事务),如果两个方法写在同一个类里,spring的事务会只处理能同一个。
解决方案1:需要将两个方法分别写在不同的类里。
解决方案2:方法写在同一个类里,但调用B方法的时候,将service自己注入自己,用这个注入对象来调用B方法。
5、使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。
6、默认只有RuntimeExcetion会触发回滚,经验证明确实如此,所以rollbackFor可以自行设置如下:rollbackFor = {Exception.class},当然具体业务具体处理,可能有的业务抛出的某些异常并不需要触发回滚,所以此时应该细化处理异常。
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor={/*自定义Exception,可以是多个*/ IOException.class})
上面的这个rollbackFor配置,可以理解为:当配置这个事务的方法内返回了IOException,则会触发回滚操作。
7、MySQL数据库表引擎应为InnoDB,否则不支持事务。
二、事务失效的场景
1、 查看数据库中使用的表是否都是 InnoDB
的引擎,有些表为了追求写入的性能,比如记录日志的表,会将引擎改为 MyiSAM
,如果数据库引擎不支持,那么添加的事务也就不会生效。
2、 事务要加在 public
方法上,否则事务将不会起作用,如果要用在非 public
方法上,事务不会生效。如果方法被 final
修饰, spring
事务底层使用了 aop
, 也就是通过jdk
动态代理或者cglib
,生成了代理类,而被final
或者 staitc
的方法,是不能重写或者代理,因此事务就不会生效。
3、 在事务内部调用本类的另一个事务方法,那么被调用的一方事务不会生效。因为spring
的事务是使用动态代理生效的,本类内部的调用相当于是this.method
的调用,没有经过spring
代理所以事务不会生效。如果非要在类内部进行调用,需要按照如下图操作,updateUserInfo
调用方法 modifyUserInfo
,需要使用 AopContext
获取当前对象进行调用执行。同时还要设置 proxyTargetClass= true
,改值默认为 false
。
4、 注意事务设置的隔离级别,如果项目中配置了多个数据源,就会有多个事务管理器,在执行事务的时候需要指定其事务管理器。事务的传播属性和隔离级别是默认的,超时时间默认是 -1
,不设置超时时间。异常回滚和异常不回滚需要根据业务的情况进行设置。
5、 方法内部不能将异常 try-catch
,如果方法内部都把异常给处理了,那么事务捕捉不到异常信息,事务自然不会回滚,也就不会生效。方法内部把异常信息给处理了,就不能被事务Aop
拦截进行处理,自然就会进行回滚操作。
6、 建议事务 @Transactional
注解需要在具体的实现类上进行标注,而不是在接口层标注。注解是不能继承的,所以只有在基于接口的代理(PROXY)时事务才生效。如果正在使用基于类(AspectJ)代理时,那么注解不会被基于类的代理识别,对象也不会被事务代理所包装,因此事务是不能生效的。
7、 多线程调用,在项目开发过程中多线程的应用场景是很多的,在多线程的场景下,主线程所在的 Context
和多线程执行下的方法不在同一个上下文,我们知道 spring
的事务是通过数据库连接来实现的,数据库连接是存放在一个 ThreadLocal
中的,只有在同一个线程上下文下,事务的操作才能生效。
8、在事务方法中加锁,可能会导致锁失效
无论是Java自带的锁,还是分布式锁,都有可能出现没锁住的情况
原因是解锁先于事务提交,一旦锁释放后其它线程就可以获取到锁了,由于事务还没提交,所以新线程读到的还是旧数据(跟前一个线程读取到的数据是一样的),这就相当于多个线程做了一模一样的事情了。
正确的做法是:要么别加事务,要么把锁加在事务方法外面
9、调用内部(同一个类中)方法的话,事务不会生效
10、事务方法被声明成private或者final,导致事务不生效
三、事务即便生效也不一定能回滚
通过 AOP 实现事务处理可以理解为,使用 try…catch…来包裹标记了 @Transactional 注解的方法,当方法出现了异常并且满足一定条件的时候,在 catch 里面我们可以设置事务回滚,没有异常则直接提交事务。
这个的一定条件满足以下两条:
-
只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚
-
默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务。