草庐IT

事务service调用异步线程bug

Raral 2023-03-28 原文

事务service调用异步线程bug

当一个service更新一条数据,但是在异步方法里,查询数据时候,不是最新的数据的???

示例(普通开启线程-当前线程有睡眠):

    

    @Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {
            new Thread(() -> {
                GoodsPO byId = this.getById("111");
                //这个时候查询是没有提交事务的数据(也就是更新前的数据)
                log.info("【测试goodspo】{}", JacksonUtils.obj2json(byId));
            }).start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("【==当前线程事务提交==】");
        }

    }


 //打印
   /**
   【==当前线程事务开始==】
   【测试goodspo】 未提交事务的数据
    【==当前线程事务提交==】
   */

示例(普通开启线程-当前线程没有睡眠):

    

    @Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {
            new Thread(() -> {
                GoodsPO byId = this.getById("111");
                //这个时候查询是提交事务的数据(也就是更新后的数据)
                log.info("【测试goodspo】{}", JacksonUtils.obj2json(byId));
            }).start();
            log.info("【==当前线程事务提交==】");
        }

    }


 //打印
   /**
   【==当前线程事务开始==】
    【==当前线程事务提交==】
    【测试goodspo】 已经提交事务的数据(更新后的数据)
   */

示例(springboot线程池@EnableAsync-@Async("executor")-):


    @Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {
            //异步线程池方法
            taskExecutor.asyncTest("111");
            log.info("【==当前线程事务提交==】");
        }

    }


 @Async("executor")
    public void asyncTest(String s) {
        GoodsPO byId = goodsMapper.selectById(s);
        log.info("【测试goodspo】{}", JacksonUtils.obj2json(byId));
    }


 //打印
   /**
    【==当前线程事务开始==】
    【==当前线程事务提交==】
    【测试goodspo】 (本地环境是获取更新后的数据,其实线上环境是获取更新前的数据)
   */

(本地环境是获取更新后的数据,其实线上环境是获取更新前的数据)

解决上面问题

  1. 在当前异步方法,添加休眠时间
  2. 在service调取异步方法时,直接查询好数据,传给异步方法
@Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {
            //异步线程池方法
            taskExecutor.asyncTest("111");
            log.info("【==当前线程事务提交==】");
        }

    }

@Async("executor")
    public void asyncTest(String s) {

        try {
            long start = System.currentTimeMillis();
            log.info("线程开始休眠start{}",start);
            Thread.sleep(1000);
            log.info("线程结束休眠end{}",System.currentTimeMillis() - start);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        GoodsPO byId = goodsMapper.selectById(s);
        log.info("【测试goodspo】{}", JacksonUtils.obj2json(byId));
    }
@Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {
            //异步线程池方法
            taskExecutor.asyncTest("111");
            log.info("【==当前线程事务提交==】");
        }

    }

@Async("executor")
    public void asyncTest(GoodsPO goodsPO) {
        log.info("【测试goodspo】{}", JacksonUtils.obj2json(goodsPO));
    }

注意:异步方法里的异常,不会影响外面事务的

进阶解决@Transactional 事务提交之后执行 @Async 修饰的异步方法
最近项目中遇到的问题:
俩个service方法, 方法A中调用方法B。
方法A核心业务涉及多张表的数据操作,事务采用注解:@Transactional(rollbackFor = Exception.class)。
方法B 比较耗时,为了不影响核心业务,方法B 用@Async注解,单独开启一个线程去异步执行。(方法B在另外一个类里边,不能和A在同一个类)。
出现的问题:
方法A的事务还没提交,方法B就执行了,导致方法B中查到的数据还是老数据。
当时想到的解决方案,方法A事务提交后再执行方法B

  • 问题代码:

class A {
 
    @Autowired
    private B b;
    
    @Transactional
    public void updateA(..) {
        insert(..);
        update(..);
        b.updateB(..);
    }
 
}
    
 
class B {
 
    @Async
    public void updateB(..) {
        update(..)
    }
 

  • 注意
    方法A和方法B假如在同一个类中,则方法B的@Async注解会失效:
    首先先解释下@Transactional注解失效原因:Spring在扫描bean的时候会扫描方法是否包含@Transactional注解,如果包含,spring会为这个bean动态生成一个子类(代理类proxy),代理类是继承原来的bean的。
    此时,当前这个注解的方法被调用时候,实际上是由代理类调用的,代理类在调用之前就会启动Transaction事务。然而,如果这个方法被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来bean直接调用,所以就不会启动Transaction,我们看到的现象就是 @Transactional 注解无效。
    @Transactional和@Async注解实现都是基于spirng的Aop,而AOP实现是基于动态代理实现的,故Async 失效的原理和原理是一样的。(都是因为同一个类其他方法调用时,没有通过代理类调用,所以注解失效)

  • 代码

 @Resource
    private TaskExecutor taskExecutor;


    @Transactional
    @Override
    public void test() {
        log.info("【==当前线程事务开始==】");

        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getIsDelete, 1).eq(GoodsPO::getId, "111"));
        if(update) {

            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void beforeCommit(boolean readOnly) {
                    log.info("【==当前事务提交前==】");
                }

                @Override
                public void afterCommit() {
                    log.info("【==当前事务提交后==】");
                    //异步线程池方法
                    taskExecutor.asyncTest("111");
                }
            });

            log.info("【==当前线程事务提交完成==】");
        }

    }

    @Async("executor")
    public void asyncTest(String s) {

        GoodsPO byId = goodsMapper.selectById(s);
        log.info("【测试goodspo】{}", JacksonUtils.obj2json(byId));
    }



    //打印
    /**
        【==当前线程事务开始==】
         【==当前线程事务提交完成==】
         【==当前事务提交前==】
         【==当前事务提交后==】
         【测试goodspo】 (事务提交后的数据)
         
    */

进阶解决@Transactional 事务提交之后执行 异步方法时,获取不到事务提交后的数据(@TransactionalEventListener注解)
Spring事务监听机制—使用@TransactionalEventListener处理数据库事务提交成功后再执行操作

  • 为什么使用  
      在项目中,往往需要执行数据库操作后,发送消息或事件来异步调** * 用其他组件执行相应的操作,例如:
      用户注册后发送激活码;
      配置修改后发送更新事件等。
      但是,数据库的操作如果还未完成,此时异步调用的方法查询数据库发现没有数据,这就会出现问题。
  • 为了解决上述问题,Spring为我们提供了两种方式:
      (1) @TransactionalEventListener注解(订阅发布设计模式)
      (2) 事务同步管理器TransactionSynchronizationManager(上述已介绍)
      以便我们可以在事务提交后再触发某一事件。
  • 代码
    定义事件
/**
事件
 * */
public class AcctypeEvent extends ApplicationEvent {

    private String id;

    public AcctypeEvent(Object source, String id) {
        super(source);
        this.id = id;
    }

    public String getId() {
        return id;
    }
}

定义事件监听器

/**
事件监听器
 * */
@Component
public class AcctytpeEventListener {
    
    @Resource
    private TaskExecutor taskExecutor;

    //监听劵批次事件
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    void updataAcctypeEvent(AcctypeEvent event) {
//        String id = event.getId();//商品id
        //查询最新的数据
        taskExecutor.asyncTest();

    }
    
}

发布事件

 @Autowired
    private ApplicationContext applicationContext;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void test2() throws Exception {
        log.info("【主线程名称】:{}",Thread.currentThread().getName());
        //更新操作
        boolean update = this.update(Wrappers.<GoodsPO>lambdaUpdate().set(GoodsPO::getApp, 66).eq(GoodsPO::getId, "0001059971"));

        //发布事件,处理异步任务(查询最新数据,发送短信成功后更新状态)
        applicationContext.publishEvent(new AcctypeEvent("我是和事务相关的事件,请事务提交后执行我","0001059971"));

    }

有关事务service调用异步线程bug的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  2. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

  5. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. ruby - 调用其他方法的 TDD 方法的正确方法 - 2

    我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent

  8. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

随机推荐