欢迎光临散文网 会员登陆 & 注册

catch里面抛出了异常,finally里面的事务会提交吗?

2023-06-24 18:02 作者:没有名字如何行走江湖  | 我要投稿

想直接看结论的可以拉到文末总结。

背景

我们公司的系统中有一个业务场景,需要第三方的账户数据同步到我们系统。
同步账号的同时,会将所有同步数据和是否成功记录到一张同步日志表中,方便排查问题和记录。
好了,话不多说,我们直接上代码。

欢迎关注个人公众号【好好学技术】交流学习

目前的代码

下面是一段伪代码

java复制代码@Data @Build public class Test() {  private boolean success = true; }   @Transaction public void sync() {  Test test = Test.builder().build();  try{   xxxxx  }catch(Exception e) {    log.error("xxxx",e)    test.setSuccess(false);    throw new ThirdAccountException("同步账号错误:" + e.getMessage());  } finally {    testMapper.insert(test);  }  }

大家能看出来这段代码有什么问题吗?😄

talk is cheap, show me the code

直接实战演示

数据库新建 账户数据同步记录表

sql复制代码CREATE TABLE `account_log` (   `id` bigint NOT NULL,   `data` varchar(255)  DEFAULT NULL COMMENT '第三方数据',   `success` tinyint(1) DEFAULT NULL COMMENT '是否成功0否1是',   `error_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

第三方账户数据实体

java复制代码package com.fandf.demo.transaction;     import cn.hutool.json.JSONUtil;   import io.swagger.annotations.ApiModel;   import lombok.AllArgsConstructor;   import lombok.Data;     /**   * @author fandongfeng   * @date 2023/6/17 14:45   */   @Data   @AllArgsConstructor(staticName = "of")   @ApiModel("第三方数据实体")   public class ThirdAccount {         private String id;       private String data;         public AccountLog toAccountLog() {           return AccountLog.builder().data(JSONUtil.toJsonStr(this)).build();       }     }

本地账户同步记录实体

java复制代码package com.fandf.demo.transaction;     import com.baomidou.mybatisplus.annotation.IdType;   import com.baomidou.mybatisplus.annotation.TableId;   import com.baomidou.mybatisplus.annotation.TableName;   import com.baomidou.mybatisplus.extension.activerecord.Model;   import io.swagger.annotations.ApiModelProperty;   import lombok.Builder;   import lombok.Data;   import lombok.EqualsAndHashCode;     /**   * @author fandongfeng   * @date 2023/6/17 14:43   */   @EqualsAndHashCode(callSuper = true)   @TableName("account_log")   @Data   @Builder   public class AccountLog extends Model<AccountLog> {         private static final long serialVersionUID = 5648238459610595434L;        @TableId(type = IdType.ASSIGN_ID)       private Long id;        @ApiModelProperty("第三方原始数据")       private String data;        @ApiModelProperty("是否成功: 0否1是")       private boolean success = true;        @ApiModelProperty("错误数据")       private String errorMsg;     }

本地账户同步记录实体mapper

java复制代码package com.fandf.demo.transaction;     import com.baomidou.mybatisplus.core.mapper.BaseMapper;   import org.springframework.stereotype.Repository;     /**   * @author fandongfeng   * @date 2023/6/17 14:50   */   @Repository   public interface AccountLogMapper extends BaseMapper<AccountLog> {   }

同步账户处理的逻辑

java复制代码package com.fandf.demo.transaction;     import org.springframework.stereotype.Service;     import javax.annotation.Resource;     /**   * @author fandongfeng   * @date 2023/6/17 14:42   */   @Service   public class TestTransaction {         @Resource       AccountLogMapper accountLogMapper;         @Transactional(rollbackFor = Exception.class)     public void syncAccount(ThirdAccount account) {           AccountLog accountLog = account.toAccountLog();           try {               //模拟id为2 则抛出异常               if ("2".equals(account.getId())) {                   throw new Exception("模拟抛出异常");               }           } catch (Exception e) {               accountLog.setSuccess(false);               accountLog.setErrorMsg(e.getMessage());               throw new IllegalArgumentException("同步第三方账号错误:" + e.getMessage());         } finally {               accountLogMapper.insert(accountLog);           }       }       }

单元测试

插入成功案例

java复制代码package com.fandf.demo.transaction;     import org.junit.jupiter.api.Test;   import org.springframework.boot.test.context.SpringBootTest;     import javax.annotation.Resource;     import static org.junit.jupiter.api.Assertions.*;     @SpringBootTest   class TestTransactionTest {       @Resource       TestTransaction testTransaction;         @Test       void syncAccount() {           testTransaction.syncAccount(ThirdAccount.of("1", "成功数据"));       }   }

查看数据库

是插入了,但是成功的success应该为1啊,为什么插入了0。

AccountLog.java

java复制代码    @ApiModelProperty("是否成功: 0否1是")       private boolean success = true;  

第三方转为AccountLog实体的方法

java复制代码    public AccountLog toAccountLog() {           return AccountLog.builder().data(JSONUtil.toJsonStr(this)).build();       }  

我们来看看编译后的AccountLog.class源码中的AccountLogBuilder部分 success并未赋初始值

java复制代码public static class AccountLogBuilder {       private Long id;       private String data;       private boolean success;       private String errorMsg;        AccountLogBuilder() {       }        public AccountLogBuilder id(Long id) {           this.id = id;           return this;       }        public AccountLogBuilder data(String data) {           this.data = data;           return this;       }        public AccountLogBuilder success(boolean success) {           this.success = success;           return this;       }        public AccountLogBuilder errorMsg(String errorMsg) {           this.errorMsg = errorMsg;           return this;       }        public AccountLog build() {           return new AccountLog(this.id, this.data, this.success, this.errorMsg);       }        public String toString() {           return "AccountLog.AccountLogBuilder(id=" + this.id + ", data=" + this.data + ", success=" + this.success + ", errorMsg=" + this.errorMsg + ")";       }   }

我们看到Builder()方法返回了
AccountLogBuilder() {
}
对象。
指定data

java复制代码public AccountLogBuilder data(String data) {       this.data = data;       return this;   }

执行build()方法

java复制代码public AccountLog build() {       return new AccountLog(this.id, this.data, this.success, this.errorMsg);   }

success并未赋初始值,所以success=false, 存到数据库就是0了。

那么怎么解决呢?

  • 1.字段加上注解@Builder.Default

java复制代码@Builder.Default   private boolean success = true;

我们再来测试下

java复制代码@Test   void syncAccount() {       testTransaction.syncAccount(ThirdAccount.of("1", "加上@Builder.Default成功数据"));   }

查看数据库

插入成功。

  • 2.手动赋值

java复制代码    public AccountLog toAccountLog() {           return AccountLog.builder().success(true).data(JSONUtil.toJsonStr(this)).build();       }  

插入失败案例

java复制代码@Test   void syncAccount() {       testTransaction.syncAccount(ThirdAccount.of("2", "测试失败数据"));   }

查看数据库

错误数据并没有插入进来。

这是因为catch里面抛出了异常,finally里面提交的事务也回滚了,我们去掉syncAccount(ThirdAccount account)方法上面的@Transactional注解。
再执行一次单元测试

java复制代码@Test   void syncAccount() {       testTransaction.syncAccount(ThirdAccount.of("2", "去掉@Transactional注解测试失败数据"));   }

查看数据库

总结

  • 1.Build并不是对属性赋予默认值,如果想指定默认值可以在字段上使用@Builder.Default注解。

  • 2.如果方法上加了@Transaction注解,catch里面抛出了异常,finally里面的事务会回滚。

当然我们在使用@Transaction注解的时候也需要注意事务的粒度,不能图省事直接在入口加一个Transaction注解。

catch里面抛出了异常,finally里面的事务会提交吗?的评论 (共 条)

分享到微博请遵守国家法律