当前位置: 首页 > news >正文

MySQL的事务以及springboot中如何使用事务

事务的四大特性:

概念:

事务 是一组操作的集合,它是不可分割的工作单元。事务会把所有操作作为一个整体,一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

 注意:

默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务。

事务的特性:

原子性:

  事务是最小的执行单元,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。不允许部分成功和失败。 

 一致性:

确保从一个正确的状态转换到另外一个正确的状态。举例,张三把钱转账给李四100元,张三少了100,李四多了100.但是他俩的钱加起来的钱数,还是和转账之前加起来的钱数相同。

隔离性:

并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。

持久性:

事务被提交之后,对数据库中数据的改变是持久的,即使数据库发生故障,也不会对其有影响。

操作:

事务执行的三步操作:

开启事务、提交事务/回滚事务

-- 开启事务
start transaction; / begin;
-- 1. 保存员工基本信息
insert into emp values (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, now(), now());
-- 2. 保存员工的工作经历信息
insert into emp_expr(emp_id, begin, end, company, job) values (39,'2019-01-01', '2020-01-01', '百度', '开发'),                                                              (39,'2020-01-10', '2022-02-01', '阿里', '架构');
-- 提交事务(全部成功) / 回滚事务(有一个失败)
commit; / rollback;

事务之间的相互影响:

脏读,不可重复读,幻读,丢失更新

 

通过我们的案例,来演示数据库事务:

在EmpController编写,添加员工的接口:

    /*** 新增员工的数据* @param emp* @return* @throws Exception*/@PostMappingpublic Result add(@RequestBody Emp emp) throws Exception {log.info("新增员工数据:{}",emp);empService.add(emp);return Result.success();}

编写EmpService接口:

    /*** 新增员工信息* @param emp*/void add(Emp emp) throws Exception;

编写 EmpServiceImpl的实现类:

    @Overridepublic void add(Emp emp) throws Exception {//先添加员工的基本信息emp.setCreateTime(LocalDateTime.now());  //赋值初始值emp.setUpdateTime(LocalDateTime.now());  //赋值初始值empMapper.addEmp(emp);//在添加员工的工作经历信息List<EmpExpr> exprList = emp.getExprList();if (!CollectionUtils.isEmpty(exprList)){  //当工作经历不是空的时候,在进行添加exprList.forEach(expr ->{expr.setEmpId(emp.getId());});}// 批量添加员工的经历empExprMapper.insertBatch(exprList);}

 注意,我们在添加员工的时候,还需要添加员工的工作经历。

@Insert("insert into emp (username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) values" +"(#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime})")
void addEmp(Emp emp);

还需要编写 EmpExprMapper接口:

    /*** 批量添加员工经历的数据* @param exprList*/void insertBatch(List<EmpExpr> exprList);
主键返回:

在添加员工的工作经历的时候,我们还需要添加一个字段的值就是 emp_id 但是我们改怎么获取到这个刚添加好的主键id的??

主键返回:

在注解上使用:

@option注解

常用的属性值:

useGeneratedKeys:是否使用主键返回。

keyProperty:返回的id绑定那个属性

示例:

    @Options(useGeneratedKeys = true,keyProperty = "id") //使用主键返回,并把返回的主键赋值给id属性,emp对象的 id属性@Insert("insert into emp (username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) values" +"(#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime})")void addEmp(Emp emp);
xml中使用:

<insert id="addUser"  useGeneratedKeys="true"  keyProperty="id">

        insert into tb_user(username,password) values(#{username},#{password})

</insert>

编写 EmpExprMapper接口:

    /*** 批量添加员工经历的数据* @param exprList*/void insertBatch(List<EmpExpr> exprList);

编写EmpExprMapper.xml

    <insert id="insertBatch">insert into emp_expr(emp_id,begin,end,company,job) values<foreach collection="exprList" item="expr" separator=",">(#{expr.empId},#{expr.begin},#{expr.end},#{expr.company},#{expr.job})</foreach></insert>

在Api测试我们成功添加了,员工的基本信息和员工的工作经历信息:

接下来,我做一些改动,添加员工的基本信息成功之后,手写一个运行时异常的bug

点击提交,然后看看会发生什么情况。

可以看到,员工的基本信息,添加成功了。

但是员工的工作经历,添加失败了。

为什么会失败呢?看看idea的看控制台

可以发现,控制台出现了异常 除0异常。

但是我们想一想,添加员工基本信息的时候,就要把员工的工作经历信息添加上去,这是才能保证数据的完整性和一致性,不能一个成功一个失败。

 这时我们想到了数据库的事务,如果添加员工和添加员工的工作经历都成功了,那么我们才向数据库执行提交 commit,如果其中一个失败了,我们就回滚事务。roooback

我们可以通过spring事务管理,来解决这个问题。

Spring事务管理:

事务控制:

注解: @Transactional

作用:将当前方法交给spring事务进行管理,方法执行前,开启事务,成功执行后提交事务。出现异常回滚事务。

位置:业务(service)层的方法上面,类上,接口上。

作用在接口上:

作用在类上:

作用在方法上:

    @Transactional@Overridepublic PageBean getList(Integer page, Integer pageSize) {PageHelper.startPage(page,pageSize);List<Emp> empList = empMapper.PageList();  //PageHelper后面的第一条SQL语句System.out.println(empList);Page<Emp> emps = (Page<Emp>) empList;return new PageBean(emps.getTotal(),emps.getResult());}

虽然在方法,接口还有实体类上面都可以添加@Transaction注解,但是我的建议是,在一个方法上面添加,因为有的操作,只涉及到了一张表的操作,也不用添加事务,就像添加一张表,要是成功就成功了,失败就失败了。不会保存在数据库里面的。因此不会造成数据的不完整性。

spring事务管理的日志输出:

 开启Spring事务管理的debug级别日志,就可以看到控制台中事务开启、提交、回滚的日志了 

在application.properties配置问价里面添加

开启spring事务管理的debug级别日志logging.level.org.springframework.jdbc.support.JdbcTransactionManager=debug

在添加员工的基本信息的方法上面添加注解:

   @Transactional@Overridepublic void add(Emp emp) throws Exception {try {//先添加员工的基本信息emp.setCreateTime(LocalDateTime.now());  //赋值初始值emp.setUpdateTime(LocalDateTime.now());  //赋值初始值empMapper.addEmp(emp);//在添加员工的工作经历信息List<EmpExpr> exprList = emp.getExprList();if (!CollectionUtils.isEmpty(exprList)){  //当工作经历不是空的时候,在进行添加exprList.forEach(expr ->{expr.setEmpId(emp.getId());});}int i = 1/ 0 ;// 批量添加员工的经历empExprMapper.insertBatch(exprList);}finally {EmpLog empLog = new EmpLog(null,LocalDateTime.now(),emp.toString());  //添加日志empLogService.insertLog(empLog);}}

在测试一下:

可以发现这个时候,已经报错了。

看看数据库里面添加成功里面数据没有:

可以发现员工的基本信息也没有添加进去,说明spring事务生效了。让我们来看一下控制台

在执行添加员工的信息的这个方法时,开始了事务,执行添加员工的基本信息后,SQL是执行成功的,但是里面出现了一个除0的异常,后面的添加员工的工作经历的SQL语句,就不执行了。所以在这一个事务中,一个执行成功了,一个执行失败了,事务就没有commit提交,而是rollback回滚了,所以我们在数据库里面并没有看到有数据添加到数据库里面的表中。

现在·我们把这个除0异常注释掉,看看程序执行会不会报错,数据能不能添加到数据库里面。

前后端联调:

可以看到数据添加成功了。

看看ideal的控制台

事务提交了

看看数据库里面的数据,emp表

在看看emp_expr表

 

数据也添加成功了

事务进阶:
属性-rollbackFor
  • 默认情况下,只有出现 RuntimeException 才回滚异常。
  • rollbackFor属性用于控制出现何种异常类型,回滚事务。

如果我们在代码中,添加一个编译时异常,这个时候,spring事务还会回滚吗?

我们可以测试一下,在添加员工的基本信息成功之后,在中间throws一个异常,然后在添加员工的工作经历信息。看看程序会发生什么。

  @Transactional@Overridepublic void add(Emp emp) throws Exception {try {//先添加员工的基本信息emp.setCreateTime(LocalDateTime.now());  //赋值初始值emp.setUpdateTime(LocalDateTime.now());  //赋值初始值empMapper.addEmp(emp);//在添加员工的工作经历信息List<EmpExpr> exprList = emp.getExprList();if (!CollectionUtils.isEmpty(exprList)){  //当工作经历不是空的时候,在进行添加exprList.forEach(expr ->{expr.setEmpId(emp.getId());});}if (true){throw new Exception();}// 批量添加员工的经历empExprMapper.insertBatch(exprList);}finally {EmpLog empLog = new EmpLog(null,LocalDateTime.now(),emp.toString());  //添加日志empLogService.insertLog(empLog);}}

服务器端出现异常:

看看数据库里面的数据。

员工的基本信息还是添加成功了

这是因为,默认情况下,只有出现 RuntimeException 才回滚异常。

可以发现在执行添加员工信息的时候,它commit提交了。

这个时候,需要在@Transaction注解里面添加 rollbaclFor属性了。

 

 @Transactional(rollbackFor = {Exception.class})     //开启事务 spring事务默认只能识别到运行时异常,要是想识别到Exception的异常,需要使用rollbackFor

我们在重启服务器测试:

发现数据添加失败了,但是数据库里面没有新增员工的基本信息。

数据表里面刚刚添加的数据。

我们把刚刚 手动写的异常删掉,在运行程序。

可以发现刚刚添加的数据成功了,

看看数据库里面的数据,emp表

查看emp_expr表。数据添加成功了。

 

属性-propagation:

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

propagation常见的属性值和含义:

  • REQUIRED       [默认值]需要事务,有则加入,无则创建新事务
  • REQUIRES_NEW  需要新事务,,无论有无,都会创建新事务
  • SUPPORTS      支持事务,有则加入,无则在无事务状态中运行
  • NOT_SUPPORTED  不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
  • MANDATORY   必须有事务,否则抛异常
  • NEVER    必须没事务,否则抛异常

下面这个需求是在添加员工的时候,在添加员工的工作经历,然后在数据库里面添加日志信息。

操作日志的这张表,无论我在新增员工时,不管插入成功还是失败,我都要记录操作的日志。

准备实体类:EmpLog

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpLog {private Integer id; //IDprivate LocalDateTime operateTime; //操作时间private String info; //详细信息
}

编写EmpLogMapper接口:

@Mapper
public interface EmpLogMapper {@Insert("insert into emp_log (operate_time, info) values (#{operateTime}, #{info})")public void insert(EmpLog empLog);}

编写EmpLogService接口:

public interface EmpLogService {public void insertLog(EmpLog empLog);}

EmpLogServiceImpl实现类

@Service
public class EmpLogServiceImpl implements EmpLogService {@Autowiredprivate EmpLogMapper empLogMapper;@Overridepublic void insertLog(EmpLog empLog) {empLogMapper.insert(empLog);}
}

在empServiceImpl实现类里面调用:

想一想我们改如何,让一段代码不管怎样都执行了?那就是放在finally代码块里面

在添加完成员工的基本信息后,手动制造一个错误 

    @Transactional(rollbackFor = {Exception.class})     //开启事务 spring事务默认只能识别到运行时异常,要是想识别到Exception的异常,需要使用rollbackFor@Overridepublic void add(Emp emp) throws Exception {try {//先添加员工的基本信息emp.setCreateTime(LocalDateTime.now());  //赋值初始值emp.setUpdateTime(LocalDateTime.now());  //赋值初始值empMapper.addEmp(emp);//在添加员工的工作经历信息List<EmpExpr> exprList = emp.getExprList();if (!CollectionUtils.isEmpty(exprList)){  //当工作经历不是空的时候,在进行添加exprList.forEach(expr ->{expr.setEmpId(emp.getId());});}if(true){throw new Exception();}// 批量添加员工的经历empExprMapper.insertBatch(exprList);}finally {EmpLog empLog = new EmpLog(null,LocalDateTime.now(),emp.toString());  //添加日志empLogService.insertLog(empLog);}}

前后端联调测试:

可以发现报错了。

根据我们上面的猜想,添加日志的操作,不管前面执行成功与否都要执行。

但是数据库里面并没有添加成功,操作日志的记录。

这是为什么呢?我们查看idea的控制台

因为,我们添加员工信息这个实现类里面的方法上,有了一个事务,当我们这个方法里面的出现错误的时候,他要回滚,而添加日志的操作受这个事务的影响,所以我们需要指定,把操作日志记录的,也新建一个事务,使它不受,添加员工信息的影响。

常用的使用场景:

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

 

 在EmpLogServiceImpl实现类里面,添加日志的方法上面。加上@Transaction注解

    @Transactional(propagation = Propagation.REQUIRES_NEW)  //事务传播行为,不管那个方法是否有事务,都会开启新的事务。@Overridepublic void insertLog(EmpLog empLog) {empLogMapper.insert(empLog);}

然后我们在运行,进行前后端联调看效果:

我们发现,程序报错了。看看数据库里面,记录员工操作的日志是否有数据。

可以看到记录日志的操作,在添加员工失败的时候也添加成功了。

分析idea控制台

 在执行分析:

相关文章:

  • Java研学-HTML
  • mysql 导入时遇到 的解决 Variable ‘time_zone‘ can‘t、‘character_set_client‘问题
  • C语言----文件操作(二)
  • 异地现场工控设备,如何实现远程配置、调试?
  • MIT6.5840-2023-Lab2C: Raft-Persistence
  • 深入理解网络 I/O:单 Group 混杂模式|多 Group 主从模式
  • 设计模式——策略模式(Strategy Pattern)
  • 设计模式—策略模式
  • 张正友相机标定法原理与实现
  • 如何将xlsx中的数据通过datagrep导入到mysql数据库表中
  • Spring概述
  • 使用Go实现一个百行聊天服务器
  • leetcode
  • v-md-editor高级使用之自定义目录
  • SpringBoot 3.0 升级之 Swagger 升级
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CSS 三角实现
  • github从入门到放弃(1)
  • Spring框架之我见(三)——IOC、AOP
  • Vue.js-Day01
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 复习Javascript专题(四):js中的深浅拷贝
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 码农张的Bug人生 - 见面之礼
  • 排序(1):冒泡排序
  • 学习笔记TF060:图像语音结合,看图说话
  • 你对linux中grep命令知道多少?
  • 阿里云API、SDK和CLI应用实践方案
  • 带你开发类似Pokemon Go的AR游戏
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • (Java)【深基9.例1】选举学生会
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (算法)Game
  • (一)UDP基本编程步骤
  • (已解决)什么是vue导航守卫
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)scrum常见工具列表
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (转)原始图像数据和PDF中的图像数据
  • .NET DataGridView数据绑定说明
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • @test注解_Spring 自定义注解你了解过吗?
  • []AT 指令 收发短信和GPRS上网 SIM508/548
  • []sim300 GPRS数据收发程序
  • [04] Android逐帧动画(一)
  • [16/N]论得趣
  • [Android]竖直滑动选择器WheelView的实现
  • [BZOJ 4034][HAOI2015]T2 [树链剖分]