@Transactional @Aysnc 循环依赖 事务同步问题

news/2024/2/28 17:07:48

文章目录

    • 学习链接
    • 场景
      • 最初版本
        • TestController
        • TestService
        • 问题
      • @Lazy版本 + 事务同步
        • 报错版本:TestService
        • @Lazy正常启动版本(有问题)
        • @Lazy + 注册事务同步

学习链接

@Async学习及循环依赖

场景

我们要做的事情很简单:

  1. 现在我们需要在一个业务方法中插入一个用户,
  2. 这个业务方法我们需要加上事务,
  3. 然后插入用户后,我们要异步的方式打印出数据库中所有存在的用户。

最初版本

我们的代码在最开始,可能是如下:

TestController

@RestController
@RequestMapping("test")
public class TestController {@Autowiredprivate TestService testService;@GetMapping("testTx")public String testTx() {testService.doTx();return "ok";}}

TestService

@Slf4j
@Service
@EnableAsync // 开启异步
@EnableTransactionManagement // 开启事务
public class TestService {@Autowiredprivate UserService userService;@Transactionalpublic void doTx(){log.info("-----------------doTx-----------------" + this.getClass());User user = new User();user.setNickname(RandomStringUtils.randomAlphabetic(5));userService.save(user); // 插入用户log.info("插入用户:{}" , user);printUserList(); // 我们希望的是异步打印所有的用户log.info("-----------------doTx-----------------");try {Thread.sleep(3000); // 这里还需要干其它的活,反正就是这里不确定,万一它就卡在这里了呢, 就模拟这个情况} catch (InterruptedException e) {e.printStackTrace();}}@Asyncpublic void printUserList() {log.info("-----------------printUserList-----------------" + this.getClass());List<User> list = userService.list(new QueryWrapper<User>());for (User user1 : list) {log.info("printUser:  {}",user1);}log.info("-----------------printUserList-----------------");}}

问题

我们访问上面的这个接口:http://localhost:8085/web-api/test/testTx,输出如下的日志。

发现问题:可以看到 保存用户 和 异步打印所有用户 用的是同一个线程,说好的异步没有了,为什么没有异步了呢?可以看到我们使用的仍然是TestService而不是代理对象,所以直接就是调用的就是TestService类的方法,而异步注解是基于代理的(但不是基于自动代理创建器的),所以就有问题了。

在这里插入图片描述

@Lazy版本 + 事务同步

既然,上面我们知道了,是由于没有调用代理,所以异步打印所有用户仍然用的是原来的线程。那么再问一句:TestService没有被代理吗?它的的确确被代理了,是因为@Transactional让它做了事务代理,但是事务代理基于的就是aop,aop责任链调用的最终节点,调用的是真实对象,所以那里就用的是真实对象去打印,那可不就没代理了嘛!

原因,我们也知道了,那我们可以让它自己注入自己,发现启动报错,启动报错的原因在于@Async实现代理的方式 和 aop的自动代理方式 用的不是同一个代理创建器。在一般情况下,自己注入自己的确是可以解决这种循环依赖 + 自动代理的问题的(或者用AopContxt.currentProxy()获取到绑定到当前线程的代理对象),但是一旦碰到这种@Async 和 aop自动代理的情况,由于有2个代理创建器存在,且它们都要对这个对象进行代理,那就有问题了。会报如下的错误:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService': Bean with name 'testService' has been injected into other beans [testService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1251)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1171)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)... 19 common frames omitted

报错版本:TestService

@Slf4j
@Service
@EnableAsync // 开启异步
@EnableTransactionManagement // 开启事务
public class TestService {@Autowiredprivate UserService userService;@Autowiredprivate TestService testService;@Transactionalpublic void doTx(){log.info("-----------------doTx-----------------" + this.getClass());User user = new User();user.setNickname(RandomStringUtils.randomAlphabetic(5));userService.save(user); // 插入用户log.info("插入用户:{}" , user);testService.printUserList(); // 我们希望的是异步打印所有的用户log.info("-----------------doTx-----------------");try {Thread.sleep(3000); // 这里还需要干其它的活,反正就是这里不确定,万一它就卡在这里了呢, 就模拟这个情况} catch (InterruptedException e) {e.printStackTrace();}}@Asyncpublic void printUserList() {log.info("-----------------printUserList-----------------" + this.getClass());List<User> list = userService.list(new QueryWrapper<User>());for (User user1 : list) {log.info("printUser:  {}",user1);}log.info("-----------------printUserList-----------------");}}

@Lazy正常启动版本(有问题)

给TestService加个@Lazy注解,就可以解决这个问题,解决的方式是因为在解析含有@Lazy注解的依赖时,会创建一个代理对象,这个代理把从spring容器中获取目标bean的时机,调整到了使用它的时候,也就是说,往TestService中注入的testService,在解析依赖的解决,不去容器中去找或者创建,而是直接构建了个代理对象,放入到里面。这样就相当于没有发生循环发生一样,因为循环依赖产生的的时机就是在解析bean的依赖的时候,通过@Lazy创建代理的方式处理了依赖,也就不存在这个循环依赖的问题了。

也好比说:我在TestService中注入一个容器中压根就没有定义的bean,但是我给这个这个字段上的bean加了@Lazy注解,它依然可以正常启动,当然,在用的时候,它仍然会报错。但在这里没关系,在启动阶段已经不报错了,在运行阶段,会去容器中寻找testService,而在运行阶段,spring容器已经初始化好了,也就没问题了。

@Slf4j
@Service
@EnableAsync // 开启异步
@EnableTransactionManagement // 开启事务
public class TestService {@Autowiredprivate UserService userService;@Autowired@Lazyprivate TestService testService;@Transactionalpublic void doTx(){log.info("-----------------doTx-----------------" + this.getClass());User user = new User();user.setNickname(RandomStringUtils.randomAlphabetic(5));userService.save(user); // 插入用户log.info("插入用户:{}" , user);testService.printUserList(); // 我们希望的是异步打印所有的用户log.info("-----------------doTx-----------------");try {Thread.sleep(3000); // 这里还需要干其它的活,反正就是这里不确定,万一它就卡在这里了呢, 就模拟这个情况} catch (InterruptedException e) {e.printStackTrace();}}@Asyncpublic void printUserList() {log.info("-----------------printUserList-----------------" + this.getClass());List<User> list = userService.list(new QueryWrapper<User>());for (User user1 : list) {log.info("printUser:  {}",user1);}log.info("-----------------printUserList-----------------");}}

我们继续访问上面的这个接口:http://localhost:8085/web-api/test/testTx,输出如下的日志。

异步打印的问题是解决了,但是,又有个问题了,查出来怎么只会有1个用户呢?这个接口调用了2次,肯定会有2个用户的,现在却只有一个用户,原因就在于是异步打印的,当前事务还有提交,然后就去查询,肯定就只会查询1个出来

在这里插入图片描述

@Lazy + 注册事务同步

上面代码中,调用@Aysnc注解修饰的异步方法应该是要在事务提交了之后,再去调用,而不是插入数据之后调用!所以需要注册事务同步到事务同步管理器中,在事务提交之后,再去作异步任务,这样异步任务才能在数据库中查到刚刚插入的数据。感觉有点像vue里面的nextTick了。

@Slf4j
@Service
@EnableAsync // 开启异步
@EnableTransactionManagement // 开启事务
public class TestService {@Autowiredprivate UserService userService;@Autowired@Lazyprivate TestService testService;@Transactionalpublic void doTx(){log.info("-----------------doTx-----------------" + this.getClass());User user = new User();user.setNickname(RandomStringUtils.randomAlphabetic(5));userService.save(user); // 插入用户log.info("插入用户:{}" , user);TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {testService.printUserList();// 我们希望的是异步打印所有的用户}});log.info("-----------------doTx-----------------");try {Thread.sleep(3000); // 这里还需要干其它的活,反正就是这里不确定,万一它就卡在这里了呢, 就模拟这个情况} catch (InterruptedException e) {e.printStackTrace();}}@Asyncpublic void printUserList() {log.info("-----------------printUserList-----------------" + this.getClass());List<User> list = userService.list(new QueryWrapper<User>());for (User user1 : list) {log.info("printUser:  {}",user1);}log.info("-----------------printUserList-----------------");}}

可以看到,刚刚插入的时id为4用户,现在能够把刚刚插入的查询出来了

在这里插入图片描述


http://www.ppmy.cn/news/92318.html

相关文章

Nomad 会替代 Kubernetes 吗?对比一下,两者如何选择?

概 述 根据市场状况&#xff0c;Kubernetes&#xff08;又称“K8s”&#xff09;已经成为容器编排之王&#xff0c;超越了Docker Swarm和Mesos等竞争对手。但是&#xff0c;在K8s&#xff08;2014&#xff09;出现的同时&#xff0c;还有另一个编排项目HashiCorp的Nomad&…

产品经理-产品驱动增长2-方案

目录 一、对需求做减法 1.1G公司券商交易系统需求提炼 1.2K公司学习机需求提炼 二、解决方案 三、解决方案的原则 3.1解决方案不能是承诺性的描述 3.2解决方案和需求要有因果关系。 3.3要说人话 公式&#xff1a;增长需求方案共识体验场域效率口碑流量&#xff0c;这篇…

数据链路层:封装成帧

1.数据链路层&#xff1a;封装成帧 笔记来源&#xff1a; 湖科大教书匠&#xff1a;封装成帧 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 封装成帧是指数据链路层给上层交付的协议数据单元添加帧头和帧尾使之成为帧 帧头和帧尾中包含重要的控制…

tektronix泰克TDS3054B数字荧光示波器

TDS3054B示波器体积小巧、便于携带、可用电池供电&#xff0c;所以可在任何需要的地方使用。安装电池 后&#xff0c;其重量还不到5 公斤。即便是在现场工作&#xff0c;也可通过选购的插入式热敏打印机当场打印测量 结果。 用户还可通过选购的应用模块轻而易举地使TDS3000B 系…

Shell脚本攻略:条件语句if、case

目录 一、理论 1.条件测试 2.if语句 3.case语句 二、实验 1.实验一 2.实验二 3.实验三 4.实验四 5.实验五 6.实验六 7.实验七 一、理论 1.条件测试 &#xff08;1&#xff09;三种测试方法 ① test命令测试 ② [ ]测试&#xff08;注意前后需要有空格&…

JWT 详解,当前主流java token 框架之一

JWT&#xff08;JSON Web Token&#xff09;是一种基于JSON格式的轻量级的、用于身份认证的开放标准。它通过在用户和服务器之间传递一个安全的、可靠的、独立的JSON对象来进行身份验证和授权。它如今被广泛应用在RESTful API中&#xff0c;因为RESTful API通常不会维持任何状态…

android 12.0去掉usb授权提示框 默认给予权限

1.概述 在12.0的系统rom产品开发中,在进行iot开发过程中,在插入usb设备时会弹出usb授权提示框,也带来一些不便,这个需要默认授予USB权限,插拔usb都不弹出usb弹窗所以这要从usb授权相关管理页默认给与usb权限 2.去掉usb授权提示框 默认给予权限的相关代码 frameworks/bas…

快速上手kettle

一、前言 最近由于工作需要&#xff0c;需要用到kettle工具进行数据迁移转换。特意找资料学习了一下&#xff0c;kettle基本操作算是学会了。 所学的也结合实际工作进行了验证。为了防止以后用到忘记了&#xff0c;便写了几篇文章记录一下。 二 、ETL简介 ETL ( Extract-Tran…

探索AI助手ChatGPT实际应用场景

使用 Prompt 是使用 ChatGPT 的关键点之一。Prompt 是针对机器学习模型的输入&#xff0c;它提供了一个清晰、简洁的问题描述&#xff0c;并指示模型生成合适的答案。正确的 Prompt 可以帮助 ChatGPT 生成更准确和有用的回答&#xff0c;进而提升用户体验。 Prompt 的有效性取…

2小时入门Netty网络框架

2小时入门Netty 一、Netty概述二、为什么使用Netty2.1 NIO的缺点2.2 Netty的优点三、架构图四、永远的Hello Word4.1 引入Maven依赖4.2 创建服务端启动类4.3 创建服务端处理器4.4 创建客户端启动类4.5 创建客户端处理器4.6 测试五、Netty的特性与重要组件5.1 taskQueue任务队列…

torch中的model.eval()、model.train()详解

&#x1f468;‍&#x1f4bb;个人简介&#xff1a; 深度学习图像领域工作者 &#x1f389;工作总结链接&#xff1a;https://blog.csdn.net/qq_28949847/article/details/128552785 链接中主要是个人工作的总结&#xff0c;每个链接都是一些常用demo&#xff0c…

PAT A1112 Stucked Keyboard

1112 Stucked Keyboard 分数 20 作者 CHEN, Yue 单位 浙江大学 On a broken keyboard, some of the keys are always stucked. So when you type some sentences, the characters corresponding to those keys will appear repeatedly on screen for k times. Now given a…

【ISO14229_UDS刷写】-5-$38诊断服务RequestFileTransfer理论部分

总目录&#xff1a;&#xff08;单击下方链接皆可跳转至专栏总目录&#xff09; 《UDS/OBD诊断需求编辑工具》总目录https://blog.csdn.net/qfmzhu/article/details/123697014 目录 1 $0x38 RequestFileTransfer诊断服务描述 2 0x38服务请求消息 2.1 0x38服务请求消息定义…

Office project 2007安装

哈喽&#xff0c;大家好。今天一起学习的是project 2007的安装&#xff0c;Microsoft Office project项目管理工具软件&#xff0c;凝集了许多成熟的项目管理现代理论和方法&#xff0c;可以帮助项目管理者实现时间、资源、成本计划、控制。有兴趣的小伙伴也可以来一起试试手。…

[译文] PostgreSQL 自动生成的主键的 UUID、序列或标识列?

有时客户会问我关于自动生成主键的最佳选择。在本文中&#xff0c;我将探索这些选项并给出建议。 为什么要自动生成主键&#xff1f; 每个表都需要一个主键。在关系数据库中&#xff0c;能够识别单个表行很重要。如果您想知道为什么&#xff0c;请在 Internet 上搜索数以千计…

C++回调函数理解

C回调函数理解 0.引言1.回调函数的实现方式2.普通函数以函数指针的形式进行实现3.类成员函数以静态函数进行实现4.类成员函数以非静态函数进行实现5.std::funtion和std::bind的使用6.c回调的实现7.应用实例 0.引言 看了一些介绍感觉太官方了&#xff0c;我的简单理解就是从模式…

【Linux】Vim的使用快捷方式

VIM工具使用各种快捷方式&#xff0c;熟练掌握能够做到事半功倍的效果 复制粘贴 复制&#xff1a; yy&#xff1a;复制当前行。nyy&#xff1a;复制当前行及其下面的n行&#xff08;n为数字&#xff09;。yiw&#xff1a;复制当前光标所在的单词。y$&#xff1a;复制光标所在…

ORB_SLAM2算法中特征点是如何精确匹配的?

文章目录 CV_8UC1和CV_32F这两种数据类型有什么区别?IL = IL - IL.at<float>(w,w) *cv::Mat::ones(IL.rows,IL.cols,CV_32F);cv::Mat IR = mpORBextractorRight->mvImagePyramid[kpL.octave].rowRange(scaledvL-w,scaledvL+w+1).colRange(scaleduR0+incR-w,scaleduR0…

新手一定要掌握的实用调试技巧(vs2019)

目录 1、什么是bug&#xff1f; 2、调试是什么&#xff1f; 2.1、调试是什么 2.2、调试的基本步骤 2.3、Debug和Release的介绍 3、Windows环境调试介绍 3.1、调试环境的准备 3.2、学会快捷键 3.3、调试的时候查看程序当前信息 3.3.1、查看临时变量的值 3.3.2、查看内存信息…

如何用Thanos 和 Prometheus 打造一个高可用的K8S监控系统

概 述 对于弹性伸缩和高可用的系统来说&#xff0c;一般有大量的指标数据需要收集和存储&#xff0c;如何为这样的系统打造一个监控方案呢&#xff1f;本文介绍了如何使用 ThanosPrometheusGrafana 构建监控系统。 集群容量概览 用户故事 直到今年 1 月&#xff0c;我一直在…
最新文章