【Spring Boot系列】- Spring Boot事务应用详解

【Spring Boot系列】- Spring Boot事务应用详解

一、事务简介

事务(Transaction)是数据库操作最基本单元,逻辑上一组操作,要么都成功。如果有一个操作失败。则事务操作都失败(回滚(Rollback))。

事务的四个特性(ACID):

1. 原子性(Atomicity)

一个事务(Transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

2. 一致性(Consistency)

事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则。

3. 隔离性(Isolation)

一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。

4. 隔离性(Durability)

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
其中,事务隔离又分为以下 4 种不同的级别

  • 未提交读(Read uncommitted):最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
  • 提交读(read committed):一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
  • 可重复读(repeatable read): 一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了
  • 串行化(Serializable): 最严格的隔离级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然 Serializable 隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。

需要格外注意的是:事务能否生效,取决于数据库引擎是否支持事务。如MySQL的InnoDB引擎是支持事务的,但是MyISAM就不支持事务。

二、Spring事务

Spring对事务提供了很好的支持。Spring借助IOC容器强大的配置能力,为事务提供丰富功能支持。

Spring支持以下2种事务管理方式:

  1. 声明式事务管理:Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。
  2. 编程式事务管理:编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。

选择编程式事务还是声明式事务,很大程度上就是在控制权上颗粒度和易用性之间进行权衡。

  • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

三、Spring声明式事务

Spring的声明式事务管理在底层是建立在AOP的基础上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

在开发中使用声明式事务,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。

3.1 声明式事务的2种实现方式

  1. 配置文件的方式: 即在spring xml文件中进行统一配置,开发者基本上就不用关注事务的事情了,代码中无需关心任何和事务相关的代码,一切交给spring处理。
  2. 注解的方式: 只需在需要spring来帮忙管理事务的方法上加上@Transaction注解就可以了,注解的方式相对来说更简洁一些,都需要开发者自己去进行配置,可能有些同学对spring不是太熟悉,所以配置这个有一定的风险,做好代码review就可以了。

3.2 声明式事务注解方式5个步骤

1. 启用Spring的注释驱动事务管理功能

在spring配置类上加上@EnableTransactionManagement注解

@EnableTransactionManagement
public class MainConfig4 {
}

当spring容器启动的时候,发现有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描看一下bean上是否有@Transaction注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring会通过aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。

EnableTransactionManagement 的源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {/*** spring是通过aop的方式对bean创建代理对象来实现事务管理的* 创建代理对象有2种方式,jdk动态代理和cglib代理* proxyTargetClass:为true的时候,就是强制使用cglib来创建代理*/boolean proxyTargetClass() default false;/*** 用来指定事务拦截器的顺序* 我们知道一个方法上可以添加很多拦截器,拦截器是可以指定顺序的* 比如你可以自定义一些拦截器,放在事务拦截器之前或者之后执行,就可以通过order来控制*/int order() default Ordered.LOWEST_PRECEDENCE;
}
2. 定义事务管理器

事务交给spring管理,那么你肯定要创建一个或者多个事务管理者,有这些管理者来管理具体的事务,比如启动事务、提交事务、回滚事务,这些都是管理者来负责的。

spring中使用PlatformTransactionManager这个接口来表示事务管理者。

PlatformTransactionManager多个实现类,用来应对不同的环境

JpaTransactionManager: 如果你用jpa来操作db,那么需要用这个管理器来帮你控制事务。

DataSourceTransactionManager: 如果你用是指定数据源的方式,比如操作数据库用的是:JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来帮你控制事务。

HibernateTransactionManager: 如果你用hibernate来操作db,那么需要用这个管理器来帮你控制事务。

JtaTransactionManager: 如果你用的是java中的jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。

3. 需使用事务的目标上加@Transaction注解
  • @Transaction放在接口上,那么接口的实现类中所有public都被spring自动加上事务。
  • @Transaction放在类上,那么当前类以及其下无限级子类中所有pubilc方法将被spring自动加上事务。
  • @Transaction放在public方法上,那么该方法将被spring自动加上事务。

Transaction参数介绍

参数描述
value指定事务管理器的bean名称,如果容器中有多事务管理器PlatformTransactionManager,那么你得告诉spring,当前配置需要使用哪个事务管理器
transactionManager同value,value和transactionManager选配一个就行,也可以为空,如果为空,默认会从容器中按照类型查找一个事务管理器bean
propagation事务的传播属性,下篇文章详细介绍
isolation事务的隔离级别,就是制定数据库的隔离级别,数据库隔离级别大家知道么?不知道的可以去补一下
timeout事务执行的超时时间(秒),执行一个方法,比如有问题,那我不可能等你一天吧,可能最多我只能等你10秒 10秒后,还没有执行完毕,就弹出一个超时异常吧
readOnly是否是只读事务,比如某个方法中只有查询操作,我们可以指定事务是只读的 设置了这个参数,可能数据库会做一些性能优化,提升查询速度
rollbackFor定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常及其子类异常的时候,spring会让事务回滚 如果不配做,那么默认会在 RuntimeException 或者 Error 情况下,事务才会回滚
rollbackForClassName同 rollbackFor,只是这个地方使用的是类名
noRollbackFor定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常的时候,事务不会回滚
noRollbackForClassName同 noRollbackFor,只是这个地方使用的是类名
4. 执行db业务操作

在@Transaction标注类或者目标方法上执行业务操作,此时这些方法会自动被spring进行事务管理。

@Component
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic void insertBatch(String  names) {jdbcTemplate.update("truncate table t_user");for (String name : names) {jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);}}
}
5. 启动spring容器,使用bean执行业务操作
@Test
public void test1() {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.register(MainConfig4.class);context.refresh();UserService userService = context.getBean(UserService.class);userService.insertBatch("java高并发系列", "mysql系列", "maven系列", "mybatis系列");
}

四、Spring编程式事务

通过硬编码的方式使用spring中提供的事务相关的类来控制事务。

编程式事务主要的两种用法:

  1. 通过PlatformTransactionManager控制事务。
  2. 通过TransactionTemplate控制事务。

4.1 PlatfornTransactionManager

这种是最原始的方式,代码量较大,后面其他方式都是针对这种方式的封装

@Testpublic void test1() throws Exception {//定义一个数据源DataSource dataSource = new DataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setInitialSize(5);//定义一个JdbcTemplate,用来方便执行数据库增删改查JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);//2.定义事务属性:TransactionDefinition,// TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();//3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);//4.执行业务操作,下面就执行2个插入操作try {System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");//5.提交事务:platformTransactionManager.commitplatformTransactionManager.commit(transactionStatus);} catch (Exception e) {//6.回滚事务:platformTransactionManager.rollbackplatformTransactionManager.rollback(transactionStatus);}System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));}

4.2 代码分析

步骤一:定义事务管理器PlatformTransactionManager

事务管理器相当于一个管理员,这个管理员就是用来帮你控制事务的,比如开启事务,提交事务,回滚事务等等。

spring中使用PlatformTransactionManager这个接口来表示事务管理器

public interface PlatformTransactionManager {//获取一个事务(开启事务)TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;//提交事务void commit(TransactionStatus status) throws TransactionException;//回滚事务void rollback(TransactionStatus status) throws TransactionException;
}
步骤二:定义事务属性TransactionDefinition

定义事务属性,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
spring中使用TransactionDefinition接口来表示事务的定义信息,有个子类比较常用:DefaultTransactionDefinition。

步骤三:开启事务

调用事务管理器的getTransaction方法,即可以开启一个事务。这个方法会返回一个TransactionStatus表示事务状态的一个对象,通过TransactionStatus提供的一些方法可以用来控制事务的一些状态,比如事务最终是需要回滚还是需要提交。

步骤四:执行业务操作

事务管理器开启事务的时候,会创建一个连接,将datasource和connection映射之后丢在了ThreadLocal中,而JdbcTemplate内部执行db操作的时候,也需要获取连接,JdbcTemplate会以自己内部的datasource去上面的threadlocal中找有没有关联的连接,如果有直接拿来用,若没找到将重新创建一个连接,而此时是可以找到的,那么JdbcTemplate就参与到spring的事务中了。

4.2 TransactionTemplate

@Testpublic void test2() throws Exception {//定义一个数据源DataSource dataSource = new DataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setInitialSize(5);//定义一个JdbcTemplate,用来方便执行数据库增删改查JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。DefaultTransactionDefinition  transactionDefinition = new DefaultTransactionDefinition();transactionDefinition.setTimeout(10);//3.创建TransactionTemplate对象TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager,transactionDefinition);/*** 4.通过TransactionTemplate提供的方法执行业务操作* 主要有2个方法:* (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。* 那么什么时候事务会回滚,有2种方式:* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态* (2)execute方法或者executeWithoutResult方法内部抛出异常* 什么时候事务会提交?* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();*/transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {@Overridepublic void accept(TransactionStatus transactionStatus) {jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");}});System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));}

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

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

相关文章

光流法动目标检测

目录 前言 一、效果展示 二、光流法介绍 三、代码展示 总结 前言 动目标检测是计算机视觉领域的一个热门研究方向。传统的方法主要基于背景建模&#xff0c;但这些方法对于光照变化、遮挡和噪声敏感。因此&#xff0c;研究人员一直在寻找更加鲁棒和有效的技术来解决这一问题。…

C# 串口通信简单示例

C# 简单串口通信示例 串口通信示例代码 串口通信 C# 串口通信主要操作&#xff1a; 命名空间&#xff1a;using System.IO.Ports;获取端口&#xff1a;string[] ports System.IO.Ports.SerialPort.GetPortNames();设置端口名&#xff1a;serialPort1.PortName “COM1”; //…

vue3中常用的新组件

一、Fragment vue2中&#xff0c;组件必须有一个根标签 vue3中&#xff0c;组件可以没有根标签&#xff0c;内部会将多个标签包含在一个Fragment虚拟元素中。 优点&#xff1a;减少标签层级。 二、Teleport&#xff08;传送门&#xff09; 作用&#xff1a;将组件的 html …

渗透测试tomcat错误信息泄露解决办法

解决方法&#xff1a; 1、使用tomcat8.5.16&#xff0c;会重定向非法url到登录url 2、配置server.xml&#xff0c;加上 <Valve className"org.apache.catalina.valves.ErrorReportValve" showReport"false" showServerInfo"false" />配置…

C++ 模板和泛型编程详解

C中的模板和泛型编程是非常重要的概念。模板是一种将数据类型作为参数的通用程序设计方法。它们允许开发人员编写可以处理各种数据类型的代码&#xff0c;而无需为每种数据类型编写不同的代码。下面介绍了一些关于C中模板和泛型编程的重要知识点 模板的定义 模板是一种通用程序…

opencalib中lidar2camera安装记录

目录 一、opencalib安装 二、lidar2camera的安装 三、测试运行 四、出现过的问题 一、opencalib安装 代码地址&#xff1a;https://github.com/PJLab-ADG/SensorsCalibration/blob/master/README.md # pull docker image sudo docker pull scllovewkf/opencalib:v1 # Aft…

【计算机网络】HTTP 协议的基本格式以及 fiddler 的用法

HTTP协议的基本格式如下&#xff1a; 1.请求行&#xff1a; 包括请求THHP协议的版本、请求URI&#xff08;资源路径&#xff09;和HTTP方法&#xff08;如GET、POST、PUT、DELETE等&#xff09; GET/example.html HTTP/1.1 GET表示请求方法&#xff0c;/example.html表示请求的…

HTTP 协议参考文档

开发者Web协议文档&#xff1a; https://developer.mozilla.org/zh-CN/docs/Web 其中子节点包含 HTTP 协议内容&#xff1a; https://developer.mozilla.org/zh-CN/docs/Web/HTTP 其内容是基于 markdown 编写的&#xff0c;对应源文件在 Github 中&#xff0c;如下&#xf…

IDEA 之 在不更改操作系统用户名的情况下更改 ${USER} 变量?

如何在不更改操作系统用户名的情况下更改 IntelliJ IDEA 中的 ${USER} 变量 IDEA -> Help -> Edit Custom VM 添加如下内容 -Duser.nameusername这样在文件或者函数注释的时候会读取这个配置&#xff0c;而不会读取电脑登录用户名

ADB原理(第四篇:聊聊adb shell ps与adb shell ps有无双引号的区别)

前言 对于经常使用adb的同学&#xff0c;不可避免的一定会这样用adb&#xff0c;比如我们想在手机里执行ps命令&#xff0c;于是在命令行中写下如下代码&#xff1a; adb shell ps -ef 或者 adb shell "ps -ef" 两种方式都可以使用&#xff0c;你喜欢用哪个呢&#…

大语言模型(LLM)综述(三):大语言模型预训练的进展

A Survey of Large Language Models 前言4. PRE-TRAINING4.1数据收集4.1.1 数据源4.1.2 数据预处理4.1.3 预训练数据对LLM的影响 4.2 模型架构4.2.1 典型架构4.2.2 详细配置4.2.3 预训练任务4.2.4 解码策略4.2.5 总结和讨论 4.3 模型训练4.3.1 优化设置4.3.2 可扩展的训练技术 …

TypeScript之索引签名

1. 索引签名 在 TypeScript 中&#xff0c;索引签名是一种定义对象类型的方式&#xff0c;它允许我们使用字符串或数字作为索引来访问对象的属性。 索引签名最主要的作用就是允许我们动态地添加或访问对象的属性&#xff0c;通过使用索引签名&#xff0c;我们可以在编译时无法…

【Linux】线程同步

线程同步 线程仅仅互斥&#xff0c;是可以保证线程安全的。但是&#xff0c;这不合理&#xff01;如果一个线程竞争到了锁&#xff0c;那么再它释放后它依然可以竞争个锁。因为CPU此时正在执行当前线程&#xff0c;所以该线程又可以继续竞争锁。 这样就会造成一个问题&#xf…

Python 继承和子类示例:从 Person 到 Student 的演示

继承允许我们定义一个类&#xff0c;该类继承另一个类的所有方法和属性。父类是被继承的类&#xff0c;也叫做基类。子类是从另一个类继承的类&#xff0c;也叫做派生类。 创建一个父类 任何类都可以成为父类&#xff0c;因此语法与创建任何其他类相同&#xff1a; 示例&…

简单介绍一下迁移学习

迁移学习是一种机器学习技术&#xff0c;旨在利用从一个任务或领域学习到的知识来改善另一个任务或领域的学习性能。在传统的机器学习方法中&#xff0c;通常假设训练数据和测试数据是从相同的分布中独立同分布采样的。然而&#xff0c;在现实世界中&#xff0c;这个假设并不总…

Golang Websocket框架:实时通信的新选择

前言 在现代应用程序中&#xff0c;实时通信已经成为了一种必需的特性。而Websocket是一种在客户端和服务器之间建立持久连接的协议&#xff0c;可以实现实时的双向通信。Golang作为一门高效且简洁的语言&#xff0c;也提供了一些优秀的Websocket框架&#xff0c;方便开发者构…

【计算机网络】从输入URL到页面都显示经历了什么??

文字总结 ① DNS 解析&#xff1a;当用户输入一个网址并按下回车键的时候&#xff0c;浏览器获得一个域名&#xff0c;而在实际通信过程中&#xff0c;我们需要的是一个 IP 地址&#xff0c;因此我们需要先把域名转换成相应 IP 地址。浏览器会首先从缓存中找是否存在域名&…

一文5000字从0到1使用Jmeter实现轻量级的接口自动化测试(图文并茂)

接口测试虽然作为版本的一环&#xff0c;但是也是有一套完整的体系&#xff0c;有接口的功能测试、性能测试、安全测试&#xff1b;同时&#xff0c;由于接口的特性&#xff0c;接口的自动化低成本高收益的&#xff0c;使用一些开源工具或一些轻量级的方法&#xff0c;在测试用…

uniapp进行条件编译的两种方法

在UniApp中&#xff0c;进行条件编译有两种方法&#xff1a;使用process.env全局变量和使用条件注释。 1.使用process.env全局变量&#xff1a;UniApp支持根据不同的环境变量来进行条件编译。可以通过在代码中使用process.env来判断当前环境并执行相应的逻辑。 if(process.env…

Tauri2 mobile development traps

时间点&#xff1a;2023/10/24。最近在倒腾移动端开发&#xff0c;不想学原生和 flutter&#xff0c;试了试 react-native&#xff0c;开发体验没有想象中的舒服&#xff0c;干脆直接上 tauri 2 吧&#xff0c;半年前就听说 tauri 2 支持移动端&#xff0c;到现在应该可堪小用。…