事务service调用异步线程bug

事务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"));

    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352