5. Spring 事务

news2024/11/28 14:36:24

文章目录

  • 1. Spring 事务简介
  • 2. Spring 事务角色
  • 3. Spring 事务属性
    • 3.1 事务配置
    • 3.2 案例:转账业务追加日志
    • 3.3 事务传播行为

1. Spring 事务简介

Spring 事务作用:在数据层或业务层保障一系列的数据库操作同成功、同失败。

数据层有事务我们可以理解,为什么业务层也需要处理事务呢?

举个简单的例子:
转账业务会有两次数据层的调用,一次是转入者加钱,一次是转出者减钱;
把事务放在数据层,加钱和减钱就有两个事务;
如果先加钱后减钱,且加钱之后其他地方出现异常,就会出现加钱成功减钱失败的结果。即没办法保证加钱和减钱同时成功或者同时失败;
这个时候就需要将事务放在业务层进行处理。

不用事务时,我们是这样处理的:

在这里插入图片描述

Dao接口

public interface AccountDao {
    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}

Service 接口和实现类

public interface AccountService {
    /**
     * 转账操作
     * @param out 转出方
     * @param in 转入方
     * @param money 余额
     */
    void transfer(String out, String in, Double money);
}

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired//按类型注入
    private AccountDao accountDao;

    @Override
    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out, money);
        accountDao.inMoney(in, money);
    }
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer() {
        accountService.transfer("Tom", "Jerry", 100D);
    }
}

上述代码运行时,会执行转账操作,Tom 的账户减少 100,Jerry 的账户增加 100。

这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:

在这里插入图片描述

这个时候就模拟了转账过程中出现异常的情况,正确的操作应该是 Tom 和 Jerry 账户中的钱不变。但是真正运行后会发现,并没有像我们想象的那样,Tom账户中的钱变少了,而 Jerry 账户中的钱没变。

当程序出问题后,我们需要让事务回滚,且这个事务应该加在业务层上。Spring 的事务管理就是用来解决这类问题的。

Spring事务管理具体的实现步骤为:

(1) 在需要被事务管理的方法上添加注解

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired//按类型注入
    private AccountDao accountDao;

    @Override
    @Transactional//开启事务
    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out, money);
        accountDao.inMoney(in, money);
    }
}

注意:
@Transactional 可以写在接口类上、接口方法上、实现类上和实现类方法上
写在接口类上,该接口的所有实现类的所有方法都会有事务
写在接口方法上,该接口的所有实现类的该方法都会有事务
写在实现类上,该类中的所有方法都会有事务
写在实现类方法上,该方法上有事务
建议写在实现类或实现类的方法上

(2) 在 JdbcConfig 类中配置事务管理器

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean//把方法的返回值定义成一个bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean//把方法的返回值定义成一个bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

注意:事务管理器要根据使用技术进行选择,Mybatis 框架使用的是 JDBC 事务,可以直接使用 DataSourceTransactionManager。

(3) 开启事务注解

@Configuration//该类是配置类
@ComponentScan("com.itheima")//扫描这个包下的类,找bean
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})//引入这些类中的bean
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {

}

(4) 测试

会发现在转账业务出现错误后,事务就可以控制回滚,保证数据的正确性。

2. Spring 事务角色

本节重点理解两个概念,分别是事务管理员事务协调员

(1) 未开启Spring事务之前:
在这里插入图片描述

AccountDao 的 outMoney 因为是修改操作,会开启一个事务 T1;
AccountDao 的 inMoney 因为是修改操作,会开启一个事务 T2;
AccountService 的 transfer 没有事务。

运行过程中如果没有抛出异常,则 T1 和 T2 都正常提交,数据正确;
如果在两个方法中间抛出异常,T1 因为执行成功提交事务,T2 因为抛异常不会被执行,就会导致数据出现错误。

(2) 开启 Spring 事务管理后

在这里插入图片描述

transfer上添加了 @Transactional 注解,在该方法上就会有一个事务 T。
AccountDao 的 outMoney 方法的事务 T1 加入到 transfer 的事务 T 中;
AccountDao 的 inMoney 方法的事务 T2 加入到 transfer 的事务 T 中。
这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。

通过上面例子的分析,可以得到如下概念:

事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法(如:transfer 方法)
事务协调员:加入事务方,在 Spring 中通常指代数据层方法(如:outMoney、inMoney 方法),也可以是业务层方法。

注意:
目前的事务管理的前提是 DataSourceTransactionManager 和 SqlSessionFactoryBean 使用的是同一个数据源。

3. Spring 事务属性

3.1 事务配置

在这里插入图片描述

上面这些属性都可以在@Transactional注解的参数上进行设置。

  • readOnly:true 只读事务,false 读写事务,增删改要设为 false,查询设为 true。
  • timeout:设置超时时间(单位:秒),在多长时间之内事务没有提交成功就自动回滚,-1 表示不设置超时时间。
  • rollbackFor:当出现指定异常时,进行事务回滚。
  • noRollbackFor:当出现指定异常时,不进行事务回滚。
  • rollbackForClassName 等同于 rollbackFor,只不过属性为异常的类全名字符串。
  • noRollbackForClassName 等同于 noRollbackFor,只不过属性为异常的类全名字符串。
  • isolation 设置事务的隔离级别
    DEFAULT:默认隔离级别, 会采用数据库的隔离级别
    READ_UNCOMMITTED:读未提交
    READ_COMMITTED:读已提交
    REPEATABLE_READ:重复读取
    SERIALIZABLE:串行化

rollbackFor 是当出现指定异常时,进行事务回滚;对于异常事务不应该都回滚么,为什么还要指定?

这块需要更正一个知识点,并不是所有的异常都会回滚事务,比如下面的代码就不会回滚,导致转出账户减钱成功,转入账户加钱失败。

在这里插入图片描述

出现这个问题的原因是:Spring 事务只对 Error 异常和 RuntimeException 异常及其子类进行事务回滚,其他的异常类型不回滚。IOException 就是其他的异常类型,所以不回滚。

此时就可以使用 rollbackFor 属性来设置 IOException 异常回滚:

在这里插入图片描述

3.2 案例:转账业务追加日志

需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志

分析:
①:基于转账操作案例添加日志模块,实现数据库中记录日志
②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能

实现效果预期:
无论转账操作是否成功,均进行转账操作的日志留痕

该环境是基于转账环境来完成的,下面在其基础上继续往下写:
在这里插入图片描述

(1) 创建日志表

create table tbl_log(
    id int primary key auto_increment,
    info varchar(255),
    createDate datetime
)

(2) 添加 LogDao 接口

public interface LogDao {
    @Insert("insert into tbl_log (info,createDate) values(#{info},now())")
    void log(String info);
}

(3) 添加 LogService 接口与实现类

public interface LogService {
    void log(String out, String in, Double money);
}

@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;
    @Transactional
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

(4) 在转账的业务中添加记录日志

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired//按类型注入
    private AccountDao accountDao;
    @Autowired
    private LogService logService;//添加日志相关
    @Override
    //开启事务,出现IOException时回滚
    @Transactional
    public void transfer(String out, String in, Double money){
        try{
            accountDao.outMoney(out,money);
            //int i =1/0;
            accountDao.inMoney(in,money);
        }finally {
            logService.log(out,in,money);//添加日志相关
        }
    }
}

在这里插入图片描述

当程序正常运行,tbl_account 表中转账成功,tbl_log 表中日志记录成功;
当转账业务之间出现异常(int i =1/0),转账失败,tbl_account 成功回滚,但 tbl_log 表未添加数据。

这个结果和我们想要的不一样,什么原因?该如何解决?
失败原因:日志的记录与转账操作隶属同一个事务,同成功同失败。

在这里插入图片描述
理想效果:无论转账操作是否成功,日志必须保留

3.3 事务传播行为

前面讲过 Spring 事务会把 T1、T2、T3 都加入到事务 T 中。
所以当转账失败后,所有的事务都回滚,导致日志没有记录下来。
这与需求不符,此时就想能不能让 log 方法单独是一个事务呢。

在这里插入图片描述

要想解决这个问题,就需要用到事务传播行为。
事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

具体如何解决,就需要用到 propagation 属性。

@Service
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

经过上面的修改,当转账失败时,tbl_log 表中的日志记录也会成功。但是记录的内容也是 “转账操作由Tom到Jerry,金额:50.0”,这显然不合理。于是 AccountServiceImpl 中做如下修改:

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired//按类型注入
    private AccountDao accountDao;
    @Autowired
    private LogService logService;//添加日志相关
    @Override
    @Transactional
    public void transfer(String out, String in, Double money){
        try{
            accountDao.outMoney(out,money);
            //int i = 1/0;
            accountDao.inMoney(in,money);
            logService.log(out,in,money);//添加日志相关
        }catch (Exception e){
            logService.log(out,in+"失败",0D);//添加日志相关
            throw new RuntimeException();//使事务回滚
        }
    }
}

这样,转账失败时,记录的日志就变为 “转账操作由Tom到Jerry失败,金额:0.0“。

【注意】当 catch 捕获了异常时,事务不会回滚。如果非得写 catch,需要 catch 后 throw new RuntimeException() 让事务回滚。

事务传播行为的可选值:
在这里插入图片描述

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

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

相关文章

多传感器融合定位十三-基于图优化的建图方法其二

多传感器融合定位十二-基于图优化的建图方法其二3.4 预积分方差计算3.4.1 核心思路3.4.2 连续时间下的微分方程3.4.3 离散时间下的传递方程3.5 预积分更新4. 典型方案介绍4.1 LIO-SAM介绍5. 融合编码器的优化方案5.1 整体思路介绍5.2 预积分模型设计Reference: 深蓝学院-多传感…

Vue3 - 自定义指令封装

Vue3 - 自定义指令封装一. 自定义指令封装1.1 全局/局部注册自定义聚焦指令1.2 自定义指令相关参数1.3 自定义指令参数传递二. 总结一. 自定义指令封装 vue中有很多内置的指令,我们一般在开发中也经常用到,比如v-if,v-for等等。那么本篇文章…

Vue极简使用

Vue安装Vue模板语法安装Vue 安装nodejs 这里我安装的是14.5.4版本 https://nodejs.org/download/release/v14.15.4/解压后配置一下环境变量就行 安装cnpm镜像 (这个安装的版本可能过高,后面安装Vue可能出问题) npm install -g cnpm --registryhttps://registry…

二十二、Gtk4-ListView

GTK 4添加了新的列表对象GtkListView、GtkGridView和GtkColumnView。这个新特性在Gtk API参考—列表小构件概述中有描述。 GTK 4还有其他实现列表的方法。它们是GtkListBox和GtkTreeView,它们是从GTK 3接管的。在Gtk开发博客中有一篇关于Matthias Clasen所写的列表…

vscode执行Python输出exited with code=9009 in 0.655 seconds

vscode执行Python输出exited with code9009 in 0.655 seconds 想用vscode写个脚本,用自己电脑配置了下vscode的python环境,结果点击右上角三角图标运行时却只会输出exited with code9009 in 0.655 seconds 这就不太理解了,我在公司时是能正…

linux性能分析 性能之巅学习笔记和内容摘录

本文只是在阅读《性能之巅》的过程中,对一些觉得有用的地方进行的总结和摘录,并附加一些方便理解的材料,完整内容还请阅读Gregg的大作 概念和方法 性能分析领域一词的全栈代表了整个操作系统的软硬件在内的所有事物 软件生命周期和性能规划…

LabWindows CVI 2017开发笔记--串口API

参考资料:https://download.csdn.net/download/Stark_/87424565?spm1001.2014.3001.5501 转载请注明出处:https://blog.csdn.net/Stark_/article/details/128966962?spm1001.2014.3001.5501 打开串口OpenComConfig OpenComConfig 打开一个串行并进行…

HTML-CSS-js教程

HTML 双标签<html> </html> 单标签<img> html5的DOCTYPE声明 <!DOCTYPE html>html的基本骨架 <!DOCTYPE html> <html> </html>head标签 用于定义文档的头部。文档的头部包含了各种属性和信息&#xff0c;包括文档的标题&#…

【成为架构师课程系列】架构设计中的核心思维方法

架构设计中的核心思维方法 目录 前言 #一、抽象思维 #二、分层思维 #三、分治思维 #四、演化思维 #五、如何培养架构设计思维

leaflet 加载WKT数据(示例代码050)

第050个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载WKT文件,将图形显示在地图上。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意如果OpenStreetMap无法加载,请加载其他来练习 文章目录 示例效果配置方式示例源代码(共67行…

中国特色地流程管理系统,天翎让流程审批更简单

编者按&#xff1a;本文分析了国内企业在采购流程管理系统常遇到的一些难点&#xff0c;并从适应中国式流程管理模式的特点出发&#xff0c;介绍了符合中国特色的流程审批管理系统——天翎流程管理系统。关键词&#xff1a;可视化开发&#xff0c;拖拽建模&#xff0c;审批控制…

威联通ContainerStation部署Oracle11g

文章目录前言部署过程详解使用docker-compose文件创建容器临时开启NAS的SSH远程访问通过SSH客户端远程连接NAS进入容器创建用户拷贝容器中的数据库相关文件至宿主机在ContainerStation中修改docker-compose文件总结前言 ContainerStation本质上是对Docker可视化的一款软件&…

聊聊分布式锁——Redis和Redisson的方式

一、什么是分布式锁 分布式~~锁&#xff0c;要这么念&#xff0c;首先得是『分布式』&#xff0c;然后才是『锁』 分布式&#xff1a;这里的分布式指的是分布式系统&#xff0c;涉及到好多技术和理论&#xff0c;包括CAP 理论、分布式存储、分布式事务、分布式锁... 分布式系统…

Android开发

前言&#xff1a;因为这学期选了手机APP开发这门课&#xff0c;所以还是写个博客记录一下学习过程&#xff0c;包括安卓开发和ios开发。用到的资料包括课程PPT&#xff0c;和我在网上找的一些视频和资料。 1.Andriod入门 XML&#xff1a;描绘应用界面 &#xff08;决定APP长什…

NeurIPS/ICLR/ICML AI三大会国内高校和企业近年中稿量完整统计

点击文末公众号卡片&#xff0c;找对地方&#xff0c;轻松参会。 近日&#xff0c;有群友转发了一张网图&#xff0c;统计了近年来中国所有单位在NeurIPS、ICLR、ICML论文情况。原图如下&#xff1a; 中稿数100&#xff1a; 清华(1) 北大(2) 占比&#xff1a;22.6%。 累计数…

基于注解管理Bean

一、介绍从 Java 5 开始&#xff0c;Java 增加了对注解&#xff08;Annotation&#xff09;的支持&#xff0c;它是代码中的一种特殊标记&#xff0c;可以在编译、类加载和运行时被读取&#xff0c;执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下&#x…

全板电镀与图形电镀,到底有什么区别?

衔接上文&#xff0c;继续为朋友们分享普通单双面板的生产工艺流程。 如图&#xff0c;第四道主流程为电镀。 电镀的目的为&#xff1a; 适当地加厚孔内与板面的铜厚&#xff0c;使孔金属化&#xff0c;从而实现层间互连。 至于其子流程&#xff0c;可以说是非常简单&#x…

黑马】后台管理176-183

一、新建订单管理的分支二、创建一个订单管理的vue文件进行组件页面的路由配置import Order from ../components/order/Order.vue{path:/orders,component:Order},注意上面的components不要忘记少加一个s&#xff01;三&#xff0c;获取后台数据面包屑导航粘贴过来文本输入框&a…

手写MySQL补充章(十二)SQL语法解析之语法树

目录 模块分析 AST节点类型 SQL词法解析 举个例子 之前写的在第九章写的sql解析太简单了&#xff0c;SQL规范还有复杂的开闭括号以及嵌套查询&#xff0c;复杂SQL几乎不可能通过字符串匹配来实现。 本章以Druid SQL Parser解析SQL为例&#xff0c;进行分析。 模块分析 D…

如何做好需求管理?经验方法、模型、工具

需求管理能力是衡量产品经理能力的一个重要指标。因为需求是产品的基石&#xff0c;只有选取恰当的方法进行需求分析及管理&#xff0c;才能更好的构建产品方案&#xff0c;从而输出精准的产品定义。结合本人学习和自身经验&#xff0c;打算将需求管理分”需求挖掘”、”需求分…