在mysql阶段的文章中,已经介绍过事务了。本篇文章是对mysql事务的总结和对使用Spring框架来实现事务操作的讲解。

事务回顾

什么是事务

事务时一组操作的集合,是一个不可分割的操作。

事务会把所有操作作为一个整体,一起向数据库提交或者撤销操作请求。所以这组操作要么同时成功,要么同时失败。

为什么需要事务

我们在程序开发的时候,会有事务的需求。

比如:转账操作。

第一步:A:-100元

第二步:B:+100元

事务的操作

事务的操作主要有三步:

1、开启事务:start transaction(一组操作开启事务)

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

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

Spring中事务的实现

Spring中的事务实现操作分为两类:

1、编程式事务(手动写代码操作事务)

2、声明式事务(利用注解自动开启和提交事务)

假设现在有需求:用户注册,注册时在日志表中插入一条操作记录。

数据准备:

DROP TABLE
IFEXISTS user_info;
CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR ( 128 ) NOT NULL,`password` VARCHAR ( 128 ) NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '日志表';-- 操作日志表
DROP TABLE
IFEXISTS log_info;
CREATE TABLE log_info (`id` INT PRIMARY KEY auto_increment,`user_name` VARCHAR ( 128 ) NOT NULL,`op` VARCHAR ( 256 ) NOT NULL,`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';

代码准备:

1、创建项目,引入SpringWeb,Mybatis,mysql等依赖

2、配置文件

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 5028driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # 配置打印 MyBatis⽇志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #配置驼峰⾃动转换

实体类:

Userinfo:

@Data
public class Userinfo {private Integer id;private String userName;private String password;private Date createTime;private Date updateTime;
}

Loginfo:

@Data
public class LogInfo {private Integer id;private String userName;private String op;private Date createTime;private Date updateTime;
}

Mapper:

UserinfoMapper:

@Mapper
public interface UserinfoMapper {@Insert("insert into user_info(user_name,password)values (#{userName},#{password})")Integer insert(String userName,String password);
}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op})")Integer insertLog(String name,String op);
}

Service:

UserService:

@Service
public class UserService {@Autowiredprivate UserinfoMapper userinfoMapper;public Integer insert(String userName,String password){return userinfoMapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;public Integer login(String userName,String op){return loginfoMapper.insertLog(userName,op);}
}

Controller:

UserController:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/r1")public boolean login(String userName,String password){userService.insert(userName,password);return true;}
}

Spring编程式事务(了解)

Spring手动操作事务有三个操作步骤:

  • 开启事务
  • 提交事务
  • 回滚事务

SpringBoot内置了两个对象:

  1. DataSourceTransactionManager 事务管理器,用来开启、提交或回滚事务
  2. TransactionDefinition是事务的属性,在获取事务的时候需要将TransactionDefinition传递进去从而获得一个事务TransactionStatus

下面是代码实现:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;//JDBC事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@RequestMapping("/r1")public boolean login(String userName,String password){//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);//用户注册userService.insert(userName,password);//提交事务dataSourceTransactionManager.commit(transactionStatus);//回滚事务
//        dataSourceTransactionManager.rollback(transactionStatus);return true;}
}

使用postMan进行测试:

提交事务:

回滚事务:

刷新之后数据库的数据并没有增加:

Spring声明式事务@Transactional

声明式事务只要在需要事务的方法上添加@Transactional注解就可以实现了。无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。

代码实现:


@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);return true;}
}

日志:

刷新数据库,发现数据插入成功:

修改程序,使它出现异常:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);//制造异常int a = 10/0;return true;}
}

重新测试:

日志:

刷新数据库,发现并没有数据插入:

对比日志:

那如果我们需要让它发生异常时不发生回滚呢?

此时我们可以使用try-catch将异常捕获住,代码修改如下:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {e.getMessage();}return true;}
}

重新测试:

日志:

数据库:

那我们如果在异常捕获后需要事务进行回滚呢?有以下两种方式:

1、重新抛出异常

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {//将异常重新抛出throw e;}return true;}
}

测试:

2、手动回滚事务

使用TransationAspectSupport.currentTransactionStatus()得到当前事务,并使用setRollbackOnly设置setRollbackOnly。

代码:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {//手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return true;}
}

测试:

@Transactional详解

1、rollbackFor

上面我们已经知道了,@Transactional注解会开始事务并且自动提交/回滚事务。

我们将异常类型改为IOException再进行测试:

    @Transactional@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

测试:

此时我们发现虽然程序已经抛出异常,但是事务仍然提交了:

数据库也新增了一条数据:

咦?这是为什么呢?不是抛出异常后,事务就应该自动回滚吗?

这是因为事务回滚的默认是遇到运行时异常进行回滚,我们上面的算数异常就属于运行时异常的子类。因此,能够正常进行回滚。

如何解决呢?

通过@Transactional中的rollbackfor属性进行解决:

    @Transactional(rollbackFor = Exception.class)@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

测试:

可以看到事务发生了回滚:

数据库:

对上面内容的总结:

事务隔离级别

回顾Mysql事务隔离级别

1、读未提交:读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的事务。

因为其他事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称为脏数据,这个问题称之为脏读

脏读问题:

2、读提交:读已提交,也叫提交读,该隔离级别的事务能读取到已提交事务的数据。

该隔离级别不会有脏读问题,但由于在事务执行中可以读取到其他事务提交的结果,所以在不同的时间的相同sql查询可能会得到不同的结果,这种现象叫做不可重复读(前后多次读取,数据内容不一致)

不可重复读:

3、可重复读(mysql默认的隔离级别):事务不会读到其他事务对已有数据的修改,即使其他事务已经提交,也可以确保同一事务多次查询结果一致,但是其他事务新插入的数据,是可以感知到的,这也就引发了幻读问题。

此隔离级别事务执行时,另一个事务成功插入了某条数据,但因为它每次查询的结果都是一样的(修改能查询到是因为它涉及到了表中的所有数据行),所以会导致查询不到这条数据,这个现象称为幻读(前后多次读取,数据总量不同)

幻读:

4、串行化:序列化,事务最高隔离级别。它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率第,所以真正使用的场景并不多。

Spring事务隔离级别

Spring中的事务隔离级别有5种:

1、Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。

2、Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准的READ UNCOMMITTED。

3、Isolation.READ_COMITTED:读已提交,对应SQL标准中的READ COMMITTED。

4、Isolation.REPEATABLE_READ:可重复读,对应SQL标准中REPEATABLE READ。

5、Isolation.SERIALIZABLE:串行化,对应SQL标准中的SERIALIZABLE。

我们可以通过@Transactional中的islation属性设置事务隔离级别:

//设置为读已提交@Transactional(isolation = Isolation.READ_COMMITTED)@RequestMapping("/r3")public boolean login3(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

Spring事务传播机制

事务传播机制:是多个事务方法存在调用关系时,事务时如何在这些方法间进行传播的。

比如:Controller中的方法A调用Service中的方法B,它们都是被@Transactional修饰。A方法运行时,会开启事务,当A调用B时,B方法本身也有事务,此时方法B运行时,是加入A的事务还是在创建一个新的事务呢?

这就涉及到了事务的传播机制。

打个比方,公司的流程管理:

执行任务之前需要先写执行文档,任务执行结束,再写总结汇报。

此时A部门有一项工作是和B部门一起干的,此时B部门是直接使用A部门的文档,还是新建一个文档呢?

事务隔离级别解决的是多个事务同时调用一个数据库的问题:

而事务传播机制解决的是一个事务再多个方法中传递的问题

事务传播级别有哪些

@Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为。

Spring事务传播机制有以下七种:

1、Propagtion.REQUIRED:默认的事务传播级别。如果当前存在事务,则加入该事务。如果没有事务,则创建一个新的事务。

2、Propagtion.SUPPORTS:如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。

3、Propagtion.MANDATORY:强制性。如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常

4、Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起,也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会开启新的事务且开启的事务相互独立,互不干扰。

5、Propagtion.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不使用)。

6、Propagtion.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

7、Propagtion.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务则该取值等价于Propagtion.REQUIRED。

举例记忆:

事务传播机制场景演示

此时,用户注册不仅要在用户表中添加数据,在日志表中也需要进行登记。

REQUIRE

Controller:


@RequestMapping("/user2")
@RestController
public class UserController2 {@Autowiredprivate UserService userService;@Autowiredprivate LogService logService;@Transactional@RequestMapping("/register")public boolean register(String userName,String password){/*** 用户表和注册表的插入理应再Service完成* 此处为了方便,直接在Controller中完成*/if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){return false;}Integer result = userService.insert(userName,password);System.out.println("result:"+result);//插入日志表Integer insert = logService.insert(userName, "用户注册");System.out.println("insert:"+insert);return true;}
}

Service:

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

UserSerVice:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

mapper:

UserinfoMapper:


@Mapper
public interface UserInfoMapper {@Insert("insert into user_info (user_name,password) values (#{userName},#{password})")Integer insert(String userName,String password);}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op}) ")Integer insertLog(String userName,String op);
}

我们尝试在其中的一个Service中制造异常:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造异常int a = 10/0;return result;}
}

测试:

从日志上可以看出,事务发生了回滚:

总结:

REQUIRE_NEW

修改Service代码即可:

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造异常int a = 10/0;return result;}
}

再次进行测试,通过日志可以看到,日志表的事务发生了回滚,而用户表的事务提交了:

总结:

NEVER

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

这里我们不制造异常,但是让Controller存在事务进行测试:

可以看到仍然报了500:

NESTED

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

测试没有异常的情况:

事务得到了提交:

测试有异常发生的情况:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);int a = 10/0;return result;}
}

事务回滚:

看起来NESTED传播机制好像跟REQUIRE机制没什么区别:但实际上NESTED可以实现部分回滚,使得其他事务能够被提交。

部分回滚:


@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);try {int a = 10/0;} catch (Exception e) {//部分回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
}

重新测试:

事务得到提交:

总结:

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

事务管理介绍

为什么要用事务管理在我们同时操作两个或更多个数据库时,可能因为网络等各方面原因导致中间出现异常。造成像对第一个数据库的操作成功了,但是对第二个数据库的操作没有成功。这样数据的完整性就被破坏了。事务:是一组操作的集合,…

Android 之 ViewBinding 实现更安全、高效的视图绑定

​​一、配置说明​​​​作用位置​​需在模块级 build.gradle或 build.gradle.kts文件的 android {}块内添加:android {buildFeatures {viewBinding true // Kotlin DSL 语法} }android {buildFeatures {viewBinding true // Groovy 语法} }​​生成规则​​为每…

全球首款Java专用AI开发助手实测:一句话生成完整工程代码——飞算 JavaAI

🌟 嗨,我是Lethehong!🌟🌍 立志在坚不欲说,成功在久不在速🌍🚀 欢迎关注:👍点赞⬆️留言收藏🚀🍀欢迎使用:小智初学计算机…

Shader开发(七)创建第一个Shader项目

在前面的章节中,我们已经了解了Shader的基本概念和渲染管线的工作原理。现在,是时候动手实践了!本章将带您一步步创建第一个Shader项目,开启真正的Shader开发之旅。 为什么选择openFrameworks? 与其他文章不同&#x…

IAR软件中测量函数执行时间

通常在调试代码中需要直到某个函数或者某段代码的实际执行时间,在IAR中可以直接借助软件提供的工具来计算代码执行时间。 第一种方法 进入仿真调试界面,在需要测量的代码前面打断点。工具栏中选择 ST-LINK — Data Log Summary在 Data Log Summary 窗口中…

Java 字节码文件(.class)的组成详解

文章目录基础信息常量池字段方法属性字节码文件内容说明案例文件基本信息类的基本信息常量池字段信息构造方法实例方法主方法源文件信息字节码文件由五部分组成,分别是基础信息、常量池、字段、方法、属性。案例: public class Main implements Interfa…

C++之vector类的代码及其逻辑详解 (下)

1. insert()这个就是在指定位置插入一个元素,首先计算要插入的这个位置和开头之间的距离,接着判断那个_finish 有没有碰到_endofstorage 或者_endofstorage 是不是为0,如果满足条件,那就进行扩容,然后接着重新计算距离…

【自动化测试】Python Selenium 自动化测试元素定位专业教程

1. 引言:元素定位在 Selenium 中的核心地位 元素定位是 Selenium 自动化测试的基础,所有用户交互操作(如点击、输入、选择)都依赖于准确识别页面元素。Selenium WebDriver 提供了多种定位策略,从简单的 ID 定位到复杂…

通用代码自用

多文件上传public int save(Role role, RequestParam("nfile") MultipartFile nfile, HttpServletRequest request) {System.out.println(nfile.getOriginalFilename());String path request.getSession().getServletContext().getRealPath("/upload");Fi…

生成式AI如何颠覆我们的工作和生活

原问题: ​你觉得生成式AI未来会如何改变普通人的工作和生活?​ 做过一个对比国外和国内工业化产品制造的简单调研,类似一款定制化的台灯或者语音音响,从零到原型实物, 美国至少需要20万美刀,国内成本大概…

K8S、Docker安全漏洞靶场

1 介绍 一个脆弱基础设施自动化构建框架,主要用于快速、自动化搭建从简单到复杂的脆弱云原生靶机环境。 1.1 项目的缘起 在研究漏洞时,我们经常会发现“环境搭建”这一步骤本身就会占用大量的时间,与之相比,真正测试PoC、ExP的时间可能非常短。由于许多官方镜像在国内的…

使用Nginx部署前后端分离项目

使用Nginx部署前后端分离项目:用户中心系统实践指南 部署前的关键准备 在正式部署前,务必确保前后端在生产环境能正常运行: 前端:测试所有API请求路径和生产环境配置后端:验证数据库连接、环境变量和外部服务集成完整流…

当前就业形势下,软件测试工程师职业发展与自我提升的必要性

软件测试行业正处于深刻变革期,2025年的市场已超越400亿美元规模,预计2027年将增长7% 。在这个技术驱动、效率至上的时代,测试工程师若想保持竞争力,必须主动拥抱变革,系统性提升技能。通过深入分析行业现状与人才需求…

java 之 继承

一、继承 1.1 、什么是继承? 继承就是把所有的类的公共部分(相同的成员)提取出来,放到一个类中继承需要使用 extends 关键字 public class Animal{ public String name; } public class Dog extends Animal{}Dog 是 An…

强化应急通信生命线:遨游三防平板、卫星电话破局极端灾害救援

暴雨倾盆,山洪咆哮,城市陷入内涝。今年进入汛期以来,我国广东、福建、河南、陕西、京津冀等地相继遭遇暴雨、洪涝、山洪等灾害,道路损毁、基站断网、电力中断等次生问题为应急响应带来严峻挑战。如何保障极端场景下的通信畅通&…

【Linux系统】进程间通信:命名管道

1. 匿名管道的限制匿名管道存在以下核心限制:仅限亲缘关系进程:只能用于父子进程等有血缘关系的进程间通信(如通过 fork() 创建的子进程)。单向通信:数据只能单向流动(一端写,另一端读&#xff…

Python Day24 多线程编程:核心机制、同步方法与实践案例

一、线程事件对象(threading.Event)threading.Event 用于实现线程间的通信,可让一个线程通知其他线程终止任务,核心是通过 “事件触发” 机制协调线程行为。核心方法:创建事件对象:event threading.Event(…

007 前端( JavaScript HTML DOM+Echarts)

一.html dom运用查找html元素的三种方式通过 id 找到 HTML 元素通过标签名找到 HTML 元素通过类名找到 HTML 元素1.通过 id 找到 HTML 元素<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>msf的网页</title> <…

实习文档背诵

实习内容:1.定时任务与数据补全:基于 XXL-JOB 实现分布式定时任务调度&#xff0c;补全近半年历史操作日志数据&#xff0c;有效解决因网络异常导致的数据缺失问题。业务场景&#xff1b;集团的4a日志半年内没有同步&#xff0c;这边需要把日志数据同步到集团上首先先评估每天的…

分布式CAP定理

CAP 定理在一个分布式系统中&#xff0c;以下三个特性不可能同时完全满足&#xff0c;最多只能满足其中两个&#xff1a;C&#xff08;Consistency&#xff0c;一致性&#xff09;&#xff1a;所有节点在同一时间看到的数据是完全一致的&#xff08;即更新操作成功并返回后&…