Spring事务哪些情况下会失效

 写在前面

不论是工作中还是面试的时候,spring事务失效的问题一直是经常碰到的问题,其实会有很多情况下spring事务会失效,所以我们平时最好反复确认程序中的事务是否真的生效。

事务的本质是Spring AOP通过生成代理类,并重写其中的public并且非final,static方法,并对目标方法做了事务方面的增强来实现的

 方法访问权限

@Transactional(rollbackFor = Exception.class)
private Response doTheTransactionStuff(TransactionEntity entity){

    //do something

}

这种情况下spring的事务是没法生效的。直接原因是因为spring中规定了事务生效的方法必须是public,否则返回null,即事物不生效

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) {
    // 不允许非public的方法
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }

    //其它代码
  }


protected boolean allowPublicMethodsOnly() {
    return false;
  }

spring中源码不允许有非public的方法支持事务。有意思的事,spring为什么要预留一个allowPublicMethodsOnly的方法呢?

根本原因是因为Spring AOP是通过代理来实现事务管理的。代理对象会包裹目标对象并对其方法进行增强,以实现事务的控制。而代理对象只能代理目标对象的public方法,因为代理对象的方法是作为目标对象的替代者,代理对象只能对外暴露目标对象中的public方法。因此,只有public方法才能被代理对象截获并添加事务管理的逻辑。

spring aop----->代理----->代理只能暴露目标对象的public方法

final修饰的方法

@Transactional(rollbackFor = Exception.class)
public final Response doTheTransactionStuff(TransactionEntity entity){

    //do something

}

上述方法被final修饰,同样也无法使得事务生效,即使是public方法也不行。

原因是Spring的事务机制无法在final方法上生效是因为final方法是无法被子类重写的,而Spring AOP是通过代理来实现事务管理的。代理对象会继承目标对象的方法,并通过对目标对象方法的增强来实现事务的控制。然而,由于final方法无法被子类重写,代理对象无法对final方法进行增强。

spring aop----->代理----->代理需要继承并重写目标方法来做事务增强----->final方法没法被重写

方法内部调用

@Transactional(rollbackFor = Exception.class)
public Response doTheTransactionStuff(TransactionEntity entity){

    //do something

}

public Response entryMethod(TransactionEntity entity){

    return doTheTransactionStuff(entity);

}

方法doTheTransactionStuff被entryMethod调用,在doTheTransactionStuff上的事务没法生效,是因为上述的entryMethod方法实际上是直接通过this来调用了,也就是不通过代理类的那个增强方法,而是被代理对象的初始方法。

public Response entryMethod(TransactionEntity entity){

    return this.doTheTransactionStuff(entity);

}

基于上面说的原理,我们可以通过注入一个自己的对象来调用方法就行

@Service
public class UserService{

    @Resource
    private UserService otherInstance;

    @Transactional(rollbackFor = Exception.class)
    public Response doTheTransactionStuff(TransactionEntity entity){

        //do something

    }

    public Response entryMethod(TransactionEntity entity){

        return otherInstance.doTheTransactionStuff(entity);

    }
}

只要不通过this.doTheTransactionStuff来调用,这么做看上去有点hack,不建议这么做

这个解法很适合解释为什么spring解决循环依赖需要三级缓存,而不是二级缓存

简单解释一下就是这里注入的otherInstances实际不是UserService的对象,而是otherInstance的代理对象。

还有一种方法更加hack,通过((UserService)AopContext.currentProxy()).doTheTransactionStuff(entity),来显式的拿出来UserService的代理对象,也就是otherInstance的代理对象

未被spring容器管理的对象

包含了prototype类型的,没有被注入到容器的对象,比如没有添加@Component,@Service等注解,不展开了

多线程调用

在spring中每个事务都需要维护自己的数据库连接,不同事务间的数据库连接不一样,事务自然也就没法传递

private static final ThreadLocal> resources =

  new NamedThreadLocal<>("Transactional resources");

多线程调用代码如下,即使添加了join,明确知道了db操作结束了,但也不再事务管理范围内

@Service
public class UserService{

    @Resource
    private UserService otherInstance;

    @Transactional(rollbackFor = Exception.class)
    public Response doTheTransactionStuff(TransactionEntity entity){

        //do something

    }

    public Response entryMethod(TransactionEntity entity){
        Thread transactionThread = new Thread(() -> {
            otherInstance.doTheTransactionStuff(entity);
        });
        transactionThread.start();
        transactionThread.join();
        return new Response();

    }
}

表不支持事务或者未开启事务

前者不做解释,后者主要是在传统的spring项目中,那么需要applicationContext中配置事务相关参数。另外需要注意的是,pointcut切不到的位置也没法让事务生效。spring boot的项目在启动类加上@EnableTransactionManagement

   
 
 
     
 
 
     
        
     
 
 
 
     
     
 

你可能感兴趣的:(Spring,spring,事务不生效)