热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Spring事务失效了,怎么办?:spring事务在哪些情况下失效

本文主要分享【spring事务在哪些情况下失效】,技术文章【Spring事务失效了,怎么办?】为【_江南一点雨】投稿,如果你遇到关于SpringBoot相关问题,本文相关知识或能到你。sprin

本文主要分享【spring事务在哪些情况下失效】,技术文章【Spring 事务失效了,怎么办?】为【_江南一点雨】投稿,如果你遇到关于Spring Boot相关问题,本文相关知识或能到你。

spring事务在哪些情况下失效

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

这个问题比较典型,让我想到面试时有一个 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 动态代理?。

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 中要开启循环依赖才能实现),大家在实际开发中,还是要从设计上尽量避免这种问题。

好啦,这个问题搞明白了,那么事务失效这个问题,也不用我多说了吧!

本文《Spring 事务失效了,怎么办?》版权归_江南一点雨所有,引用Spring 事务失效了,怎么办?需遵循CC 4.0 BY-SA版权协议。


推荐阅读
  • 在使用SSH框架进行项目开发时,经常会遇到一些常见的问题。例如,在Spring配置文件中配置AOP事务声明后,进行单元测试时可能会出现“No Hibernate Session bound to thread”的错误。本文将详细探讨这一问题的原因,并提供有效的解决方案,帮助开发者顺利解决此类问题。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 原文网址:https:www.cnblogs.comysoceanp7476379.html目录1、AOP什么?2、需求3、解决办法1:使用静态代理4 ... [详细]
  • com.hazelcast.config.MapConfig.isStatisticsEnabled()方法的使用及代码示例 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 第二十五天接口、多态
    1.java是面向对象的语言。设计模式:接口接口类是从java里衍生出来的,不是python原生支持的主要用于继承里多继承抽象类是python原生支持的主要用于继承里的单继承但是接 ... [详细]
  • 类加载机制是Java虚拟机运行时的重要组成部分。本文深入解析了类加载过程的第二阶段,详细阐述了从类被加载到虚拟机内存开始,直至其从内存中卸载的整个生命周期。这一过程中,类经历了加载(Loading)、验证(Verification)等多个关键步骤。通过具体的实例和代码示例,本文探讨了每个阶段的具体操作和潜在问题,帮助读者全面理解类加载机制的内部运作。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 在C#中开发MP3播放器时,我正在考虑如何高效存储元数据以便快速检索。选择合适的数据结构,如字典或数组,对于优化性能至关重要。字典能够提供快速的键值对查找,而数组则在连续存储和遍历方面表现优异。根据具体需求,合理选择数据结构将显著提升应用的响应速度和用户体验。 ... [详细]
author-avatar
张梦蒙4428
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有