事务及在SpringBoot项目中使用的两种方式

news2024/11/29 12:37:38

1.事务简介

事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。

事物的四大特性:

  1. 原子性(Atomicity)
    原子性指事务是一个不可分割的工作单位,事务中包括的操作要么全部完成,要么全部不完成,不可能结束在中间某个环节。事务中的所有操作要么全部提交成功,要么在发生错误时全部回滚,撤销对数据库的所有更改。这意味着事务内的操作如果失败了,那么会回滚到事务开始前的状态,就像这些操作从来没有发生过一样。
  2. 一致性(Consistency)
    一致性确保事务将数据库从一个一致的状态转变为另一个一致的状态。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的任何数据都必须满足所有设置的规则,包括约束、触发器、级联更新等。例如,在转账事务中,无论进行多少次转账操作,所有用户的总金额应保持不变。
  3. 隔离性(Isolation)
    多个事务并发执行时,一个事务的操作不应影响其他事务。隔离性通过提供“单独”的环境来保证事务不受其他并发事务的影响。这意味着一个事务内部的操作及使用的数据对并发的其他事务是隔离的,反之亦然。这有助于防止数据在并发操作中产生不一致的问题。
  4. 持久性(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.事物并发问题

在事务处理、数据库操作或任何需要确保数据一致性和完整性的系统中,并发问题特别重要。当多个事务(或用户)同时尝试访问和修改相同的数据时,可能会出现以下问题:

  1. 脏读(Dirty Read):一个事务读取了另一个尚未提交的事务的数据。如果未提交的事务在之后被回滚,那么之前读取的数据将是无效的。
  2. 不可重复读(Non-repeatable Read):在同一个事务内,由于其他事务的修改,导致多次读取同一数据返回的结果有所不同。
  3. 幻读(Phantom Read):一个事务在读取某些行的集合时,另一个事务插入了新的行,导致前一个事务在再次读取相同的范围时看到额外的“幻影”行。
  4. 丢失更新(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框架中定义的事务传播行为:

  1. PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择,通常作为默认的事务传播行为。

  1. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。这意味着方法可以在事务中运行,也可以不在事务中运行,取决于调用它的代码是否处于事务中。
  2. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这要求调用方法必须在一个事务中运行。
  3. PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果当前存在事务,则将其挂起。这意味着无论调用方法是否处于事务中,被调用方法都将在一个新的事务中运行。
  4. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。这意味着方法将不会在一个事务中运行,即使调用它的代码处于事务中。
  5. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。这要求调用方法不能在一个事务中运行。
  6. 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() {
        @Override
        protected 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;  
        }  
    }  
}

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

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

相关文章

编程和计算机基础

编程 编程:就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。 计算机程序:是计算机所执行的一系列的指令集合,而程序全部都是用我们所掌握的语言来编写的,所以人们控制计算机一…

Java实现课程案例资源库系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员需求分析2.2 用户需求分析 三、系统设计3.1 业务流程设计3.1.1 管理员业务流程设计3.1.2 用户业务流程设计3.1.3 首页功能模块及业务流程分析3.1.4 案例资源中心功能模块及业务流程分析3.1.5 用户信息中心功能模块…

2024龙年特别篇 -- 魔法指针 之 指针变量的意义 指针运算

学习完指针变量:链接后, 我们继续学习指针变量的应用 目录 程序展示 原始方式 指针变量方式 代码对比 指针运算 指针-整数 用指针打印数组内容 使用指针打印1-10中的奇数 指针-指针 指针的关系运算 程序展示 打印一个有10个元素的数组&am…

删除 Windows 设备和驱动器中的 WPS网盘、百度网盘等快捷图标

在安装诸如WPS软件、百度云盘、爱奇艺等客户端后,Windows 的“我的电脑”(或“此电脑”)中的“设备和驱动器”部分会出现对应的软件图标。这种情况被许多技术人员视为不必要的干扰,因此许多用户想要知道如何隐藏或删除这些图标。 …

面试技术栈 —— 2024网易雷火暑期实习真题

面试技术栈 —— 2024网易雷火暑期实习真题 1. 最长递增子序列。2. 集中限流和单机限流你觉得哪个好?3. redis部署服务器配置,为什么不用哨兵?4. 讲讲分布式session的原理。5. 数据库:表数据量大了,如何分表&#xff1…

基于四叉树的图像分割算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................... Imgs(dx 1 : dx R1, dy 1 …

Linux学习(三)shell编程

1.echo指令 用于将后面的字体内容回显到控制台,将后面的字体用单引号或双引号引起来都会忽略引号 2.expr数学运算 3.第一个shell脚本 一般shell脚本以.sh为后缀,通过sh命令来执行shell脚本。 4.shell脚本的命令行参数 $1 $2 $3 $4 $5 $6 $7 $8 $9 $0 &…

TeamCity创建git项目Timed out 超时的一个解决办法

问题: 当自己: ping github.com从本地推送到远程仓库浏览器浏览www.github.com ——都没有问题 但是在teamcity创建工程的时候就超时: 或者多试几次,终于成功了,然后构建的时候半途超时报错。。。。。 一种解决办…

用HTML5 Canvas创造视觉盛宴——动态彩色线条效果

目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- 声明文档类型为XHTML 1.0 Transitional -…

【Linux 04】编辑器 vim 详细介绍

文章目录 &#x1f308; Ⅰ 基本概念&#x1f308; Ⅱ 基本操作1. 进入 / 退出 vim2. vim 模式切换 &#x1f308; 命令模式1. 光标的移动2. 复制与粘贴3. 剪切与删除4. 撤销与恢复 &#x1f308; Ⅲ 底行模式&#x1f308; Ⅳ 异常退出 &#x1f308; Ⅰ 基本概念 vim 是一种…

ArduPilot开源飞控之硬件SBC分析

ArduPilot开源飞控之硬件SBC分析 1. 源由2. Companion Computer2.1 APSync【不推荐&#xff0c;无更新】2.2 DroneKit【不推荐&#xff0c;无更新/SDK】2.3 FlytOS【不推荐&#xff0c;闭源】2.4 Maverick【不推荐&#xff0c;闭源】2.5 ROS【专门讨论&#xff0c;开源/复杂】2…

【STM32 CubeMX】串口编程DMA

文章目录 前言一、DMA方式1.1 DMA是什么1.2 CubeMX配置DMA1.3 DMA方式函数使用DMA的发送接收函数 总结 前言 在嵌入式系统中&#xff0c;串口通信是一项至关重要的功能&#xff0c;它允许单片机与外部设备进行数据交换&#xff0c;如传感器、显示器或其他设备。然而&#xff0…

基于GPT-4一键完成数据分析全流程的AI Agent: Streamline Analyst

大型语言模型&#xff08;LLM&#xff09;的兴起不仅为获取知识和解决问题开辟了新的可能性&#xff0c;而且催生了一些新型智能系统&#xff0c;例如旨在辅助用户完成特定任务的AI Copilot以及旨在自动化和自主执行复杂任务的AI Agent&#xff0c;使得编程、创作等任务变得高效…

每日OJ题_递归②_力扣21. 合并两个有序链表

目录 力扣21. 合并两个有序链表 解析代码 力扣21. 合并两个有序链表 21. 合并两个有序链表 难度 简单 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4]…

Three.js学习9:Three.js 响应式设计

当浏览器窗口尺寸变化的时候&#xff0c;Three.js 渲染的场景&#xff0c;也就是 canvas 不会变化。 让 canvas 随着窗口的尺寸变化&#xff0c;可以利用 window 对象的 resize 事件实现响应式变化。 window.onresize function(){winH window.innerHeight;winW window.inn…

leetcode:343.整数拆分

解题思路&#xff1a; 拆分的越多越好&#xff08;暂且认为&#xff09;&#xff0c;尽可能拆成m个近似相等的数&#xff0c;会使得乘积最大 dp含义&#xff1a;将i进行拆分得到最大的积为dp[i] 递推公式&#xff1a;j x dp[i-j](固定j&#xff0c;只通过凑dp[i-j]进而实现所…

C++ 动态规划 计数类DP 整数划分

一个正整数 n 可以表示成若干个正整数之和&#xff0c;形如&#xff1a;nn1n2…nk &#xff0c;其中 n1≥n2≥…≥nk,k≥1 。 我们将这样的一种表示称为正整数 n 的一种划分。 现在给定一个正整数 n &#xff0c;请你求出 n 共有多少种不同的划分方法。 输入格式 共一行&…

SpringCloud第二天

1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#x…

【AIGC】Stable Diffusion之模型微调工具

推荐一款好用的模型微调工具&#xff0c;cybertron furnace 是一个lora训练整合包&#xff0c;提供训练 lora 模型的工具集或环境。集成环境包括必要的依赖项和配置文件、预训练脚本&#xff0c;支持人物、二次元、画风、自定义lora的训练&#xff0c;以简化用户训练 lora 模型…

Linux中sigaction函数和SIGCHLD信号的使用

sigaction函数&#xff1a; 函数说明&#xff1a;注册一个信号处理函数 函数原型&#xff1a;int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 函数参数&#xff1a; signum:捕捉的信号act:传入参数&#xff0c;…