关于spring:Spring-事务失效了怎么办

4次阅读

共计 2680 个字符,预计需要花费 7 分钟才能阅读完成。

这是小伙伴们在微信上问的一个问题:

这个问题比拟典型,让我想到面试时有一个 Spring 事务生效的问题,跟这个起因以及解决方案是截然不同的,因而,抽空整篇文章和小伙伴们分享下。

1. AOP 的原理

小伙伴们晓得,AOP 底层就是动静代理,动静代理有两种实现形式:

  • JDK 动静代理:利用拦截器(必须实现 InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来解决。举个例子,假如有一个接口 A,A 有一个实现类 B,当初要给 B 生成代理对象,那么实际上是给 A 接口主动生成了一个匿名实现类,并且在这个匿名实现类中调用到 B 中的办法。
  • CGLIB 动静代理:利用 ASM 框架,对代理对象类生成的 class 文件加载进来,通过批改其字节码生成子类来解决。举个例子,当初有一个类 A,A 没有接口,当初想给 A 生成一个代理对象,那么实际上是主动给 A 生成了一个子类,在这个子类中笼罩了 A 中的办法, 所以,小伙伴们要留神,A 类以及它里边的办法不能是 final 类型的,否则无奈生成代理

如果被代理的对象有接口,则能够应用 JDK 动静代理,没有接口就能够应用 CGLIB 动静代理。

在 Spring 中,默认状况下,如果被代理的对象有接口,就应用 JDK 动静代理,如果被代理的对象没有接口,则应用 CGLIB 动静代理。

在 Spring Boot 中,2.0 之前也跟 Spring 中的规定一样,2.0 之后则对立都应用 CGLIB 动静代理。

不过这些都是默认的规定,如果有接口,然而你又心愿应用 CGLIB 动静代理,通过批改配置,也都是能够实现的:

如果是 XML 配置,想应用 CGLIB 动静代理,能够按如下形式实现:

<aop:config proxy-target-class="true">
    <aop:pointcut id="pc1" expression="。。。"/>
    <aop:aspect ref="logAdvice">。。。</aop:aspect>
</aop:config>

如果是 Java 配置,想应用 CGLIB 动静代理,能够按如下形式实现:

@Component
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class LogAspect {}

当然,在新版 Spring Boot 我的项目中,有接口的类默认就是应用 CGLIB 动静代理的。然而此时如果有接口的类你又想应用 JDK 动静代理,那么能够通过如下配置:

spring.aop.proxy-target-class=false

对于 Spring Boot 中的 AOP 代理问题,能够参考去年松哥写的文章:[
Spring Boot 中的 AOP,到底是 JDK 动静代理还是 Cglib 动静代理?](https://mp.weixin.qq.com/s/V3…)。

2. 理论用的类

基于第一大节的解说,小伙伴们晓得,当你在我的项目中用到了 AOP 之后,其实你所以见到的类,并不是本来的类了。

松哥后面写了好几篇 AOP 相干的文章,如下:

  • 手把手教你玩多数据源动静切换!
  • Redis 做接口限流,一个注解的事!
  • 解决接口幂等性的两种常见计划 | 手把手教你
  • 数据权限,一个注解搞定!

尽管是解决不同的问题,然而有一个独特的点,那就是都是通过自定义注解 + AOP 解决问题的。

当初我就以手把手教你玩多数据源动静切换!为例,来和大家说说这里的动静代理到底是咋回事,没看过这篇文章的小伙伴能够先看下。

小伙伴们看下,我的 UserService 大抵上是上面这样:

@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    @DS("master")
    public Integer count() {return userMapper.getCount();
    }
}

小伙伴们看到,count() 办法上加了 @DS 注解,所以这个 count() 办法未来是要被主动代理的。换言之,当你在另外一个类中注入 UserService 的时候,其实不是这个 UserService,我 DEBUG 小伙伴们来看一下:

小伙伴们从图中能够看到,此时我注入的 UserService 并不是真正的 UserService,而是一个通过 CGLIB 动静代理为 UserService 生成的子类,这个子类里边的 count 办法大抵逻辑相似上面这样(其实就是 AOP 中的代码,具体小伙伴们能够参考 手把手教你玩多数据源动静切换!一文):

# 切换数据源
# 去数据库查问 count
# 清空 ThreadLocal 中的变量
# ...

然而,如果我的调用逻辑是这样呢:

@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    public Integer count2() {return count();
    }

    @DS("master")
    public Integer count() {return userMapper.getCount();
    }
}

小伙伴们来看,count2 办法,这个时候间接在 count2 办法中调用了 count 办法,当然,count2() 办法中的调用也能够写作 this.count();,这样看起来就更明确了,咱们调用 count 办法,应用的是以后对象,而以后对象是不蕴含代理对象中的代码的,咱们通过 DEBUG 来看下:

所以,当咱们在 count2 中间接调用 count 办法的时候,那么加在 count 办法上的注解就会生效。

3. 问题解决

这个问题存在于所有应用了 AOP 的中央,存在的起因第二大节曾经剖析的很分明了。

解决办法其实也有很多种,最为简略省事的一种,就是在以后类中注入代理对象,而后通过代理对象去调用其余办法,如下:

@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    UserService userService;

    public Integer count2() {return userService.count();
    }

    @Transactional
    @DS("master")
    public Integer count() {return userMapper.getCount();
    }

}

尽管问题解决了,不过这毕竟不是一个好的解决办法(因为本人中注入本人,在新版 Spring Boot 中要开启循环依赖能力实现),大家在理论开发中,还是要从设计上尽量避免这种问题。

好啦,这个问题搞明确了,那么事务生效这个问题,也不必我多说了吧!

正文完
 0