Spring事务失效场景

159 阅读4分钟

同一个类的不同方法之间的调用(以方法A调用方法B为例)

如果方法A开启事务,则事务都可以生效

因为此时方法A走的是代理对象,所以事务会生效。并且方法B的默认传播机制是REQUIRED,即方法B会加入到方法A的事务中,他们便处于同一个事务当中,双方都会进行回滚。

@Transactional(rollbackFor = Exception.class)
@Override
public void methodA() {
    // ...业务操作
    methodB();
    // 当A异常时,双方都会进行回滚。
    int i = 10 / 0;
}
​
// 即使B没有事务,也会进行回滚。
@Transactional(rollbackFor = Exception.class)
@Override
public void methodB() {
    // ...业务操作
    // 同理,B异常时,A也会回滚,因为处于同一个事务中
    // int i = 10 / 0;
}
如果方法A没有事务,则方法B无论怎样,事务都会失效

主要原因就是Spring的声明式事务是通过AOP代理实现的,所以必须由代理类调用对应的方法才能进行增强生效。由于方法A没有事务,则不会被代理,则其调用方法B时就是通过this自身来调用本类的方法,而不是代理类来调用。

@Override
public void methodA() {
    // ...业务操作
    methodB();
    // 当A异常时,没有开启事务,不是走代理类,所以A和B都不会回滚。
    int i = 10 / 0;
}
​
@Transactional(rollbackFor = Exception.class)
@Override
public void methodB() {
    // ...业务操作
    // 同理,B异常时,因为本身不是代理类进行调用,所以也不会回滚。
    // int i = 10 / 0;
}
如何解决上述的问题呢

方法一:既然是同类操作引起的,则将方法A和方法B进行抽离即可解决,这样就能让代理类去进行调用。

@Service
public class ServiceA {
    @Resource
    private ServiceB serviceB;
​
    @Override
    public void methodA() {
        // ...业务操作
        serviceB.methodB();
    }
}   
​
@Service
public class ServiceB {
​
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void methodB() {
        // ...业务操作
        // 当这里发生异常时,B会进行回滚。
        int i = 10 / 0;
    }
} 

方法二:不要让他进行this调用,通过代理类调用即可。

@Service 
public class ServiceA {
    @Resource
    private ServiceA serviceA;
   
    @Override
    public void methodA() {
        // ...业务操作
        serviceA.methodB();
    }
   
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void methodB() {
        // ...业务操作
        // 当B发生异常时,因为通过代理类进行调用,所以自身会进行回滚。
        int i = 10 / 0;
    }
}

方法三:也是跟方法二类似,不过是直接从IOC容器获取bean,这样也可以走代理。

@Service 
public class ServiceA {
    @Resource
    private ServiceA serviceA;
​
    @Resource
    private ApplicationContext applicationContext;
   
    @Override
    public void methodA() {
        // ...业务操作
        serviceA.methodB();
    }
   
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void methodB() {
        // ...业务操作
        // 当B发生异常时,因为通过代理类进行调用,所以自身会进行回滚。
        int i = 10 / 0;
    }
}

不同类的不同方法之间的调用(以方法A调用方法B为例)

如果方法A开启事务,则事务都可以生效

因为方法A和方法B都会被代理,同时根据默认的传播机制,此时方法A和B处于同一个事务中,只要A或者B回滚,他们都会进行回滚。

@Service
public class ServiceA {
    @Resource
    private ServiceB serviceB;
​
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void methodA() {
        // ...业务操作
        serviceB.methodB();
        // 当这里发生异常时,A和B都会回滚。
        int i = 10 / 0;
    }
}   
​
@Service
public class ServiceB {
​
    // 即使B没有事务,也会进行回滚。
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void methodB() {
        // ...业务操作
        // 同理,当只有B异常时,A和B都会回滚。因为处于同一个事务中。
        //int i = 10 / 0;
    }
} 
如果方法A没有事务,则方法B只有自身发生异常时才会回滚
@Service
public class ServiceA {
   @Resource
   private ServiceB serviceB;
​
   @Override
   public void methodA() {
       // ...业务操作
       serviceB.methodB();
       // 当这里发生异常时,A和B都不会回滚。
       int i = 10 / 0;
   }
}   
​
@Service
public class ServiceB {
​
   @Transactional(rollbackFor = Exception.class)
   @Override
   public void methodB() {
       // ...业务操作
       // 只有B自己发生异常时,才会自行回滚。
       //int i = 10 / 0;
   }
} 

补充说明

以上仅在单体架构中有效,实际的微服务场景,涉及到跨服务间调用的时候,就没办法生效,此时必须引入分布式事务来解决。另外其他可能导致@Transaction失效的场景还有:

  • 方法是非public或者final的,事务会失效。
  • 传播机制设置成非事务运行时也会失效。
  • 数据库引擎不支持事务。
  • 异常被捕获时,事务也会失效。
  • 未配置事务管理器或者没有开启事务管理。
  • 没有设置rollbackFor=Exception.class,默认只会处理RuntimeException。