Spring学习笔记13 Spring对事务的支持

news2024/11/23 21:48:20

Spring学习笔记12 面向切面编程AOP-CSDN博客

什么是事务:在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全.

多条DML要么同时成功,要么同时失败,叫做事务(Transaction)

事务四个处理过程:

1.开启事务(start transaction)

2.执行核心业务代码

3.提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)

4.回滚事务(如果核心业务处理过程中出现异常)(rollabck transaction)

事务的四个特性:

A.原子性:事务是最小的工作单元,不可再分

C.一致性:事务要求要么同时成功,要么同时失败.事务前和事务后的总量不变.

I.隔离性:事务和事务之间因为有隔离,才可以保证互不干扰

D.持久性:持久性是事务结束的标志.

引入事务场景:

以银行账户转账为例学习事务.两个账户act-01和act-02.

act-01向act-02转账10000.

一个账户减10000,一个账户加10000,必须同时成功,或者同时失败

连接数据库的技术采用Spring框架的JdbcTemplate

新建maven项目或者模块

依赖

<dependencies>
    <!--spring依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--jdbcTemplate依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!--druid德鲁伊依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.13</version>
    </dependency>
    <!--javaee的注解 @Resource依赖-->
    <dependency>
        <groupId>jakarta.annotation</groupId>
        <artifactId>jakarta.annotation-api</artifactId>
        <version>2.1.1</version>
    </dependency>
    <!--单元测试依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

准备数据库表

项目结构

实体类

package com.example.pojo;

import java.util.Objects;

/**
 * @author hrui
 * @date 2023/9/26 15:02
 */
public class Account {
    private Integer id;

    private String actno;

    private Double balance;


    public Account() {
    }

    public Account(Integer id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Objects.equals(id, account.id) && Objects.equals(actno, account.actno) && Objects.equals(balance, account.balance);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, actno, balance);
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}

持久层

package com.example.dao;

import com.example.pojo.Account;

/**
 * 专门负责账户信息的CRUD操作
 * DAO中只执行SQL语句,没有任何业务逻辑.
 * 也就是说DAO不和业务挂钩
 * @author hrui
 * @date 2023/9/26 15:00
 */
public interface AccountDao {

    Account selectByActNo(Integer id);

    int updateAct(Account account);
}

持久层实现类

package com.example.dao.impl;

import com.example.dao.AccountDao;
import com.example.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;


/**
 * @author hrui
 * @date 2023/9/26 15:04
 */
@Repository
public class AccountDaoImpl implements AccountDao {

    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActNo(Integer id) {
        String sql="select id,actno,balance from t_act where id=?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), id);
        return account;
    }

    @Override
    public int updateAct(Account account) {
        String sql="update t_act  set balance=?  where id=?";
        int count = jdbcTemplate.update(sql, account.getBalance(),account.getId());
        return count;
    }
}

业务层接口

package com.example.service;

/**
 * @author hrui
 * @date 2023/9/26 15:55
 */
public interface AccountService {

    void transfer(Integer fid,Integer tid,double balance);
}

业务层实现类

package com.example.service.impl;

import com.example.dao.AccountDao;
import com.example.pojo.Account;
import com.example.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * @author hrui
 * @date 2023/9/26 15:57
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Resource(name="accountDaoImpl")
    private AccountDao accountDao;

    @Override
    public void transfer(Integer fid, Integer tid, double balance) {
        //查询转出账户余额够不够
        Account fAccount = accountDao.selectByActNo(fid);
        if(fAccount.getBalance()<balance){
            throw new RuntimeException("余额不足");
        }
        //余额充足
        Account tAccount = accountDao.selectByActNo(tid);

        //修改内存中两个对象的值
        fAccount.setBalance(fAccount.getBalance()-balance);
        tAccount.setBalance(tAccount.getBalance()+balance);

        int count = accountDao.updateAct(fAccount);
        //模拟异常
        String str=null;
        System.out.println(str.toString());

        count+=accountDao.updateAct(tAccount);
        if(count!=2){
            System.out.println("转账失败,联系银行");
        }

    }
}

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.example"></context:component-scan>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="1:3306/spring6"></property>
        <property name="username" value="1"></property>
        <property name="password" value="1"></property>
    </bean>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

测试类

import com.example.service.AccountService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author hrui
 * @date 2023/9/26 16:14
 */
public class Test {

    @org.junit.Test
    public void transfer(){
        BeanFactory beanFactory=new ClassPathXmlApplicationContext("spring-config.xml");
        AccountService accountServiceImpl = beanFactory.getBean("accountServiceImpl", AccountService.class);
        try {
            accountServiceImpl.transfer(1, 2, 50);
            System.out.println("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面代码中间抛出异常就会导致,一遍转账了,而另一边没收到的情况

以代码逻辑的方式  需要在下面代码中执行1.开启事务  2.执行核心业务逻辑  3.无异常则提交事务

4.有异常则回滚事务

  @Override
    public void transfer(Integer fid, Integer tid, double balance) {
        //1.开启事务

        //2.执行核心业务逻辑
        //查询转出账户余额够不够
        Account fAccount = accountDao.selectByActNo(fid);
        if(fAccount.getBalance()<balance){
            throw new RuntimeException("余额不足");
        }
        //余额充足
        Account tAccount = accountDao.selectByActNo(tid);

        //修改内存中两个对象的值
        fAccount.setBalance(fAccount.getBalance()-balance);
        tAccount.setBalance(tAccount.getBalance()+balance);

        int count = accountDao.updateAct(fAccount);
        //模拟异常
        String str=null;
        System.out.println(str.toString());

        count+=accountDao.updateAct(tAccount);
        if(count!=2){
            System.out.println("转账失败,联系银行");
        }

        //3.如果执行业务流程过程中,没有异常.提交事务

        //4.如果执行业务流程过程中,有异常,回滚事务
    }

Spring对事务的支持

Spring实现事务的两种方式

编程式事务:通过编写代码的方式来实现事务的管理

声明式事务:1.基于注解方式   2.基于XML配置方式

Spring事务管理API

Spring对事务的管理底层实现方式是基于AOP实现的.采用AOP的方式进行了封装.所以Spring专门针对事务开发了一套API,API的核心接口如下

PlatformTransactionManager接口:Spring事务管理器的核心接口.在Spring6中它有两个实现

1.DataSourceTransactionManager:支持JdbcTemplate,Mybatis,Hibernate等事务管理

2.JtaTransactionManager:支持分布式事务管理

如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务.(Srping内置写好了,可以直接使用)

声明式事务基于注解的实现方式

Spring配置文件里配置事务管理器,让SpringIOC容器管理  设置dataSource属性为druid的DataSource实现类

然后在方法上加@Transactional即可

好比有了1 2 3 4的步骤

写在类上,类里面所有方法都有事务控制

写在方法上,单个方法有事务控制

@Transactional注解

事务的传播行为

在a()方法中调用了b()方法,比如a()方法有事务,b()方法也有事务,那么事务是如何传递的?是合并到一个事务?还是另开启一个事务?这就是事务的传播行为

事务一共有七种传播行为:

REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入]

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行[有就加入,没有就不管了]

MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常[有就加入,没有就抛异常]

REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有事务,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起]

NOT_SUPPORTED:以非事务方式运行,如果有事务在,挂起当前事务[不支持事务,存在就挂起]

NEVER:以事务方式运行,如果有事务存在,挂起当前事务[不支持事务,存在就挂起]

NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套事务中.被嵌套的事务可以独立于外层事务进行提交或回滚.如果外层事务不存在,行为就像REQUIRED一样[有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚,没有事务就和REQUIRED一样]

默认是@Transactional(Propagation=Propagation.REQUIRED)

下面两个是常用的:

REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入]

REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有事务,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起]

举例:    A方法和B方法的方法上都有@Transactional(Propagation=Propagation.REQUIRED)默认

在A方法中调用B方法

A(){

        保存操作XXXX;

        B();

}

B方法

B(){

        保存操作

}

那么A方法和B方法在同一个事务中, 即使你在A方法里对B方法进行try catch,无论哪个方法里报错,都会回滚

举例:    A方法是@Transactional(Propagation=Propagation.REQUIRED)默认,B方法的方法上是@Transactional(Propagation=Propagation.REQUIRES_NEW)

A方法内进行保存操作且调用了B方法,假如B方法报错了,且A方法没有对B方法进行try catch那么两个都会回滚,假如B方法报错了,但是A方法内对B方法进行了try catch那么B方法会回滚,而A方法不会回滚,因为是两个事务

事务隔离级别:

数据库中读取数据的三大问题:(三大读问题)

脏读:读取到没有提交的数据,叫脏读(读的是缓存(内存里的东西))

不可重复读:在同一个事务当中,第一次和第二次读到的数据不一样

幻读:督导的数据是假的

事务隔离级别包括四个级别:

读未提交:READ_UNCOMMITTED.这种隔离级别,存在脏读问题.所谓脏读(dirty read)表示能够读取到其他事务还未提交的数据

读提交:READ_COMMITTED.解决了脏读问题,其他事务提交之后才能督导,但存在不可重复读问题.(Oracle数据库的默认级别)

可重复读:REPEATABLE_READ.解决了不可重复读,可以达到可重复读的效果,只要当前事务不结束,读取到的数据一直都是一样的.但存在幻读问题(Mysql数据库的默认级别)

序列化:SERIALIZABLE.解决了幻读问题,事务排队执行.不支持并发

读未提交,读已提交,可重复读都是多线程并发问题引起的,序列化就排队

事务超时问题     @Transactional(timeout=10)

以上代码表示设置事务的超时时间为10秒

表示超过10秒如果该事务中所有的DML(增删改)语句还没有执行完毕,最终结果会选择回滚

默认值-1,表示没有时间限制

这里有个坑,事务的超时时间指的是哪段时间?

在当前事务当中,最后一条DML(增删改)语句执行之前的时间,如果最后一条DML语句后面还有很多业务逻辑,这些业务代码执行的时间不会被计入超时时间

只读事务   代码   @Transactional(readOnly=true)

将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可以执行.

该特性的作用:启动Spring的优化策略,提高select语句执行效率.

如果该事务中确实没有增删改操作,建议设置为只读事务.

异常回滚事务:

代码 例 @Transactional(rollbackFor=NumberFormatException.class)

表示只有发生NumberFormatException异常或该异常的子类异常时才回滚

设置哪些异常不回滚事务:

代码 例 @Transactional(noRollbackFor=NullPointerException.class)

表示发生NullPointerException或该类子类异常不回滚,其他异常则回滚

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

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

相关文章

安防视频平台EasyCVR视频调阅全屏播放显示异常是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Apache DolphinScheduler在中国信通院“2023 OSCAR开源尖峰案例”评选中荣获「尖峰开源项目奖」!

在近日由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;和中国通信标准化协会联合主办的“2023 OSCAR 开源产业大会”上&#xff0c;主办方公布了 2023 年“OSCAR 开源尖峰案例”评选结果&#xff0c;包括“开源人物”“开源项目”“开源社区”“开源企业”…

python+vue实验室课程预约管理系统

实验室课程管理系统运用计算机完成数据收集、查询、修改和删除以及统计等工作&#xff0c;提高了管理者工作效率&#xff0c;避免了因信息量巨大&#xff0c;造成的人为错误&#xff0e;通过前面的功能分析可以将实验室课程管理系统的功能分为管理员、学生和教师三个部分&#…

Linux高性能服务器编程 学习笔记 第八章 高性能服务器程序框架

TCP/IP协议在设计和实现上没有客户端和服务器的概念&#xff0c;在通信过程中所有机器都是对等的。但由于资源&#xff08;视频、新闻、软件等&#xff09;被数据提供者所垄断&#xff0c;所以几乎所有网络应用程序都采用了下图所示的C/S&#xff08;客户端/服务器&#xff09;…

LeetCode_BFS_中等_1926.迷宫中离入口最近的出口

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你一个 m x n 的迷宫矩阵 maze &#xff08;下标从 0 开始&#xff09;&#xff0c;矩阵中有空格子&#xff08;用 ‘.’ 表示&#xff09;和墙&#xff08;用 ‘’ 表示&#xff09;。同时给你迷宫的入口 …

电脑提示vcruntime140.dll缺失重新安装的修复方法

电脑出现 vcruntime140.dll 丢失的情况&#xff0c;通常是由于系统缺失了 Microsoft Visual C Redistributable 的运行库文件。这个文件是许多应用程序在运行时所需的依赖库&#xff0c;如果丢失了该文件&#xff0c;可能会导致某些软件无法正常运行。 下面是关于 vcruntime140…

免费录音软件推荐,告别杂音,音质更清晰!

“求推荐一款免费的录音软件&#xff01;最近下载了好多的录音软件&#xff0c;不是音质太差&#xff0c;就是需要收费解锁新的功能&#xff0c;根本不好用&#xff0c;有没有人知道一款免费优秀的录音软件呀&#xff0c;告诉我一下。” 录音已成为现代人们学习和工作中的一项…

DataExcel控件读取和保存excel xlsx 格式文件

需要引用NPOI库 https://github.com/dotnetcore/NPOI 调用Read 函数将excel读取到dataexcel控件 调用Save 函数将dataexcel控件文件保存为excel文件 using NPOI.HSSF.UserModel; using NPOI.HSSF.Util; using NPOI.SS.UserModel; using NPOI.SS.Util; using System; using …

pytho实例--pandas读取表格内容

前言&#xff1a;由于运维反馈帮忙计算云主机的费用&#xff0c;特编写此脚本进行运算 如图&#xff0c;有如下excel数据 计算过程中需用到数据库中的数据&#xff0c;故封装了一个读取数据库的类 import MySQLdb from sshtunnel import SSHTunnelForwarderclass SSHMySQL(ob…

Java BigDecimal 详解

目录 一、BigDecimal 1、简介 2、构造器描述 3、方法描述 4、使用 一、BigDecimal float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算&#xff0c;这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而&#xff0c;它…

25841-2017 1000kV电力系统继电保护技术导则

声明 本文是学习GB-T 25841-2017 1000kV电力系统继电保护技术导则. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了交流1000 kV 系统及1000 kV 变电站相关电压等级具有特别要求的继电保护装置 的基本准则。 本标准适用于1000 k…

大厂秋招真题【BFS+DP】华为20230921秋招T3-PCB印刷电路板布线【欧弟算法】全网最全大厂秋招题解

题目描述与示例 题目描述 在PCB印刷电路板设计中&#xff0c;器件之间的连线&#xff0c;要避免线路的阻抗值增大&#xff0c;而且器件之间还有别的器任和别的干扰源&#xff0c;在布线时我们希望受到的干扰尽量小。 现将电路板简化成一个M N的矩阵&#xff0c;每个位置&am…

如何快速学习AdsPower RPA(1)——简单、进阶部分

你是否刚开始学习使用AdsPower的RPA功能&#xff1f; 你是否对着这些操作选项头皮发麻&#xff0c;不知所措&#xff1f; 你是否想快速学会RPA&#xff1f; 你是否想编写出满足各种业务场景的RPA流程&#xff1f; 以上这些&#xff0c;Tool哥统统都帮你搞定&#xff01; Too…

科技成果鉴定测试有多重要?可出具专业测试报告的软件测评机构推荐

科技成果鉴定测试在现代社会中具有重要意义&#xff0c;它不仅可以评估科技成果的价值和可行性&#xff0c;还可以为科技创新提供决策依据&#xff0c;推动科技进步和社会发展&#xff0c;那么科技成果鉴定测试究竟重要在哪呢? 1、对于科技项目的投资决策至关重要。鉴定测试可…

YOLOV8-DET转ONNX和RKNN

目录 1. 前言 2.环境配置 (1) RK3588开发板Python环境 (2) PC转onnx和rknn的环境 3.PT模型转onnx 4. ONNX模型转RKNN 6.测试结果 1. 前言 yolov8就不介绍了&#xff0c;详细的请见YOLOV8详细对比&#xff0c;本文章注重实际的使用&#xff0c;从拿到yolov8的pt检测模型&…

玩转gpgpu-sim 04记—— __cudaRegisterBinary() of gpgpu-sim 到底做了什么

官方文档&#xff1a; GPGPU-Sim 3.x Manual __cudaRegisterBinary(void*) 被执行到的代码逻辑如下&#xff1a; void** CUDARTAPI __cudaRegisterFatBinary( void *fatCubin ) { #if (CUDART_VERSION < 2010)printf("GPGPU-Sim PTX: ERROR ** this version of GPGPU…

查看Linux系统信息的常用命令

文章目录 1. 机器配置查看2. 常用分析工具3. 常用指令解读3.1 lscpu 4. 定位僵尸进程5. 参考 1. 机器配置查看 # 总核数物理CPU个数x每颗物理CPU的核数 # 总逻辑CPU数物理CPU个数x每颗物理CPU的核数x超线程数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| w…

电子电路学习笔记之NCV6324BMTAATBG——同步降压转换器

关于同步降压转换器&#xff1f; 是一种广泛应用于各种电子产品中的转换器。它具有输入范围宽、转换效率高、输出电流大等优点。在同步降压型转换器的驱动电路中&#xff0c;一般包括电平移位电路、死区时间控制电路以及过零检测电路等。 电平移位电路用于将固定电平Vcc和GND…

86、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->String相关命令

本次讲解要点&#xff1a; String相关命令&#xff1a;String是指value中的数据类型 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.…

【知识点随笔分析】我看看谁还不会用CURL命令

目录 前言&#xff1a; CURL介绍&#xff1a; CURL的基本使用&#xff1a; CURL与PING命令的区别&#xff1a; CURL命令的应用&#xff1a; 总结&#xff1a; 前言&#xff1a; 当今互联网时代&#xff0c;与服务器进行数据交互成为了无法回避的需求。无论是获取Web…