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<Map<Object, Object>> 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

   
<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> 
    <tx:attributes> 
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* property.hghargo.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

 

 

 

 

 

 

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢一点,细一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值