【Spring】编程式事务的应用场景理解

news2025/1/9 16:44:56

前言

我们经常在使用Spring全家桶开发JavaEE项目的时候,一想到事务就会习惯性的使用声明式注解@Transactional,由Spring框架帮你做AOP实现事务的回滚,但是声明式事务恰恰比较方便,所以有些场景下并不好用,接下来我来举一个例子,看大家有没有遇到过类似的需求场景。

场景复现

说有这么两张表,业主表房屋表。关系是 一个业主可以有多个房屋房产登记,一对多的关系。

表结构
--业主表
CREATE TABLE `person` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `age` int(3) DEFAULT '0' COMMENT '年龄',
  `address` varchar(100) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

--房屋表
CREATE TABLE `home` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `home_name` varchar(30) DEFAULT NULL COMMENT '房屋名称',
  `person_id` bigint(20) DEFAULT NULL COMMENT '所属业主',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
实体类
/**
业主实体
*/
@Data
@TableName("person")
public class PersonEntity {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    private String name;

    private Integer age;

    private String address;
}

/**
房屋实体
*/
@Data
@TableName("home")
public class HomeEntity {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    @TableField("home_name")
    private String homeName;

    @TableField("person_id")
    private Long personId;
}
需求

现在要求有一个添加业主信息的接口,支持将批量的业主信息和相关的房产信息录入到系统中,然后返回的数据结构中要告知调用者哪些成功哪些失败了

实现

根据上面的需求,我们涉及多表的插入或者说循环插入表的环境都要先想到事务,这里第一前提是对单个业主来说,业主信息跟房屋信息一定是要么都成功要么都失败,不能说业主信息录入了,房屋信息录入失败,而业主信息还留在上面(这里不讨论业务容错性,就是失败了都不能留有失败业主的记录在上面),这时候脑子里会有以下这个设计接口的构思。

请求参数对象

@Data
public class PersonRequest {

    //名称
    private String name;

    //年龄
    private int age;

    //地址
    private String address;

    //多个房屋信息
    private List<HomeEntity> homeList;
}

Controller层

@RequestMapping("person")
@RestController
public class PersonController {

    @Autowired
    TestService testService;

    @PostMapping("person-v3")
    public DefaultResponse addPersonV3(@RequestBody List<PersonRequest> request) {
        testService.addPersonV3(request);
        return DefaultResponse.DEFAULT_RESPONSE;
    }
}

Service层

这里就展示实现类的addPersonV2方法代码

@Transactional
@Override
public void addPersonV3(List<PersonRequest> request) {
    request.forEach(item->{

        //插入业主信息表中
        PersonEntity personInsert = new PersonEntity();
        personInsert.setName(item.getName());
        personInsert.setAge(item.getAge());
        personInsert.setAddress(item.getAddress());
        this.save(personInsert);

        // 插入房屋表
        if (CollectionUtils.isNotEmpty(item.getHomeList())) {
            item.getHomeList().forEach(home->{
                HomeEntity homeEntity = new HomeEntity();
                homeEntity.setHomeName(home.getHomeName());
                homeEntity.setPersonId(personInsert.getId());
                this.homeMapper.insert(homeEntity);
            });
        }

    });
}

这样子的代码只保证了事务,一旦报错都会回滚,满足不了能提示给用户哪些成功哪些不成功的数据结构。 这时候我们又会想到下面这个用try..catch方式将业务逻辑包起来,然后用一个返回对象记录成功跟失败的信息给前端。

Vo

/**
*@Description   业主添加的返回视图
*@Author        wengzhongjie
*@Date          2022/12/1 9:39
*@Version
*/
@Data
public class PersonResponse {

    //记录录入成功的名字
    private List<String> success=new ArrayList<>();

    //记录录入失败的名字
    private List<String> fail=new ArrayList<>();
}

Controller

@RequestMapping("person")
@RestController
public class PersonController {

    @Autowired
    TestService testService;

    @PostMapping("person-v3")
    public PersonResponse addPersonV3(@RequestBody List<PersonRequest> request) {
        return testService.addPersonV3(request);
    }
}

Service

@Transactional
@Override
public PersonResponse addPersonV3(List<PersonRequest> request) {
    PersonResponse response = new PersonResponse();
    request.forEach(item->{

        try{
            //插入业主信息表中
            PersonEntity personInsert = new PersonEntity();
            personInsert.setName(item.getName());
            personInsert.setAge(item.getAge());
            personInsert.setAddress(item.getAddress());
            this.save(personInsert);

            // 插入房屋表
            if (CollectionUtils.isNotEmpty(item.getHomeList())) {
                item.getHomeList().forEach(home->{
                    HomeEntity homeEntity = new HomeEntity();
                    homeEntity.setHomeName(home.getHomeName());
                    homeEntity.setPersonId(personInsert.getId());
                    this.homeMapper.insert(homeEntity);
                });
            }
            //记录成功的
            response.getSuccess().add(item.getName());
        }catch (Exception e){
            //记录失败的
            response.getFail().add(item.getName());
        }
    });
    return response;
}

这样子看着好像解决了返回的需求,但是其实这时候这个@Transactional已经没啥用了,因为使用了try...catch之后没有继续向外抛出,对于Spring来说,他是觉得你没有出错的。你看着感觉好像没事,但是如果代码出现在插入房屋表的时候出现错误怎么办? 业主信息也不会被回滚,这时候其实就出现了脏数据。如图所示。

请求数据

[
  {
  "name": "田七",
  "age": 1,
  "address": "福建福州",
  "homeList": [{
    "homeName": "福州仓山区某某小区3单元501"
  },{
    "homeName": "福州台江区区某某小区5单元101"
  },{
    "homeName": "福州鼓楼区某某小区1单元901"
  }]
},
{
  "name": "周八",
  "age": 1,
  "address": "福建莆田",
  "homeList": [{
    "homeName": "莆田城厢区某某小区3单元501"
  },{
    "homeName": "莆田秀屿区某某小区5单元101"
  },{
    "homeName": "莆田涵江区某某小区1单元901"
  }]
},
{
  "name": "郑九",
  "age": 1,
  "address": "福建福清",
  "homeList": [{
    "homeName": "福清西区某某小区3单元501"
  },{
    "homeName": "福清北区区某某小区5单元101"
  },{
    "homeName": "福清东北区某某小区1单元901"
  }]
}
]

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里故意设置成3个业主中间的周八业主在插入房屋表时报错,会发现周八的房屋信息没有插入进去,但是业主信息却被插入了,这还是稍微理想的状态,如果周八有多个房屋信息,在遍历后面几个房屋信息的时候出错,就连前面录入的房屋信息也会出现在表里,这是肯定不被允许的。但是一旦加了异常抛出,又会被全部回滚,这时候手动回滚,也就是声明式事务出场了。

我们只要在关键的业务代码位置加上开启事务提交事务回滚事务即可。

public void method(){
    try{
        //开启事务
        
        //=====业务代码=====START
        //todo 
        //=====业务代码=====END
        
        //提交事务
    }catch(Exception e){
        //回滚事务
    }
}

这样子之后,事务由我们自己控制,我们只要在周八报错的时候,给他回滚一下,这样就不会出现脏数据了。但是我们再思考一下当初我们为什么从编程式事务变成了声明式事务,不就是因为方便,写这些开启、提交、回滚实在是太烦了。所以Spring给我们提供了一个TransactionTemplate类帮助我们更方便的简写代码。

在这里插入图片描述

通过上面的execute方法我们来实现需求

@Override
public PersonResponse addPersonV3(List<PersonRequest> request) {
    PersonResponse response = new PersonResponse();
    request.forEach(item->{

        try{
            transactionTemplate.execute(status -> {
                //插入业主信息表中
                PersonEntity personInsert = new PersonEntity();
                personInsert.setName(item.getName());
                personInsert.setAge(item.getAge());
                personInsert.setAddress(item.getAddress());
                this.save(personInsert);

                // 插入房屋表
                if (CollectionUtils.isNotEmpty(item.getHomeList())) {
                    item.getHomeList().forEach(home -> {
                        //如果是周八 就模拟出错
                        if ("周八".equals(item.getName())) {
                            int i = 1 / 0;
                        }
                        HomeEntity homeEntity = new HomeEntity();
                        homeEntity.setHomeName(home.getHomeName());
                        homeEntity.setPersonId(personInsert.getId());
                        this.homeMapper.insert(homeEntity);
                    });
                }
                return Boolean.TRUE;
            });
            response.getSuccess().add(item.getName());
        }catch (Exception e){
            response.getFail().add(item.getName());
        }

    });
    return response;
}

再次请求接口看一下测试结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以我觉得编程式事务还是有使用场景的,而且Spring还提供了一个很方便的方法灰常的不错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务器下载mmdetection

1.申请账号密码 找实验室有root权限的同学创建一个账号密码 2.命令行中敲&#xff1a;ssh服务器账号&#xff0c;然后输入自己的服务器密码&#xff08;确认自己是否处于校园网环境&#xff0c;如果不在的话记得连vpn&#xff09; 3.命令行敲cd /切换到根目录然后ls查看服务…

通过SCADA实现KPI分析和预测性维护远比想象中简单

一、前言 随着工业发展的不断进步&#xff0c;越来越多的企业开始注重KPI分析和预测性维护。通过数据分析实现智能制造已经成为不可缺少的环节。制定并分析关键绩效指标&#xff08;KPI&#xff09;是实现数据分析战略的重要一步&#xff0c;因为只有跟踪这些关键绩效指标才能…

题目1444:蓝桥杯201 4年第五届真题斐波那契

这篇文章是帮一个叫做【废柴成长中】的孩子写的。 题目&#xff1a; 这里难点应该就是在【输入为一行用空格分开的整数n m p(0<n,m&#xff0c;p<10^18)】 &#xff0c;这里一下子就把最大值干成long的最大范围了&#xff0c;很明显&#xff0c;long肯定也不行。 解析其…

[附源码]计算机毕业设计JAVA闲置物品线上交易系统

[附源码]计算机毕业设计JAVA闲置物品线上交易系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

区块链工作原理(节点、层)

Users/Nodes in a Blockchain Network 在某个节点被当作区块链网络的一部分之前&#xff0c;它必须要完成以下的要求&#xff1a; 节点需要为认证过程生成公钥/私钥对节点需要与区块链网络中的其他一部分节点建立直接连接&#xff08;peer connection&#xff09;&#xff0c…

KVM虚拟机迁移

一、KVM宿主机高可用架构设计 宿主机故障&#xff0c;内部虚拟机可以不受影响&#xff0c;可以迁移走负载均衡&#xff0c;当某个宿主机压力过大&#xff0c;可以将部分虚拟机迁移到其他机器&#xff0c;降低其负载。 1.1、架构设计 1.2、实验机器 机器 IP 角色 node1 192.…

L. Paid Leave(贪心)[CCPC Finals 2021]

题目如下&#xff1a; 思路 or 题解 我们可以先只考虑第一段(两个休息日之间) 白色为休息日&#xff0c;蓝色为工作日 我们思考&#xff0c;如果在满足题意的条件下&#xff0c;如何安排额外的休息日可以使答案更优&#xff1a; 贪心可得&#xff1a;额外的休息日尽量往后安排…

RabbitMQ之TTL机制

在京东下单&#xff0c;订单创建成功&#xff0c;等待支付&#xff0c;一般会给30分钟的时间&#xff0c;开始倒计时。如果在这段时间内用户没有支付&#xff0c;则默认订单取消。 该如何实现&#xff1f; 定期轮询&#xff08;数据库等&#xff09; 用户下单成功&#xff0…

【自然语言处理(NLP)】聊天机器人模块实现

【自然语言处理&#xff08;NLP&#xff09;】聊天机器人模块实现 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项目总负责人&#xff0c;全国高等学校计算…

《异常检测——从经典算法到深度学习》20 HotSpot:多维特征 Additive KPI 的异常定位

《异常检测——从经典算法到深度学习》 0 概论 1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法 3 基于One-Class SVM的异常检测算法 4 基于高斯概率密度异常检测算法 5 Opprentice——异常检测经典算法最终篇 6 基于重构概率的 VAE 异常检测 7 基于条件VAE异常…

[附源码]计算机毕业设计JAVA鲜花销售管理系统

[附源码]计算机毕业设计JAVA鲜花销售管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

【学习笔记02】node的模块化和内置模块

一、nodejs的模块化 &#x1f602; nodejs 所有的东西都是模块 &#xff08;一&#xff09;node模块的分类 1. 自定义模块&#xff1a;自己写的模块 2. 内置模块&#xff1a;nodejs提供的模块 3. 第三方模块 别人写好的东西, 上传到某一个位置(npm), 我们去 npm 去下载到我们本…

SpringBoot+MyBatis和MyBatisPlus+vue+ElementUl实现批量删除 我只能说太简单了

目录准备工作MySQL数据库表Result返回结果1、SpringBootMyBatisPlusvueElementUl实现批量删除1.1、演示GIF动态图1.2、实体类1.3、Dao接口1.4、Service接口1.5、ServiceImpl接口实现层1.6、Controller控制层1.7、Vue前端2、SpringBootMyBatisvueElementUl实现批量删除2.1、演示…

关于数据权限的设计

在项目实际开发中我们不光要控制一个用户能访问哪些资源&#xff0c;还需要控制用户只能访问资源中的某部分数据。 控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC&#xff0c;但是控制用户只能访问某部分资源&#xff08;即我们常说的数据权限&#xff09;使用R…

云计算-JavaAPI与Hadoop的互联的实现

云计算-JavaAPI与Hadoop的互联的实现 文章目录云计算-JavaAPI与Hadoop的互联的实现一、环境准备二、HDFS 基本的命令操作三、HDFS客户端操作IntelliJ IDEA 环境准备通过API操作HDFS主函数程序进行连接测试1. 初始化hdfs连接获得FileSystem对象1. HDFS获取文件系统2. HDFS创建文…

Redis集群方案备忘录

文章目录哨兵模式官方Redis ClusterJedis&#xff08;客户端分片&#xff09;Codis&#xff08;代理分片&#xff09;哨兵模式 优点 哨兵模式是基于主从模式的&#xff0c;解决可主从模式中master故障不可以自动切换故障的问题。缺点 &#xff08;1&#xff09;是一种中心化的…

Express 6 指南 - 路由 6.3 路线路径 Route paths

Express Express 中文网 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录Express6 指南 - 路由6.3 路线路径 Route paths6 指南 - 路由 6.3 路线路径 Route paths 【这翻译得…生怕国人看懂】 路由路径与请求方法相结合&#xff0c;定义了可以…

大数据培训课程之序列化案例实操

序列化案例实操 1. 需求 统计每一个手机号耗费的总上行流量、下行流量、总流量 &#xff08;1&#xff09;输入数据 &#xff08;2&#xff09;输入数据格式&#xff1a; 7 13560436666 120.196.100.99 1116 954 200 id…

编辑器实现思路

复杂项目 业务的复杂性: 交互的复杂性数据结构和状态的复杂性,例如级联选择器需要遍历树结构,还有一些需要链表、栈、队列等多项目依赖,工程的复杂性性能优化流程的复杂性 git flowlint 工具单元测试commit信息Code ReviewCI/CD开发一个编辑器 例如低代码的编辑器 编辑器…

如何批量旋转图片?学会这三种方法就能轻松实现

对于喜爱拍照的小伙伴来说&#xff0c;你们的手机或者相机应该有很多图片素材吧。那么在整理这些图片到电脑的时候&#xff0c;你们的图片会不会出现方向不一致的情况呢&#xff1f;有的是倒着的&#xff0c;有的是左旋了90。想要将这些图片都调整为同一个方向&#xff0c;靠手…