Skip to content

Spring事务什么场景下会失效

  1. 访问权限问题。访问权限是private、default或protected的话,spring则不会提供事务功能。只有public方法才会提供事务
  2. 方法用final修饰。
    1. 如果你看过spring事务的源码,可能会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
    2. 如果某个方法是static的,同样无法通过动态代理,变成事务方法
  3. 方法内部调用。在普通方法A中,调用事务方法B,因为这种方法直接调用了this对象的方法(面试有被问到过*2),没有走spring代理 分析及解决
  4. 未被spring管理
  5. 多线程调用
    • 不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务
    • spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接
    • 我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。
  6. 表不支持事务
  7. 未开启事务
  8. 错误的传播特性
  9. 自己吞了异常
  10. 手动抛了别的异常
  11. 自定义了回滚异常
  12. 嵌套事务回滚多了
  13. 大事务问题。解决方案可以使用TransactionTemplate编程式事务

针对第3点的分析及解决

Spring默认的是PROPAGATION_REQUIRED机制,如果方法A标注了注解@Transactional 是完全没问题的,执行的时候传播给方法B,因为方法A开启了事务,线程内的connection的属性autoCommit=false,并且执行到方法B时,事务传播依然是生效的,得到的还是方法A的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解@Transactional 时,是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了@Transactional。

Spring在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

具体解决

就是根据分析的原因来具体解决

  1. 把方法B抽离到另外一个ServiceC中去,并且在这个Service中注入ServiceC,使用ServiceC调用方法B
  2. 通过在方法内部获得当前类代理对象的方式,通过代理对象调用方法B
    1. 使用 ApplicationContext 上下文对象获取该对象
    2. 使用 AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true。
      Terminal window
      @EnableAspectJAutoProxy(exposeProxy = true)