spring事务失效之动态代理

Spring提供了数据库事务管理,只需要在含有数据库层操作的方法或类上使用注解@Transactional,Spring会自动帮我们管理数据库事务。比如当数据库操作逻辑执行发生异常后,Spring会将当前的事务回滚。

当我们在使用@Transactiona完成Spring 事务管理的时候,有时会出现由于某些细节没有掌握、使用不当,从而导致Spring没有实现事务管理功能。导致Spring事务失效的场景有很多,今天我们只是从动态代理的角度去探究导致Spring事务失效的原因。

Spring使用注解@Transactional变能够完成事务管理,其功能是基于Spring AOP实现的。而Spring AOP又是基于动态代理实现的。所以可以站在动态代理的角度聊一聊Spring事务失效的原因。

在Java中实现动态代理的方式,有JDK动态代理和Cglib动态代理。两者之间的实现方式是不同的,但是思想是一致的,即通过构建一个对象的子类,从而将子类作为该对象的代理,在子类中实现增强的逻辑。JDK动态代理是面向接口的,通过实现接口(implements)来创建子类。Cglib是面向类的,通过继承(extends)来创建子类。
 

下面通过简单的代码来模拟Spring事务管理的代理类。当我们在一个方法上标注@Transactional,Spring会使用动态代理,为该类生成一个代理对象,然后为标注了@Transactional注解的方法实现增强。怎么实现增强呢,即在该方法调用前后进行处理。代码如下:

在类UserOperation中进行数据库的增删改查操作。其中insertUser方法使用@Transactional注解,otherMethod没有使用注解。

public class UserOperation {
 
    @Transactional
    public void insertUser() {
        // 在这里进行用户新增的逻辑处理
    }
 
    public void otherMethod() {
        // 其他方法逻辑处理
    }
    
}
Spring为UserOperation会生成一个代理对象,可以理解成如下形式:

代理对象通过继承UserOperation类,这样代理对象将也会持有UserOperation中的方法。在代理对象中重写insertUser方法,在UserOperation对象调用实际的insertUser方法前后实现开启事务,正确执行完提交事务,有异常时回滚事务的逻辑。

public class UserOperationProxy extends UserOperation {
 
    @Override
    public void insertUser() {
        // 开启事务
        try {
            super.insertUser();
            // 提交事务
        } catch (Exception e) {
            // 回滚事务
        }
        
    }
}
上面的代理对象的内容只是为了方便理解简单的写了下,实际的内容要比这多很多。而且在代理对象中并不是直接调用被代理对象的方法,而是通过一个Handler进行转发,在Handler中持有被代理对象的引用,通过反射去调用被代理对象的方法以及调用前后逻辑的增强。

聊了那么多动态代理,现在开始说说Spring事务失效的场景。

(1)通过手动new对象去调用方法。

当你获取的对象不是从Spring容器中获取(比如使用@Autowired自动注入),而是通过new关键字创建的对象。此时当使用该对象调用注解方法的时候,Spring事务是失效的。Spring事务管理是通过代理对象去实现的,所以只有通过代理对象去调用方法才能生效。

(2)私有方法

在上面说过,Spring事务管理本质上是通过动态代理实现的,而动态代理无非就两种方式,实现接口或继承类。在Java中会存在方法可见性的问题,当方法使用private修饰的时候,该方法仅当前类的内部可见。子类是不可见的。所以当继承该类的时候,子类是不可见该类的私有方法的,也就是代理对象是无法持有这个私有方法的。自然也就无法通过代理对象调用该方法了。

(3)rollbackFor没有指定的异常

通过rollbackFor指定了起作用的异常,当方法抛出此异常的时候,Spring会回滚事务。当抛出了之外的其他异常,Spring事务失效。通过代码的方式更好理解:

比如通过rollbackFor指定管理的异常有NullPointerException、ArithmeticException,可以理解成Spring为我们生成的代理对象如下,在try...catch中,只会捕获这两种异常,对于其他的异常会被抛出去,交给调用程序处理。

@Override
public void insertUser() {
    // 开启事务
    try {
        super.insertUser();
        // 提交事务
    } catch (NullPointerException e) {
        // 回滚事务
    } catch (ArithmeticException e) {
        // 回滚事务
    }
 
}
(4)方法没有抛出异常

当在注解方法中使用了try...catch捕获所有的异常,方法并没有将异常抛出,这样该方法的调用程序是无法感知到异常发生的,因为异常已经在方法内被捕获了。该方法的调用程序感知不到异常,也就是代理对象感知不到异常,代理对象就会认为方法正确执行了,就会将事务提交,然而实际并非无此。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值