• 事务控制:事务不会滚?


    可能原因一、try-catch中的代码报错,但是事务未回滚

    1、问题描述

    由于上传文件时通过异步来处理数据,故在异步处理数据之前向日志表中插入一条数据,处理状态为:处理中;等到处理结束,再修改处理状态为成功或失败。由于往数据库插入数据可能会抛异常,故用try/catch来处理,在正常情况下不会报错,故处理状态为成功;如果出现异常就catch,但是由于try/catch不会抛出异常,故事务不会回滚。那么这种情况下,那该如何处理呢?

    代码如下:

    实现类中部分代码

        @Override
        public String scanCodeOutstore(JSONObject jsonObj) throws ExecutionException, InterruptedException {
         ....
            OutstoreUploadLog uploadLog = new OutstoreUploadLog();
            // 记录日志,处理状态为:处理中
            if (StringUtils.isNotBlank(path)) {
                uploadLog = insertUploadLog(xxx);
            }
            // 异步调用
            Future<Long> future = outStoreAsycService.saveOutstoreInfo(uploadLog);
    // 记录日志,处理状态改为成功或失败 outStoreAsycService.callBack(future,uploadLog); return null; }

    注意:异步调用是,调用异步方法的方法和异步方法不能在一个类种,不然异步失效。

    saveOutstoreInfo方法:该方法异步执行,主要用于控制事务。返回outstoreId是为了判断对数据库的操作是成功还是失败,如果失败,则返回null

        @Async
        @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) // 对抛出的任何异常都进行自动回滚
        public Future<Long> saveOutstoreInfo(OutstoreUploadLog uploadLog) {
            Long outstoreId = null;
            try {
                // 所有对数据库进行DML操作的语句
            } catch (Exception e) {
                log.info(e.getMessage());
           // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return new AsyncResult(outstoreId); // 封装成Future }

    callBack方法:必须异步执行,才可以快速返回结果给调用者。改方法的目的是为了记录日志。

    /**
         * 任务总回调函数
         * @param task
         */
        @Async  // 必须要用Async异步执行,否则主线程会一直等待callBack方法执行完毕才会返回信息给调用者
        public void callBack(Future<Long> task,OutstoreUploadLog uploadLog2) throws ExecutionException, InterruptedException {
            for (;;) { // 注意:这里要加while true死循环,否则isDone()返回false,callBack执行完毕
                if(task.isDone()) { // 如果前面一个方法执行完成,则调用另外一个接口
                    uploadLog2.setOrderId(task.get());
                    uploadLog2.setUpdateTime(new Date());
                    if (null == task.get()) { // future的get()方法返回Future中封装的值,如这里的outstoreId
                        // 如果出现异常,则修改处理状态为失败
                        uploadLog2.setDealStatus("3");
                        uploadLog2.setDealDesc("处理失败");
                    } else {
                        // 如果上面的都成功了,则修改日志状态未处理成功
                        uploadLog2.setDealStatus("2");
                        uploadLog2.setDealDesc("处理成功");
                    }
                    outstoreUploadLogService.update(uploadLog2);
                    // 任务都调用完成,退出循环等待
                    break;
                }
                // 没3秒循环一次,任务的完成需要时间,不要频繁循环
                Thread.sleep(3000);
            }
        }

    2、解决办法

    在catch中抛出所捕获的异常,或者添加下面代码手动回滚。

    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

    总结:

    1.使用@Transactional(rollbackFor = Exception.class)注解,使用try-catch必须在catch语句中抛出异常,不然事务不回滚;

    2.@Transactional注解,默认识别RuntimeException,只能在抛出RuntimeException或者Error时才会触发事务回滚,可在catch语句中直接抛出RuntimeException;

    3.代码中有try-catch,需要事务回滚的话,最好添加手动回滚。

    可能原因二、事务内有DDL语句

    1、了解

    mysql中ddl事务控制:

    (1)、当执行到DDL语句时,会隐式的将当前回话的事务进行一次“COMMIT”操作,因此在MySQL中执行DDL语句时,应该严格地将DDL和DML完全分开,不能混合在一起执行。

    (2)、为什么DDL语句会隐式提交?
    因为DDL是数据定义语言,在我们的数据库中承担着创建,删除和修改的重要的职责。一旦发生问题,带来的后果很可能是不可估量的。二是在每执行完一次后就进行提交,可以保证流畅性,数据不会发生阻塞,同时也会提高数据库的整体性能。

    (3)、DDL和DML混和执行可能带来的风险:事务中有一个DDL语句,导致事务“中途”提交一次,等最后发现异常要回滚却发现只能回滚一部分。

    2、问题描述

    共有四张表,前面三张表由于要分表,故先用存储过程创建表,再插入数据,第四个表b_code_year不需要建表,只插入数据。在往第四张表插入数据时认为制造一个空指针异常,结果发现前面两张表中事务没有回滚,而第三张表事务回滚了,第四张表由于报错没有数据。

    3、解决办法

    方法一:将创建表的语句放到事务外面先执行,即所有的表都创建完成再开启事务。

    方法二:也可以把创建表的语句放到事务内,即先创建完所有表,此时当前事务结束,会另外再开启一个事务,再往各表中插入数据,如果出异常回滚,会回滚到创建表之后的位置。

    代码如下:

    @Override
        public String scanCodeOutstore(JSONObject jsonObj) { // 该方法不进行事务控制
         ....
    // 存储过程创建这三张表,注意创建表的语句不能放到事务中,因为DDL语句会自动提交,导致事务控制失败 buildOutstoreTable(tableName); //查询出库单据明细表是否存在,如果不存在则创建明细表,如果存在则新增或修改记录 buildDetailTable(tableName); //查询出库码表是否存在,如果不存在则创建出库码表 buildCodeDetailTable(tableName); // 异步调用,在该方法中进行事务控制 outStoreAsycService.saveOutstoreInfo(xxx); return null; }

    saveOutstoreInfo方法:

    @Async
        @Transactional(rollbackFor = Exception.class)
        public void saveOutstoreInfo(xxx) {
         // 切记不要在for循环内try/catch
         try { // DML 语句 } catch (Exception e) { log.info(e.getMessage()); // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();        } }

    常见几种事务失效场景:

    1、@Transactional 加于private方法, 无效。@Transactional应加于public方法上。
    2、@Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效。
    3、@Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效。
    4、**@Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效**
    5、@Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效。
    6、@Transactional 加于接口方法后, 被其它类的接口方法调用, 有效。
    7、@Transactional 加于接口方法后, 被其它类的私有方法调用后, 有效。

  • 相关阅读:
    c# 读取数据库得到dateset
    c# 读数据库二进制流到图片
    c# 读取数据库得到字符串
    c#打开颜色对话框
    WinForm-GridView
    arcengine 常用方法
    arcgis engine 调用arcgis server服务
    ae
    ae保存图层
    ae 打开地图文档
  • 原文地址:https://www.cnblogs.com/zwh0910/p/16670683.html
Copyright © 2020-2023  润新知