事物管理(黑马学习笔记)

事物回顾

在数据库阶段我们已学习过事务了,我们讲到:

事物是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。

怎么样来控制这组操作,让这组操作同时成功或同时失败呢?此时就要涉及到事务的具体操作了。

事务的操作主要有三步:

开启事务(一组操作开始前,开启事务):start transaction / begin ;

提交事务(这组操作全部成功后,提交事务):commit ;

回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;

Spring事务管理

案例

简单的回顾了事务的概念以及事务的基本操作之后,接下来我们看一个事务管理案例:解散部门 (解散部门就是删除部门)

需求:当部门解散了不仅需要把部门信息删除了,还需要把该部门下的员工数据也删除了。

步骤:

    ● 根据ID删除部门数据

    ● 根据部门ID删除该部门下的员工

代码实现:

DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;


    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

DeptMapper

@Mapper
public interface DeptMapper {
    /**
     * 根据id删除部门信息
     * @param id   部门id
     */
    @Delete("delete from dept where id = #{id}")
    void deleteById(Integer id);
}

EmpMapper

@Mapper
public interface EmpMapper {

    //根据部门id删除部门下所有员工
    @Delete("delete from emp where dept_id=#{deptId}")
    public int deleteByDeptId(Integer deptId);
    
}

重启SpringBoot服务,使用postman测试部门删除:

代码正常情况下,dept表和Emp表中的数据已删除

修改DeptServiceImpl类中代码,添加可能出现异常的代码:

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;


    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

重启SpringBoot服务,使用postman测试部门删除:

查看数据库表:

● 删除了2号部门

● 2号部门下的员工数据没有删除

以上程序出现的问题:即使程序运行抛出了异常,部门依然删除了,但是部门下的员工却没有删除,造成了数据的不一致。

原因分析

原因:

● 先执行根据id删除部门的操作,这步执行完毕,数据库表 dept 中的数据就已经删除了。

● 执行 1/0 操作,抛出异常

● 抛出异常之前,下面所有的代码都不会执行了,根据部门ID删除该部门下的员工,这个操作也不会执行 。

此时就出现问题了,部门删除了,部门下的员工还在,业务操作前后数据不一致。

而要想保证操作前后,数据的一致性,就需要让解散部门中涉及到的两个业务操作,要么全部成功,要么全部失败。那我们如何,让这两个操作要么全部成功,要么全部失败呢 ?

那就可以通过事务来实现,因为一个事务中的多个业务操作,要么全部成功,要么全部失败。

此时,我们就需要在delete删除业务功能中添加事务。

在方法运行之前,开启事务,如果方法成功执行,就提交事务,如果方法执行的过程当中出现异常了,就回滚事务。

思考:开发中所有的业务操作,一旦我们要进行控制事务,是不是都是这样的套路?

答案:是的。

所以在spring框架当中就已经把事务控制的代码都已经封装好了,并不需要我们手动实现。我们使用了spring框架,我们只需要通过一个简单的注解@Transactional就搞定了。

Transactional注解

@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。

@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。

@Transactional注解书写位置:

方法

    ○ 当前方法交给spring进行事务管理

    ○ 当前类中所有的方法都交由spring进行事务管理

接口

    ○ 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

接下来,我们就可以在业务方法delete上加上 @Transactional 来控制事务 。

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;

    
    @Override
    @Transactional  //当前方法添加了事务管理
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

在业务功能上添加@Transactional注解进行事务管理后,我们重启SpringBoot服务,使用postman测试:

添加Spring事务管理后,由于服务端程序引发了异常,所以事务进行回滚。

说明:可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

事务进阶

前面我们通过spring事务管理注解@Transactional已经控制了业务层方法的事务。接下来我们要来详细的介绍一下@Transactional事务管理注解的使用细节。我们这里主要介绍@Transactional注解当中的两个常见的属性:

    1.异常回滚的属性:rollbackFor

    2.事务传播行为:propagation

我们先来学习下rollbackFor属性。

rollbackFor

我们在之前编写的业务方法上添加了@Transactional注解,来实现事务管理。

@Transactional
public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int i = 1/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
}

以上业务功能delete()方法在运行时,会引发除0的算数运算异常(运行时异常),出现异常之后,由于我们在方法上加了@Transactional注解进行事务管理,所以发生异常会执行rollback回滚操作,从而保证事务操作前后数据是一致的。

下面我们在做一个测试,我们修改业务功能代码,在模拟异常的位置上直接抛出Exception异常(编译时异常)

@Transactional
public void delete(Integer id) throws Exception {
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        if(true){
            throw new Exception("出现异常了~~~");
        }

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
}

说明:在service中向上抛出一个Exception编译时异常之后,由于是controller调用service,所以在controller中要有异常处理代码,此时我们选择在controller中继续把异常向上抛。

@DeleteMapping("/depts/{id}")
public Result delete(@PathVariable Integer id) throws Exception {
  //日志记录
  log.info("根据id删除部门");
  //调用service层功能
  deptService.delete(id);
  //响应
  return Result.success();
}

重新启动服务后测试:

抛出异常之后事务会不会回滚

使用postman测试,删除5号部门

发生了Exception异常,但事务依然提交了

通过以上测试可以得出一个结论:默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。

假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private EmpMapper empMapper;

    
    @Override
    @Transactional(rollbackFor=Exception.class)
    public void delete(Integer id){
        //根据部门id删除部门信息
        deptMapper.deleteById(id);
        
        //模拟:异常发生
        int num = id/0;

        //删除部门下的所有员工信息
        empMapper.deleteByDeptId(id);   
    }
}

接下来我们重新启动服务,测试删除部门的操作:

控制台日志:执行了删除3号部门的操作, 因为异常又进行了事务回滚

数据表:3号部门没有删除

结论:

    ● 在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。

    ● 如果还需要回滚指定类型的异常,可以通过rollbackFor属性来指定。

propagation

介绍

我们接着继续学习@Transactional注解当中的第二个属性propagation,这个属性是用来配置事务的传播行为的。

什么是事务的传播行为呢?

    ● 就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

例如:两个事务方法,一个A方法,一个B方法。在这两个方法上都添加了@Transactional注解,就代表这两个方法都具有事务,而在A方法当中又去调用了B方法。

所谓事务的传播行为,指的就是在A方法运行的时候,首先会开启一个事务,在A方法当中又调用了B方法, B方法自身也具有事务,那么B方法在运行的时候,到底是加入到A方法的事务当中来,还是B方法在运行的时候新建一个事务?这个就涉及到了事务的传播行为。

我们要想控制事务的传播行为,在@Transactional注解的后面指定一个属性propagation,通过 propagation 属性来指定传播行为。接下来我们就来介绍一下常见的事务传播行为。

属性值含义
REQUIRED【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW需要新事务,无论有无,总是创建新事务
SUPPORTS支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛异常
NEVER

必须没事务,否则抛异常

对于这些事务传播行为,我们只需要关注以下两个就可以了:

    1.REQUIRED(默认值)

    2.REQUIRES_NEW

案例

接下来我们就通过一个案例来演示下事务传播行为propagation属性的使用。

需求:解散部门时需要记录操作日志

由于解散部门是一个非常重要而且非常危险的操作,所以在业务当中要求每一次执行解散部门的操作都需要留下痕迹,就是要记录操作日志。而且还要求无论是执行成功了还是执行失败了,都需要留下痕迹。

步骤:

    1.执行解散部门的业务:先删除部门,再删除部门下的员工(前面已实现)

    2.记录解散部门的日志,到日志表(未实现)

准备工作:

创建数据库表 dept_log 日志表:

create table dept_log(
   	id int auto_increment comment '主键ID' primary key,
    create_time datetime null comment '操作时间',
    description varchar(300) null comment '操作描述'
)comment '部门操作日志表';

实体类:DeptLog

@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {
    private Integer id;
    private LocalDateTime createTime;
    private String description;
}

Mapper接口:DeptLogMapper

@Mapper
public interface DeptLogMapper {

    @Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")
    void insert(DeptLog log);

}

业务接口:DeptLogService

public interface DeptLogService {
    void insert(DeptLog deptLog);
}

业务实现类:DeptLogServiceImpl

@Service
public class DeptLogServiceImpl implements DeptLogService {

    @Autowired
    private DeptLogMapper deptLogMapper;

    @Transactional //事务传播行为:有事务就加入、没有事务就新建事务
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }
}

代码实现:

业务实现类:DeptServiceImpl

@Slf4j
@Service
//@Transactional //当前业务实现类中的所有的方法,都添加了spring事务管理机制
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    
    @Autowired
    private EmpMapper empMapper;

    @Autowired
    private DeptLogService deptLogService;


    //根据部门id,删除部门信息及部门下的所有员工
    @Override
    @Log
    @Transactional(rollbackFor = Exception.class) 
    public void delete(Integer id) throws Exception {
        try {
            //根据部门id删除部门信息
            deptMapper.deleteById(id);
            //模拟:异常
            if(true){
                throw new Exception("出现异常了~~~");
            }
            //删除部门下的所有员工信息
            empMapper.deleteByDeptId(id);
        }finally {
            //不论是否有异常,最终都要执行的代码:记录日志
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("执行了解散部门的操作,此时解散的是"+id+"号部门");
            //调用其他业务类中的方法
            deptLogService.insert(deptLog);
        }
    }
    
    //省略其他代码...
}

测试:

重新启动SpringBoot服务,测试删除3号部门后会发生什么?

    ● 执行了删除3号部门操作

    ● 执行了插入部门日志操作

    ● 程序发生Exception异常

    ● 执行事务回滚(删除、插入操作因为在一个事务范围内,两个操作都会被回滚)

然后在dept_log表中没有记录日志数据

原因分析:

接下来我们就需要来分析一下具体是什么原因导致的日志没有成功的记录。

在执行delete操作时开启了一个事务

当执行insert操作时,insert设置的事务传播行是默认值REQUIRED,表示有事务就加入,没有则新建事务

此时:delete和insert操作使用了同一个事务,同一个事务中的多个操作,要么同时成功,要么同时失败,所以当异常发生时进行事务回滚,就会回滚delete和insert操作

解决方案:

在DeptLogServiceImpl类中insert方法上,添加@Transactional(propagation = Propagation.REQUIRES_NEW)

Propagation.REQUIRES_NEW :不论是否有事务,都创建新事务 ,运行在一个独立的事务中。

@Service
public class DeptLogServiceImpl implements DeptLogService {

    @Autowired
    private DeptLogMapper deptLogMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)//事务传播行为:不论是否有事务,都新建事务
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }
}

重启SpringBoot服务,再次测试删除3号部门:

那此时,DeptServiceImpl中的delete方法运行时,会开启一个事务。 当调用 deptLogService.insert(deptLog) 时,也会创建一个新的事务,那此时,当insert方法运行完毕之后,事务就已经提交了。 即使外部的事务出现异常,内部已经提交的事务,也不会回滚了,因为是两个独立的事务。

到此事务传播行为已演示完成,事务的传播行为我们只需要掌握两个:REQUIRED、REQUIRES_NEW。

REQUIRED :大部分情况下都是用该传播行为即可。

REQUIRES_NEW :当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

经典DP-最长单调子序列

最长递增子序列 思路 定义状态: 我们定义一个数组 dp,其中 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。初始化状态: 对于数组中的每个元素 nums[i],初始时都可以被视为一个长度为1的递增子序列,因此 dp[i] 的…

Mac电脑输入正确密码后提示密码错误

🏝 背景 Mac Pro 在擦键盘时,屏幕一直亮起,导致密码一致输入错误,想来没有什么问题便没有处理。但是!!!在擦完键盘后输入正确的密码依旧提示密码错误😱 接下来就是不断的重启、关机…

如何制作一款建材商城微信小程序

现在,微信小程序已经成为了很多企业和商家开展线上业务的重要渠道之一。对于建材商城而言,制作一款专属的微信小程序可以帮助企业更好地展示产品、提供服务,并增加销售额。下面将介绍如何制作一款建材商城微信小程序。 首先,登录【…

ai作画在线生成!这8个AI生图工具一定要知道。

过去的2023年被称作AI元年,随之而来的2024,被业内人士称之为AI应用元年,即随着大模型和各类AI应用的涌现速度放缓,人们关注的焦点也从产品层面(有哪些好用的AI应用),转移到AI如何更好地赋能实际…

如何下载和配置Linux(使用VMware部署Centos)--看这篇文章就懂了

目录: LinuxLinux概述Linux特点Linux的各个发行版本Linux和Windows区别 Linux的下载和安装安装VMWare虚拟机和Centos安装Centos实现Linux的远程登录使用Xshell连接 Linux Linux概述 Linux内核最初只是由芬兰人林纳斯托瓦兹1991年在赫尔辛基大学上学时出于个人爱好而编写的。 …

会声会影2024出来了吗?

近年来,随着人们对于娱乐和创意的需求不断增长,视频编辑软件也越来越受到大众的关注。其中,会声会影是一款备受欢迎的视频编辑软件,许多用户都在关注其新版本——会声会影2024。 然而,目前并没有官方宣布会声会影2024的…

一文速览深度伪造检测(Detection of Deepfakes):未来技术的守门人

一文速览深度伪造检测(Detection of Deepfakes):未来技术的守门人 前言一、Deepfakes技术原理卷积神经网络(CNN):细致的艺术学徒生成对抗网络(GAN):画家与评审的双重角色…

车载电子电器架构 —— 车辆模式管理

车载电子电器架构 —— 车辆模式管理 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的…

Python算法题集_组合总和

Python算法题集_组合总和 题39:组合总和1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【值传递回溯】2) 改进版一【引用传递堆栈回溯】3) 改进版二【过程值列表缓存遍历后检索】 4. 最优算法5. 相关资源 本文为Python算法题集之一的…

JVM运行流程

⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:JavaEE 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 JVM 1. 运行流程2. 运行时数据区2.1 堆&am…

【精品】集合list去重

示例一&#xff1a;对于简单类型&#xff0c;比如String public static void main(String[] args) {List<String> list new ArrayList< >();list.add("aaa");list.add("bbb");list.add("bbb");list.add("ccc");list.add(…

C++——模板详解

目录 模板 函数模板 显示实例化 类模板 模板特点 模板 模板&#xff0c;就是把一个本来只能对特定类型实现的代码&#xff0c;变成一个模板类型&#xff0c;这个模板类型能转换为任何内置类型&#xff0c;从而让程序员只需要实现一个模板&#xff0c;就能对不同的数据进行操…

4.2 数据的描述性统计

1、总体规模的描述——总量指标 定义&#xff1a;反映在一定时间、空间条件下某种现象的总体规模、总水平或总成果的统计指标。 eg&#xff1a;营业额、利润等 2、总体规模的描述——相对指标 定义&#xff1a;两个有相互联系的指标数值之比 eg&#xff1a;目标完成率&…

GCN 翻译 - 1

ABSTRACT 我们提出了一种可扩展的在以图结构为基础的数据上的半监督学习&#xff0c;这种方法直接作用在图数据上&#xff0c;可以看做是卷积神经网络的变种。我们选择了图谱理论里面的一阶近似作为我们的卷积结构。我们的模型能够随着图的规模线性伸缩&#xff0c;并且隐藏层…

计算机专业大学四年应该如何规划(Java方向)

计算机专业的学生&#xff0c;如何在大学四年内提高自己的竞争力&#xff0c;毕业之后直接进大厂工作&#xff1f; 以下将从大学四年计算机专业的学习规划、课程设置、能力提升、参考书籍等方面&#xff0c;为同学们提供一些建议和指导。 大一&#xff1a; 主攻技能学习并且达…

枚举(蓝桥练习)(反倍数、特别数的和、找到最多的数、小蓝的漆房、小蓝和小桥的挑战)

目录 一、枚举算法介绍 二、解空间的类型 三、循环枚举解空间 四、例题 &#xff08;一、反倍数&#xff09; &#xff08;二、特别数的和&#xff09; &#xff08;三、找到最多的数&#xff09; &#xff08;四、小蓝的漆房&#xff09; &#xff08;五、小蓝和小桥的…

Linpmem:一款功能强大的Linux物理内存提取工具

关于Linpmem Linpmem是一款功能强大的Linux物理内存提取工具&#xff0c;该工具专为x64 Linux设计&#xff0c;可以帮助广大研究人员在执行安全分析过程中快速读取Linux物理内存数据。 该工具类似Windows下的Winpmem&#xff0c;Linpmem不是一个传统的内存转储工具&#xff0…

scons,一个实用的 Python 构建工具!

目录 前言 什么是SCons库&#xff1f; 安装SCons库 使用SCons库 SCons库的功能特性 1. 基于Python的构建描述语言 2. 自动化依赖管理 3. 多种构建环境支持 SCons库的应用场景 1. C/C项目构建 2. Python项目构建 3. 嵌入式系统开发 4. 持续集成环境 5. 跨平台项目构建 总…

如何实现无公网ip远程访问本地安卓Termux部署的MySQL数据库【内网穿透】

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

【InternLM 实战营笔记】大模型评测

随着人工智能技术的快速发展&#xff0c; 大规模预训练自然语言模型成为了研究热点和关注焦点。OpenAI于2018年提出了第一代GPT模型&#xff0c;开辟了自然语言模型生成式预训练的路线。沿着这条路线&#xff0c;随后又陆续发布了GPT-2和GPT-3模型。与此同时&#xff0c;谷歌也…
最新文章