事务及在SpringBoot项目中使用的两种方式
1.事务简介
事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事物的四大特性:
- 原子性(Atomicity):
原子性指事务是一个不可分割的工作单位,事务中包括的操作要么全部完成,要么全部不完成,不可能结束在中间某个环节。事务中的所有操作要么全部提交成功,要么在发生错误时全部回滚,撤销对数据库的所有更改。这意味着事务内的操作如果失败了,那么会回滚到事务开始前的状态,就像这些操作从来没有发生过一样。- 一致性(Consistency):
一致性确保事务将数据库从一个一致的状态转变为另一个一致的状态。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的任何数据都必须满足所有设置的规则,包括约束、触发器、级联更新等。例如,在转账事务中,无论进行多少次转账操作,所有用户的总金额应保持不变。- 隔离性(Isolation):
多个事务并发执行时,一个事务的操作不应影响其他事务。隔离性通过提供“单独”的环境来保证事务不受其他并发事务的影响。这意味着一个事务内部的操作及使用的数据对并发的其他事务是隔离的,反之亦然。这有助于防止数据在并发操作中产生不一致的问题。- 持久性(Durability):
一旦事务提交,则其结果永久保存在数据库中。即使系统崩溃、重启或发生故障,数据库还能恢复到事务成功结束时的状态。这意味着一旦事务被提交,它对数据库中数据的改变就应该是永久性的。
2.数据库中使用事务
需求:账号A向账号B转账,账户A更新余额后账户B也同时更新余额
初始数据:
操作语句:
update account set balance=1000 where account_number='A';update account set balance=3000 where account_number='B';
操作结果:
问题:当在执行B失败时,数据库数据异常,如下:
问题:A更新数据,B数据却未更新。
解决方案:事务引入,将这两个操作作为一个事务,两个事务必须同时成功才能写入数据库。
#开启事物
start transaction ;
update account set balance=1000 where account_number='A';update ;#抛出异常update account set balance=3000 where account_number='B';
#提交事物,如果执行成功则同时写入数据库
commit ;
#回滚事物,如果执行失败则返回到数据库执行开始状态
ROLLBACK ;
执行结果:
3.事物并发问题
在事务处理、数据库操作或任何需要确保数据一致性和完整性的系统中,并发问题特别重要。当多个事务(或用户)同时尝试访问和修改相同的数据时,可能会出现以下问题:
- 脏读(Dirty Read):一个事务读取了另一个尚未提交的事务的数据。如果未提交的事务在之后被回滚,那么之前读取的数据将是无效的。
- 不可重复读(Non-repeatable Read):在同一个事务内,由于其他事务的修改,导致多次读取同一数据返回的结果有所不同。
- 幻读(Phantom Read):一个事务在读取某些行的集合时,另一个事务插入了新的行,导致前一个事务在再次读取相同的范围时看到额外的“幻影”行。
- 丢失更新(Lost Update):两个事务都读取了同一数据,然后都对其进行了修改,但第二个事务的修改覆盖了第一个事务的修改,导致第一个事务的修改丢失。
为了解决这些问题,数据库管理系统(DBMS)通常提供事务隔离级别来控制并发事务之间的交互。这些隔离级别包括:
- 读未提交(Read Uncommitted):允许读取尚未提交的事务的数据。这是隔离级别最低的情况,可能导致上述所有并发问题。
- 读已提交(Read Committed):只允许读取已提交的事务的数据。这可以防止脏读,但可能仍然会出现不可重复读和幻读。
- 可重复读(Repeatable Read):确保在同一个事务内多次读取同一数据返回的结果是一致的。这可以防止脏读和不可重复读,但可能仍然会出现幻读。
- 串行化(Serializable):这是最高的隔离级别,它强制事务序列化执行,从而防止上述所有并发问题。但这也可能导致性能下降,因为事务需要等待其他事务完成。
4.springboot项目中使用声明式事务
在Spring框架中,声明式事务管理是一种更简洁和声明性的方法来管理事务,通常是通过注解来实现的。声明式事务将事务管理的逻辑与业务逻辑分开,使开发者无需关心事务的底层细节。在Spring Boot中,声明式事务管理主要通过@Transactional注解来实现。
下面是如何在Spring Boot中使用声明式事务的步骤:
1.启用事务管理
确保在启动类或配置类上添加了@EnableTransactionManagement注解,以启用Spring的事务管理功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication
@EnableTransactionManagement
public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
2.使用@Transactional注解
在需要事务管理的方法或类上使用@Transactional注解。这个注解告诉Spring该方法需要在事务的上下文中执行。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; @Service
public class MyService { // ... 依赖注入和数据访问层代码 @Transactional public void performTransactionalTask() { // 在这里执行你的业务逻辑 // 如果抛出运行时异常,事务会自动回滚 }
}
@Transactional注解可以应用于方法或类级别。当应用于类级别时,它会影响类中的所有公共方法。
3.配置事务属性
@Transactional注解支持多种属性,用于定义事务的传播行为、隔离级别、超时和只读属性。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, timeout = 30, readOnly = false)
public void performTransactionalTask() { // ...
}
- propagation:定义事务的传播行为,例如Propagation.REQUIRED表示当前方法必须在一个事务中运行,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- isolation:定义事务的隔离级别,如Isolation.DEFAULT使用数据库默认的事务隔离级别。
- timeout:定义事务的超时时间,单位是秒。
- readOnly:指定事务是否只读。如果设置为true,则事务只读取数据而不修改数据。
4.异常处理
默认情况下,如果在事务方法内部抛出了运行时异常(RuntimeException),Spring会触发事务回滚。对于已检查的异常(checked exceptions),Spring不会触发回滚,除非明确指定了@Transactional注解的rollbackFor属性。
@Transactional(rollbackFor = Exception.class)
public void performTransactionalTask() throws Exception { // 如果抛出Exception或其子类,事务会回滚
}
5.事务的传播行为(Propagation Behavior)
定义了当事务方法被另一个事务方法调用时,应如何使用事务。这主要涉及到如何处理嵌套事务的情况。Spring框架提供了多种传播行为选项,这些选项可以通过@Transactional注解的propagation属性来设置。以下是Spring框架中定义的事务传播行为:
- PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择,通常作为默认的事务传播行为。
- PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。这意味着方法可以在事务中运行,也可以不在事务中运行,取决于调用它的代码是否处于事务中。
- PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这要求调用方法必须在一个事务中运行。
- PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果当前存在事务,则将其挂起。这意味着无论调用方法是否处于事务中,被调用方法都将在一个新的事务中运行。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。这意味着方法将不会在一个事务中运行,即使调用它的代码处于事务中。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。这要求调用方法不能在一个事务中运行。
- PROPAGATION_NESTED:如果当前存在事务,则嵌套事务作为当前事务的一个子事务运行;如果当前没有事务,则执行PROPAGATION_REQUIRED行为。这意味着如果在一个事务中调用该方法,那么该方法将在嵌套事务中运行,嵌套事务可以独立回滚而不影响外部事务。
6.事务的回滚
在Java中,使用JDBC或ORM框架(如Hibernate、MyBatis等)时,开发者可以通过调用相应的API来触发事务回滚。例如,在使用JDBC时,可以通过调用Connection对象的rollback()方法来回滚事务。而在使用Spring框架时,可以通过在方法上添加@Transactional注解,并指定rollbackFor属性来指定需要触发回滚的异常类型。
7.实例:
描述: 将用户预约id为1的车位预约信息存入数据库并更新该车位状态,在预约信息存入数据库后抛出错误,观察事务是否成功。
数据库初始状态:
关键代码
/*** 预约处理* @param model* @param request* @return*/@PostMapping("/car/orderDetail")@Transactional(propagation = Propagation.REQUIRED)public String orderDetail(Model model,HttpServletRequest request){String username = request.getParameter("username");String tel = request.getParameter("tel");String parkId = request.getParameter("id");//车位编号String location = request.getParameter("location");String date = request.getParameter("date");String time = request.getParameter("time");String totalprice = request.getParameter("totalprice");String type = request.getParameter("type");String duration = request.getParameter("duration");OrderParking orderParking=new OrderParking();// 拼接日期和时间字符串String datetime_str = date + " " + time;// 定义日期时间格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");// 将拼接后的字符串转换为LocalDateTime对象LocalDateTime datetime_obj = LocalDateTime.parse(datetime_str, formatter);orderParking.setDate(datetime_obj);orderParking.setLocation(location);orderParking.setTel(tel);orderParking.setParkId(Integer.valueOf(parkId));orderParking.setTotalprice(Double.valueOf(totalprice));orderParking.setType(type);orderParking.setUsername(username);// 计算截止时间LocalDateTime endTime = datetime_obj.plusSeconds(Long.parseLong(duration)*3600L);orderParking.setDeadtime(endTime);orderParking.setDuration(Double.valueOf(duration));orderParking.setType(type);//判断是否可以预约int status1 = orderService.findStatus(Integer.parseInt(parkId));if(status1==1){model.addAttribute("msg", "该车位已预约");model.addAttribute("target", "http://"+address+":"+port+"/order/list");return "/operate-result";}//将预约表单存入数据库int i = orderService.addOrderParking(orderParking);//这里模拟异常String s=null;System.out.println(s.toString());//更新车位表状态int status = orderService.updateStatus(Integer.parseInt(parkId));if(status==1&&i==1){model.addAttribute("msg", "预约成功");model.addAttribute("target", "http://"+address+":"+port+"/order/list");return "/operate-result";}model.addAttribute("msg", "预约失败");model.addAttribute("target", "http://"+address+":"+port+"/order/list");return "/operate-result";}
执行结果
预约表中记录并为增添且车位状态并未更新(第二列为预约车位编号)
事务执行成功
5.编程式事务
编程式事务管理指的是在代码中显式地管理事务的边界和逻辑,而不是依赖于声明式的方式(如注解)。在编程式事务管理中,开发者需要手动开始、提交或回滚事务。Spring框架提供了TransactionTemplate和PlatformTransactionManager等API来支持编程式事务管理。
以下是使用编程式事务管理的基本步骤:
1.配置事务管理器
首先,你需要在Spring配置中定义一个PlatformTransactionManager的bean。这通常是基于你使用的数据源类型(如JDBC、Hibernate、JPA等)来决定的。
例如,对于JDBC,你可以这样配置一个DataSourceTransactionManager:
2.使用TransactionTemplate
TransactionTemplate是Spring提供的一个帮助类,它简化了编程式事务的使用。你可以创建一个TransactionTemplate的bean,并注入你的事务管理器。然后,你可以在你的服务代码中使用这个TransactionTemplate来执行需要事务管理的操作。
这里是转账的逻辑
@Autowired
OrderMapper orderMapper;
public void update(String A,int aBalance,String B,int bBalance){transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {// 在这里执行你的业务逻辑orderMapper.updateAccount(A,aBalance);String s=null;System.out.println(s.toString());orderMapper.updateAccount(B, bBalance);// 如果抛出运行时异常,事务会自动回滚} catch (Exception e) {// 可以选择手动回滚status.setRollbackOnly();throw e;}}});}
3.手动管理事务
除了使用TransactionTemplate,你还可以直接使用PlatformTransactionManager来手动开始、提交和回滚事务。这通常是通过TransactionStatus对象来完成的。
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.PlatformTransactionManager; @Service
public class MyService { private final PlatformTransactionManager transactionManager; public MyService(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void performManualTransactionalTask() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // 在这里执行你的业务逻辑 // 如果抛出运行时异常,事务会自动回滚 transactionManager.commit(status); } catch (Exception e) { // 发生异常,手动回滚事务 transactionManager.rollback(status); throw e; } }
}
相关文章:

事务及在SpringBoot项目中使用的两种方式
1.事务简介 事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 事物的四大特性: 原子性(Atomicity)…...

stm32--笔记
一、引脚与变量 二、STM32时钟 [STM32-时钟系统详解_stm32时钟_KevinFlyn的博客-CSDN博客] 三、定时器中断实验 1、定时器中断实验 stm32关于通用定时器的周期、频率计算公式_stm32tim频率计算_胶囊咖啡的博客-CSDN博客 【STM32】通用…...

2024前端面试准备之CSS篇(二)
全文链接 1. 什么是伪类和伪元素 伪类(Pseudo-class): 伪类是选择器的一种,用于选择特定状态或条件下的元素。它们以冒号(:)开头,用于向选择器添加额外的特定条件。例如,:hover伪类用于选择鼠标悬停在元素上的状态,:nth-child(n)伪类用于选择父元素下的第n个子元素等。…...

轨道交通信号增强与覆盖解决方案——经济高效,灵活应用于各类轨道交通场景!
方案背景 我国是世界上轨道交通里程最长的国家,轨道交通也为我们的日常出行带来极大的便利。伴随着无线通信技术的快速发展将我们带入电子时代,出行的过程中对无线通信的依赖程度越来越高,无论是车站还是车内都需要强大、高质量的解决方案以…...

学习数据接构和算法的第10天
题目讲解 尾插 #include <stdio.h> #include <stdlib.h> // 定义顺序表结构 #define MAX_SIZE 100 struct ArrayList {int array[MAX_SIZE];int size; // 当前元素个数 }; // 初始化顺序表 void init(struct ArrayList *list) {list->size 0; // 初始时元素个…...

初识KMP算法
目录 1.KMP算法的介绍 2.next数组 3.总结 1.KMP算法的介绍 首先我们会疑惑,什么是KMP算法?这个算法是用来干什么的? KMP(Knuth-Morris-Pratt)算法是一种用于字符串匹配的经典算法,它的目标是在一个主文本…...

Javaweb之SpringBootWeb案例之AOP概述及入门的详细解析
2.1 AOP概述 什么是AOP? AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。 那什么又是面向方法编程呢,为什么又需要面向…...

【Java代码洁癖】NO.2 单元测试mock显式赋值,不能忍
反例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean new SomeBean(); } 正例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean ; } 解读 使用Mock注解的对象不应该被显式赋值,应当…...

2024.2.19
使用fread和fwrite完成两个文件的拷贝 #include<stdio.h> #include<stdlib.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./tset.txt","w"))NULL){perror("open error");retur…...

B端系统升级方案模板:针对美观性和体验性升级(总体方案)
大家好,我是大美B端工场,专注于前端开发和UI设计,有需求可以私信。本篇从全局分享如何升级B端系统,搞B端系统升级的有个整体思维,不是说美化几个图标,修改几个页面就能解决的,这个方案模板&…...

第九篇:node静态文件服务(中间件)
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 📘 引言: 当今互联网时代&am…...

软件测试-功能测试-测试流程-如何进行需求评审?对于测试人员来讲,如何从测试的角度评审需求文档?
导言 产品人员编写的需求文档,无疑是一个项目或者一项新功能的开端。需求文档的优劣,直接影响开发人员的代码质量,更会影响到后续的测试工作。所以,我认为,需求评审对于开发质量以及测试质量至关重要,那么…...

无刷电机驱动详解
无刷电机驱动详解 有刷电机和无刷电机字面上理解最大的区别就是有无电刷,实际上区别还有换向器,电刷和换向器的作用是什么?电刷负责在旋转部件与静止部件之间传导电流,换向器则利用旋转惯性周期性的改变线圈中电流的方向。 所以…...

Linux+Win双系统远程重启到Win
背景 电脑安装了双系统(ubuntu 22.04 win11),默认进入ubuntu系统。给电脑设置了WoL(Wake-on-LAN),方便远程开机远程控制。 但是ubuntu的引导程序grub无法远程控制,远程开机会默认进入ubuntu。 虽然说可以进入ubuntu后…...

【XR806开发板试用】+移植rosserial到XR806
1 XR806简介 板子来源于极术社区的试用,XR806的在线网址 其主要参数: 主控XR806AF2LDDRSIP 288KB SRAM存储SIP 160KB Code ROM. SIP 16Mbit Flash.天线板载WiFi/BT双天线,可共存按键reboot按键 1,功能按键 1灯红色电源指示灯 1…...

JSON协议详解、语法及应用
文章目录 一、什么是JSON二、JSON协议结构协议结构包括要素JSON语法规则JSON的协议结构示例 三、JSON的特点四、JSON常见应用场景 一、什么是JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以易于阅读和编写的文本格式…...

kubeasz部署k8s:v1.27.5集群
安装k8s集群相关系统及组件的详细版本号 Ubuntu 22.04.3 LTS k8s: v1.27.5 containerd: 1.6.23 etcd: v3.5.9 coredns: 1.11.1 calico: v3.24.6 安装步骤清单: 1.deploy机器做好对所有k8s node节点的免密登陆操作 2.deploy机器安装好python2版本以及pip,…...

RSA加密,解密,加签及验签
目录 1.说明 2.加密和加签的区别 3.后端加密,解密,加签及验签示例 4.前端加密,解密,加签及验签示例 5.前端加密,后端解密,前端加签,后端验签 6.注意事项 1.说明 RSA算法是一种非对称加密…...

【C++搜索】BFS:走迷宫
题目描述 一个迷宫由R行C列格子组成,有的格子里有障碍物,不能走;有的格子是空地,可以走。 给定一个迷宫,求从左上角走到右下角最少需要走多少步(数据保证一定能走到)。只能在水平方向或垂直方向走,不能斜着…...

SpringMVC 的参数绑定之list集合、Map
标签中name属性的值就是pojo类的属性名 参数绑定4 list [对象] <form action"teaupd.do" method"post"> <c:forEach items"${list}" var"tea" varStatus "status"> 教师编号:<input…...

Code Composer Studio (CCS) - Current and Local Revision
Code Composer Studio [CCS] - Current and Local Revision References 鼠标放在文件内的任意位置,鼠标右键 -> Compare With -> Local History -> Revision Time. References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/...

Vue实现多个input输入,光标自动聚焦到下一个input
遇到一个需求,需要实现和移动端短信输入一样,输入内容后,光标会进入下一个输入框 需要用到2个事件 keydown事件发生在键盘的键被按下的时候 keyup 事件在按键被释放的时候触发 <template><div class"box"><el-fo…...

人工智能技术应用笔记(二):OpenAI SORA文生视频模型技术报告全文中英对照 (GPT4翻译+人工润色)
目录 Video generation models as world simulators(视频生成模型作为世界模拟器) Turning visual data into patches (将视觉数据转换为图像块) Video compression network (视频压缩网络) Spacetim…...

Linux-系统资源管理的命令
目录 查看CPU:more /proc/meminfo 查看内存数据:free -m / free -h 查看系统版本:more /etc/issue 查看操作系统的类型:uname -a 查看主机名称:hostname 查看磁盘空间:df -h 查看某个目录空间…...

Html的<figure><figcaption>标签
Html的<figure><figcaption>标签 示例一: <figure><figcaption>figcaption001, fig标题1 </figcaption><figcaption>figcaption002, fig标题2 </figcaption><div style"width:calc(100px*2); height:calc(100px*2); back…...

Selenium实现多页面切换
当使用 Selenium 进行自动化测试或爬取数据时,有时需要处理多个页面之间的切换。以下是一些可能需要多页面切换的情况: 1、打开新窗口/页面: 在当前页面上点击链接、按钮或执行某些操作时,可能会打开一个新的窗口或页面。此时&a…...

Electron实战之菜单与托盘
菜单、托盘是桌面端应用必备的功能之一,我们通常会在菜单上配置应用常用的:偏好设置、显示隐藏、打开文件等功能,在托盘内设置:退出、重启、帮助等辅助性功能,帮助用户方便快捷地控制应用的一些系统功能。系统托盘实际…...

【Java EE初阶十六】网络原理(一)
在网络原理中主要学习TCP/IP四层模型中的重点网络协议 1. 应用层 1.1 应用程序与协议 应用层是和程序员接触最密切的; 应用程序:在应用层这里,很多时候都是程序员自定义应用层协议(步骤:1、根据需求,明确…...

51_蓝桥杯_led流水灯
一 原理图分析 二 三八译码器工作原理 三八译码器:3个输入控制8路互斥的低电平有效输出。 C B A 输出 0 0 0 Y0 0 0 1 Y1 0 1 0 Y2 0 1 1 Y3 1 0 0 Y4 1 0 1 Y5 1 1 0 Y6 1 1 1 Y7 三 锁存器工作原理 锁存器:当使…...

⭐北邮复试刷题589. N 叉树的前序遍历__DFS (力扣每日一题)
589. N 叉树的前序遍历 给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。 示例 1: 输入:root [1,null,…...