7.16 SpringBoot项目实战 【学生入驻】(下):正确理解 编程式事务和声明式事务

news2024/11/22 18:52:26

CSDN成就一亿技术人

文章目录

  • 前言
  • 一、service层
    • 1. 提交学生信息
    • 2. 申请借阅资格
    • 3. 重新提交
    • 4. 事务
  • 二、web层 StudentController
  • 三、测试
  • 最后


前言

通过上文,我们实现了【学生入驻】的第一个API:查询学生信息,接下来的流程通常如下图:如果学生未入驻,将提示学生填写信息,申请借阅资格(借阅证),这也正是本文要实现的需求!

在【数据库设计 --MySQL】时曾做过业务分析:提交学生信息(插入student表)、申请借阅证(插入qualification表),这两个SQL是一步操作,也就是原子操作,所以会用到数据库事务!

在【7.8】曾讲过声明式事务@Transactional,但有的时侯仍需要 编程式事务,所以本文将结合实战场景,帮助你正确理解编程式事务声明式事务

在这里插入图片描述


一、service层

StudentService定义方法如下(studentBO是提交的学生信息):

/**
 * 提交学生信息,并申请借阅证
 **/
void apply(StudentBO studentBO);

StudentServiceImpl的对应的空实现:

@Override
public void apply(StudentBO studentBO) {
    
}

1. 提交学生信息

先看看数据库表设计:

在这里插入图片描述

小插曲:一个字段小调整
对于is_approved字段,考虑到后面更方便查询,所以决定修改为:是否申请通过(0-待审核 1-通过 2-未通过)。
修改方法:

  1. 修改generatorConfig.xmlis_approved配置:将javaType从Boolean修改为Integer,并双击generate重新生成,如下图:
    在这里插入图片描述
  2. 生成以后,请确认Student和StudentExample类已被修改。
  3. 然后,再手动修改StudentBO类的private Integer isApproved;

回归主题,由此,提交学生信息的实现如下:

@Override
public void apply(StudentBO studentBO) {
    // 1. 提交学生信息
    // 初始化基础数据
    studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
    studentBO.setIsFrozen(Boolean.FALSE);
    studentBO.setGmtCreate(new Date());
    studentBO.setGmtModified(new Date());
    Student po = CopyUtils.copy(studentBO, Student::new);
    studentMapper.insertSelective(po);
}

因为大部分审核同样都是3种状态,所以这里将审核状态封装成了一个公共枚举类:

package org.tg.book.common.enums;

/**
 * 审核状态枚举
 **/
public enum ExamineEnum {
    TO_BE_EXAMINE(0, "待审核"),
    APPROVED(1, "审核通过"),
    REJECTED(2, "驳回"),
    ;

    Integer code;
    String msg;

    ExamineEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return this.code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

2. 申请借阅资格

我们设计的是可以多次申请,并且每次都会保存申请记录!

首先注入QualificationMapper:

@Autowired
private QualificationMapper qualificationMapper;

对于插入qualification表,只需要提供学生id、申请状态(0-待审核 1-通过 2-未通过),实现如下:

// 2. 申请借阅资格
Qualification qualification = new Qualification();
qualification.setStudentId(student.getId());
qualification.setStatus(ExamineEnum.TO_BE_EXAMINE.getCode());
qualification.setGmtCreate(new Date());
qualification.setGmtModified(new Date());
qualificationMapper.insertSelective(qualification);

3. 重新提交

前两步的实现实际还缺少重新提交的情况,所以完整的流程应该先查询学生信息

  • 如果不存在,则直接走前2步提交;
  • 如果存在,还需要校验是否已通过,如果已通过是不可以重新提交,否则重新提交。

查询两步校验写在前面,代码如下:

// 查询学生
Student student = studentMapperExt.selectByUserId(studentBO.getUserId());
if (student != null) {
    // 如果已审核通过,不可以重新提交
    Assert.ifTrue(ExamineEnum.APPROVED.getCode().equals(student.getIsApproved()), "已审核通过,请勿重新提交!");
    // 如果未通过, 看是否有待审核的借阅记录
    QualificationExample example = new QualificationExample();
    example.createCriteria().andStudentIdEqualTo(student.getId())
            .andStatusEqualTo(ExamineEnum.TO_BE_EXAMINE.getCode());
    long count = qualificationMapper.countByExample(example);
    Assert.ifTrue(count > 0, "已提交待审核,请勿重新提交!");
}

提交学生信息,同样需要支持已存在情况,无则insert,有则update,所以改造代码如下:

// 1. 提交学生信息
studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
if (student == null) {
	// 初始化基础数据
    studentBO.setIsFrozen(Boolean.FALSE);
    studentBO.setGmtCreate(new Date());
    studentBO.setGmtModified(new Date());
    student = CopyUtils.copy(studentBO, Student::new);
    studentMapper.insertSelective(student);
} else {
    // 已存在,只拷贝新录入的赋值属性
    CopyUtils.copyPropertiesIgnoreNull(studentBO, student);
    studentMapper.updateByPrimaryKeySelective(student);
}

到这,整体流程上看起来已经OK了,但是,还差一点点,那就是事务

4. 事务

声明式事务 我们之前曾用过,非常易用,非常爽,在【7.8】已经详细说明过,只需要在方法上加注解:

@Transactional(rollbackFor = Exception.class)

所以,本文主要再说一下编程式事务

纵所周知,事务虽好,但我们应尽可能避免长事务,应尽可能给数据库减压,所以事务的粒度应尽可能小,尽可能去除与事务无关的代码,能不开启则不必开启!

仔细思考,我们的场景正适合,我们真正需要事务的地方是【1. 提交学生信息】和【2. 申请借阅资格】,所以:

  1. 查询逻辑没必要开启事务
  2. 校验逻辑更必要开启事务,因为还可能回滚

这时如果想用声明式事务就不太合适了,因为@Transactional的粒度是应用于方法,如果仍要使用,需要拆分为两个方法,并且需要同类中两个方法调用,还要保证AOP注解生效!所以这就是 编程式事务的用武之地,因为它可以作用于代码块,并且粒度更小,更灵活!

OK,那编程式事务 怎么用呢?
我推荐使用的是TransactionTemplate,Spring为我们封装的非常易用的Template!它本身继承于TransactionDefinition,内部注入了PlatformTransactionManager,并且封装好了execute泛型方法:回滚、提交都安排的明明白白,有兴趣可以看看流程的编排,所以咱们用它吧!

在这里插入图片描述

用法过于简单,TransactionTemplate基本模板用法:

transactionTemplate.execute(action -> {
    // 事务执行的代码。。。
    // TODO 1。。。
    // TODO 2。。。
    
    // 最后根据需要返回,无返回值则return null
    return null;
});

接着具体应用,首先注入TransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;

然后,只需要把【1. 提交学生信息】和【2. 申请借阅资格】作为action传入即可!

因为全部传入,会报错:Variable used in lambda expression should be final or effectively final

所以,我们将构建student的过程也可以提到事务外,然后将final student再传入,完整实现代码如下:

// @Transactional(rollbackFor = Exception.class)
@Override
public void apply(StudentBO studentBO) {
    // 查询学生
    Student student = studentMapperExt.selectByUserId(studentBO.getUserId());
    if (student != null) {
        // 如果已审核通过,不可以重新提交
        Assert.ifTrue(ExamineEnum.APPROVED.getCode().equals(student.getIsApproved()), "已审核通过,请勿重新提交!");
        // 如果未通过, 看是否有待审核的借阅记录
        QualificationExample example = new QualificationExample();
        example.createCriteria().andStudentIdEqualTo(student.getId())
                .andStatusEqualTo(ExamineEnum.TO_BE_EXAMINE.getCode());
        long count = qualificationMapper.countByExample(example);
        Assert.ifTrue(count > 0, "已提交待审核,请勿重新提交!");
    }
    
    // 初始化基础数据
    studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
    if (student == null) {
        studentBO.setIsFrozen(Boolean.FALSE);
        studentBO.setGmtCreate(new Date());
        studentBO.setGmtModified(new Date());
        student = CopyUtils.copy(studentBO, Student::new);
    } else {
        // 已存在,只拷贝新录入的赋值属性
        CopyUtils.copyPropertiesIgnoreNull(studentBO, student);
    }
    
    Student finalStudent = student;
    transactionTemplate.execute(action -> {
        // 1. 提交学生信息
        if (finalStudent.getId() == null) {
            studentMapper.insertSelective(finalStudent);
            studentBO.setId(finalStudent.getId());
        } else {
            studentMapper.updateByPrimaryKeySelective(finalStudent);
        }
        // 2. 申请借阅资格
        Qualification qualification = new Qualification();
        qualification.setStudentId(studentBO.getId());
        qualification.setStatus(ExamineEnum.TO_BE_EXAMINE.getCode());
        qualification.setGmtCreate(new Date());
        qualification.setGmtModified(new Date());
        qualificationMapper.insertSelective(qualification);
        return null;
    });
}

二、web层 StudentController

service层实现以后,我们通过创建StudentController对外提供restful API,因为主要是新增,我们使用POST请求,@RequestBody方式,定义StudentVO如下:

@Data
public class StudentVO implements Serializable {
    @NotBlank(message = "学号不能为空")
    private String studentNo;
    @NotBlank(message = "学生姓名不能为空")
    private String studentName;
    @NotBlank(message = "学生昵称不能为空")
    private String nickName;
    @NotBlank(message = "学生所属院系不能为空")
    private String department;
    @NotBlank(message = "学生证照片不能为空")
    private String idCardImage;
}

这里使用了前面讲过的Spring Validation constraints中的@NotBlank注解,这个注解适用于String类型,加了以后不能为null,并且trim()以后 size>0。

定义API:apply代码如下:

@PostMapping("/apply")
public TgResult<StudentBO> apply(@Valid @RequestBody StudentVO studentVO) {
    Integer userId = AuthContextInfo.getAuthInfo().loginUserId();
    StudentBO studentBO = CopyUtils.copy(studentVO, StudentBO::new);
    studentBO.setUserId(userId);
    studentService.apply(studentBO);
    return TgResult.ok(studentBO);
}

三、测试

新注册一个学生账号li2gou,userId = 3。

在这里插入图片描述

调用上文的查询学生信息API,返回为空。

调用本文实现的apply API,结果如下:

在这里插入图片描述

看一下数据库student表

在这里插入图片描述

看一下数据库qualification表

在这里插入图片描述

一切如期望一样,再试一下重复提交
在这里插入图片描述
最后再试一下insert student成功,insert qualification 时抛出异常,看看是否会回滚student,我测试时是将verify_user_id在数据库表临时设置为不允许为空,最后成功回滚~

OK,一切如期望一样,收工!


最后

看到这,觉得有帮助的,刷波666,感谢大家的支持~

想要看更多实战好文章,还是给大家推荐我的实战专栏–>《基于SpringBoot+SpringCloud+Vue前后端分离项目实战》,由我和 前端狗哥 合力打造的一款专栏,可以让你从0到1快速拥有企业级规范的项目实战经验!

具体的优势、规划、技术选型都可以在《开篇》试读!

订阅专栏后可以添加我的微信,我会为每一位用户进行针对性指导!

另外,别忘了关注我:天罡gg ,怕你找不到我,发布新文不容易错过: https://blog.csdn.net/scm_2008

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

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

相关文章

Python第二次作业(2)【控制台界面】

要求&#xff1a;使用Python输出五个控制台界面 第一张&#xff1a; 代码如下&#xff1a; print(" 英雄联盟商城登录界面 ") print("~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") print(" 1.用户登录 &q…

STL常用遍历、查找算法

目录 算法概述 常用遍历算法for_each 常用遍历算法transform 常用查找算法find 常用查找算法find_if 常用查找算法adjacent_find 常用查找算法binary_search 常用查找算法count 常用查找算法count_if 算法概述 算法主要是由头文件<algorithm><functional>…

Linux系统编程-文件

目录 1、系统编程介绍以及文件基本操作文件编程系统调用文件基本读写练习 2、文件描述符以及大文件拷贝文件描述符大文件拷贝对比试验 3、文件实战练习 1、系统编程介绍以及文件基本操作 系统编程是基于Linux封装好的一些函数&#xff0c;进行开发。 Linux文件信息属性在indo…

面试题:集群高并发环境下如何保证分布式唯一全局ID生成?

文章目录 前言问题为什么需要分布式全局唯一ID以及分布式ID的业务需求ID生成规则部分硬性要求ID号生成系统的可用性要求 一般通用解决方案UUID数据库自增主键 集群分布式集群基于Redis生成全局ID策略单机版集群分布式 雪花算法什么是雪花算法结构实现SpringBoot整合雪花算法 前…

【Pm4py第七讲】关于visualization

本节用于介绍pm4py中的可视化函数&#xff0c;包括可视化bpmn、petri、性能图谱、变迁系统等。 1.函数概述 本次主要介绍Pm4py中一些常见的可视化函数&#xff0c;总览如下表&#xff1a; 函数名说明view_alignments(log, aligned_traces[, format])可视化对齐方法 view_bpmn(…

QT之QML开发 行列布局,流布局,网格布局

本节将演示一下QML布局之行列布局&#xff0c;流布局和网格布局 目录 1.行列布局 1.1一列多行 1.2 一行多列 2.流布局 2.1 从左向右&#xff08;默认&#xff09; ​编辑 2.2 从上往下 3.网格布局 1.行列布局 1.1一列多行 // 行列布局 import QtQuick 2.15 import Qt…

前端架构师进阶之路07_JavaScript函数

1 函数的定义与调用 1.1 初识函数 函数是用于封装一段完成特定功能的代码。 相当于将一条或多条语句组成的代码块包裹起来&#xff0c;在使用时只需关心参数和返回值&#xff0c;就能完成特定的功能&#xff0c;而不用了解具体的实现。 // 内置函数 console.log(parseFloat…

基于R语言、MATLAB、Python机器学习方法与案例分析

目录 基于R语言机器学习方法与案例分析 基于MATLAB机器学习、深度学习在图像处理中的实践技术应用 全套Python机器学习核心技术与案例分析实践应用 基于R语言机器学习方法与案例分析 机器学习已经成为继理论、实验和数值计算之后的科研“第四范式”&#xff0c;是发现新规律&…

iOS技术之app审核信息填写联系人信息提交失败

在AppStore Connect中填写联系人信息中联系方式的电话号码时,输入11位手机号,一直提示 此栏无效: 报错一直说明次栏无效, 开始以为手机号不兼容, 换了好多手机号,座机号都不行, 最终尝试正确的输入格式是:86-xxxxxxxxxxx, 前面有""号, 连接用"-"

关于Modbus消息帧,这些内容你都了解吗?

在 Modbus网络通信的两种传输模式中&#xff08; ASCII或RTU&#xff09;&#xff0c;传输设备以将Modbus消息转为有起点和终点的帧&#xff0c;这就允许接收的设备在消息起始处开始工作&#xff0c;读地址分配信息&#xff0c;判断哪一个设备被选中&#xff08;广播方式则传给…

EfficientFormer:高效低延迟的Vision Transformers

我们都知道Transformers相对于CNN的架构效率并不高&#xff0c;这导致在一些边缘设备进行推理时延迟会很高&#xff0c;所以这次介绍的论文EfficientFormer号称在准确率不降低的同时可以达到MobileNet的推理速度。 Transformers能否在获得高性能的同时&#xff0c;跑得和Mobile…

华为云云耀云服务器 L 实例评测:快速建站的新选择,初创企业和开发者的理想之选

华为云云耀云服务器 L 实例评测&#xff1a;快速建站的新选择&#xff0c;初创企业和开发者的理想之选 文章目录 华为云云耀云服务器 L 实例评测&#xff1a;快速建站的新选择&#xff0c;初创企业和开发者的理想之选导语&#xff1a;摘要&#xff1a; 正文产品概述部署简易性步…

TG Pro for Mac强大的硬件温度检测、风扇控制工具测评

无论您是旧机型还是全新MacBookPro&#xff0c;使用TG Pro均可延长Mac的使用寿命。小编就给大家详细说一下使用TG Pro的体验~ 打开TG Pro&#xff0c;您会注意到的第一件事是带有大量温度&#xff0c;风扇速度和诊断信息的主窗口。 这是您将与之交互的应用程序的主要区域之一。…

[动物文学]走红年轻人化身“精神动物”,这届年轻人不想做人了

数据洞察流行趋势&#xff0c;敏锐把握流量风口。本期千瓜与您分享近期小红书八大热点内容&#xff0c;带您看热点、追热门、借热势&#xff0c;为您提供小红书营销布局风向标。 「动物文学」走红 年轻人化身“精神动物” 其实&#xff0c;这届年轻人“不想做人”很久了………

# 深入理解高并发编程(二)

深入理解高并发编程&#xff08;二&#xff09; 文章目录 深入理解高并发编程&#xff08;二&#xff09;synchronized作用使用方法示例代码 ReentrantLock概述示例代码ReentrantLock中的方法 ReentrantReadWriteLock介绍特点示例代码 StampedLock示例代码 wait() 和 notify()示…

软件设计模式系列之十六——命令模式

目录 1 模式的定义2 举例说明3 结构4 实现步骤5 代码实现6 典型应用场景7 优缺点8 类似模式9 小结 1 模式的定义 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;旨在将请求发送者和接收者解耦&#xff0c;将一个请求封装为一个对象&#x…

OmniOutliner 5 Pro for Mac(信息大纲记录工具)v5.12正式版 支持M1、M2

OmniOutliner 5 Pro是一款功能强大的大纲工具&#xff0c;可以帮助用户进行头脑风暴、组织思维和创建结构化的笔记。以下是这款软件的一些主要功能和特点&#xff1a; 大纲模式。OmniOutliner 5 Pro支持全屏模式、分屏模式、实时预览模式和导航模式等多种创作模式&#xff0c;…

RT-Thread 自动初始化机制

RT-Thread自动初始化机制 自动初始化机制是指初始化函数不需要被显示调用&#xff0c;只需要在函数定义处通过宏定义的方式进行申明&#xff0c;就会在系统启动过程中被执行。 例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数&#xff0c;代码如下&#xff1a; …

25814-2010 三聚氯氰 阅读笔记

声明 本文是学习GB-T 25814-2010 三聚氯氰. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了三聚氯氰的要求、采样、试验方法、检验规则以及标志、标签、包装、运输、贮存、安全、 安全技术说明书。 本标准适用于三聚氯氰的产品…

基于springboot+vue的大学生创新创业系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…