一文搞懂spring boot本地事务@Transactional参数

目录
  • 1. 本地事务
    • 1.1. 基本概念
    • 1.2. 隔离级别
    • 1.3. 相关命令
    • 1.4. 传播行为
      • 1.4.1. 伪代码练习
      • 1.4.2. 改造商品新增代码
      • 1.4.3. 测试1:同一service + requires_new
      • 1.4.4. 测试2:不同service + requires_new
      • 1.4.5. 在同一个service中使用传播行为
    • 1.5. 回滚策略
      • 1.5.1. 测试编译时异常不回滚
      • 1.5.2. 定制回滚策略
    • 1.6. 超时事务
    • 1.7. 只读事务

1. 本地事务

商品新增功能非常复杂,商品管理微服务在service层中调用保存spu和sku相关的方法,为了保证数据的一致性,必然会使用事务。

在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。

咱们之前玩的事务都是本地事务。所谓本地事务,是指该事务仅在当前项目内有效。

1.1. 基本概念

事务的概念:事务是逻辑上一组操作,组成这组操作各个逻辑单元,要么一起成功,要么一起失败。

事务的四个特性(ACID):

  1. 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
  2. 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
  3. 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰
  4. 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

1.2. 隔离级别

事务并发引起一些读的问题:

  • 脏读:一个事务可以读取另一个事务未提交的数据
  • 不可重复读: 一个事务可以读取另一个事务已提交的数据 单条记录前后不匹配
  • 虚读(幻读: 一个事务可以读取另一个事务已提交的数据 读取的数据前后多了点或者少了点

并发写:使用mysql默认的锁机制(独占锁)

解决读问题:设置事务隔离级别

  1. read uncommitted(0)
  2. read committed(2)
  3. repeatable read(4)
  4. Serializable(8)

隔离级别越高,性能越低。

一般情况下:脏读是不可允许的,不可重复读和幻读是可以被适当允许的。

1.3. 相关命令

查看全局事务隔离级别:SELECT @@global.tx_isolation

设置全局事务隔离级别:set global transaction isolation level read committed;

查看当前会话事务隔离级别:SELECT @@tx_isolation

设置当前会话事务隔离级别:set session transaction isolation level read committed;

查看mysql默认自动提交状态:select @@autocommit

设置mysql默认自动提交状态:set autocommit = 0;【不自动提交】

开启一个事务:start transaction;

提交事务:commit

回滚事务: rollback

在事务中创建一个保存点:savepoint tx1

回滚到保存点:rollback to tx1

1.4. 传播行为

事务的传播行为不是jdbc规范中的定义。传播行为主要针对实际开发中的问题

七种传播行为:

REQUIRED 支持当前事务,如果不存在,就新建一个

SUPPORTS 支持当前事务,如果不存在,就不使用事务

MANDATORY 支持当前事务,如果不存在,抛出异常

REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务

NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务

NEVER 以非事务方式运行,如果有事务存在,抛出异常

NESTED 如果当前事务存在,则嵌套事务执行(嵌套式事务)

  1. 依赖于JDBC3.0提供的SavePoint技术
  2. 删除用户 删除订单。在删除订单后,设置savePoint,执行删除用户。删除订单和删除用户在同一事务中,删除用户失败,事务回滚savePoint,由用户控制视图提交还是回滚

这七种事务传播机制最常用的就两种:

REQUIRED:一个事务,要么成功,要么失败

REQUIRES_NEW:两个不同事务,彼此之间没有关系。一个事务失败了不影响另一个事务

1.4.1. 伪代码练习

传播行为伪代码模拟:有a,b,c,d,e等5个方法,a中调用b,c,d,e方法的传播行为在小括号中标出

a(required){
	b(required);
	c(requires_new);
	d(required);
	e(requires_new);
	// a方法的业务
}

问题:

  1. a方法的业务出现异常,会怎样?a,b,d回滚 c,e不回滚
  2. d方法出现异常,会怎样?a,b,d回滚;c不回滚;e未执行
  3. e方法出现异常,会怎样?a,b,d,e回滚 c不回滚,e方法出异常会上抛影响到上级方法
  4. b方法出现异常,会怎样?a,b回滚 c,d,e未执行

加点难度:

a(required){
	b(required){
		f(requires_new);
		g(required)
	}
	c(requires_new){
		h(requires_new)
		i(required)
	}
	d(required);
	e(requires_new);
	// a方法的业务
}

问题:

  1. a方法业务出异常?a,b,g,d回滚;f,c,h,i,e不回滚
  2. e方法出异常?e,a,b,g,d回滚;f,c,h,i不回滚
  3. d方法出异常?a,b,g,d回滚;f,c,h,i不回滚;e为执行
  4. h,i方法分别出异常?h,i,c,a,b,g回滚;f不回滚;d,e未执行
  5. i方法出异常?i,c,a,b,g回滚;f,h不回滚;d,e未执行
  6. f,g方法分别出异常?f,g,b,a回滚;c,h,i,d,e未执行

1.4.2. 改造商品新增代码

现在商品保存的方法结构如下:

    @Override
    public void bigSave(SpuVo spuVo) {
        /// 1.保存spu相关
        // 1.1. 保存spu基本信息 spu_info
        Long spuId = saveSpu(spuVo);

        // 1.2. 保存spu的描述信息 spu_info_desc
        saveSpuDesc(spuVo, spuId);

        // 1.3. 保存spu的规格参数信息
        saveBaseAttr(spuVo, spuId);

        /// 2. 保存sku相关信息
        saveSku(spuVo, spuId);
    }

    /**
     * 保存sku相关信息及营销信息
     * @param spuInfoVO
     */
    private void saveSku(SpuVo spuVo, Long spuId) { 。。。 }

    /**
     * 保存spu基本属性信息
     * @param spuInfoVO
     */
    private void saveBaseAttr(SpuVo spuVo, Long spuId) { 。。。 }

    /**
     * 保存spu描述信息(图片)
     * @param spuInfoVO
     */
    private void saveSpuDesc(SpuVo spuVo, Long spuId) { 。。。 }

    /**
     * 保存spu基本信息
     * @param spuInfoVO
     */
    private void saveSpu(SpuVo spuVo) {  。。。 }

为了测试事务传播行为,我们在SpuInfoService接口中把saveSkuInfoWithSaleInfo、saveBaseAttrs、saveSpuDesc、saveSpuInfo声明为service接口方法。

public interface SpuInfoService extends IService<SpuInfoEntity> {

    PageVo queryPage(QueryCondition params);

    PageVo querySpuInfo(QueryCondition condition, Long catId);

    void saveSpuInfoVO(SpuInfoVO spuInfoVO);

    void saveSku(SpuVo spuVo, Long spuId);

    void saveBaseAttr(SpuVo spuVo, Long spuId);

    void saveSpuDesc(SpuVo spuVo, Long spuId);

    Long saveSpu(SpuVo spuVo);
}

再把SpuInfoServiceImpl实现类的对应方法改成public:

1.4.3. 测试1:同一service + requires_new

springboot 1.x使用事务需要在引导类上添加**@EnableTransactionManagement注解开启事务支持**

springboot 2.x可直接使用**@Transactional**玩事务,传播行为默认是REQUIRED

添加事务:

这时,在保存商品的主方法中制造异常:

由于保存商品描述方法使用的是requires_new,spu应该会回滚,spu_desc应该保存成功。

清空pms_spu_desc表,再添加一个spu保存。

结果pms_spu_desc表中依然没有数据。

但是控制台打印了新增pms_spu_desc表的sql语句:

说明saveSpuDesc方法的事务回滚了,也就是说该方法配置的事务传播机制没有生效。

解决方案:

把service方法放到不同的service中使用动态代理对象调用该方法

1.4.4. 测试2:不同service + requires_new

把saveSpuDesc方法放到SpuDescService中:

在实现类中实现该方法,可以把之前的实现copy过来:

改造SpuServiceImpl中保存商品的方法,调用SpuDescServiceImpl的saveSpuDesc方法:

再次重启gmall-pms,虽然控制台依然报错,但是数据可以保存成功,说明没有在一个事务中。

为什么测试1的事务传播行为没有生效,而测试2的事务传播行为生效了?

spring的事务是声明式事务,而声明式事务的本质是Spring AOP,SpringAOP的本质是动态代理。

事务要生效必须是代理对象在调用。

测试1:通过this调用同一个service中的方法,this是指service实现类对象本身,不是代理对象,就相当于方法中的代码粘到了大方法里面,相当于还是一个方法。

测试2:通过其他service对象(spuDescService)调用,这个service对象本质是动态代理对象

接下来debug,打个断点看看:

spuDescService:

this:

1.4.5. 在同一个service中使用传播行为

只需要把测试1中的this.方法名()替换成this代理对象.方法名()即可。

问题是怎么在service中获取当前类的代理对象?

在类中获取代理对象分三个步骤:

  1. 导入aop的场景依赖:spring-boot-starter-aop
  2. 开启AspectJ的自动代理,同时要暴露代理对象:@EnableAspectJAutoProxy(exposeProxy=true)
  3. 获取代理对象:SpuInfoService proxy = (SpuInfoService) AopContext.currentProxy();

具体如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

重启后测试:先清空pms_spu_info_desc表中数据

表中数据新增成功,说明saveSpuDesc方法走的是自己的事务,传播行为生效了。

debug可以看到,spuInfoService是一个代理对象。

1.5. 回滚策略

事务很重要的另一个特征是程序出异常时,会回滚。但并不是所有的异常都会回滚。

默认情况下的回滚策略:

  • 运行时异常:不受检异常,没有强制要求try-catch,都会回滚。例如:ArrayOutOfIndex,OutofMemory,NullPointException
  • 编译时异常:受检异常,必须处理,要么try-catch要么throws,都不回滚。例如:FileNotFoundException

可以通过@Transactional注解的下面几个属性改变回滚策略:

  1. rollbackFor:指定的异常必须回滚
  2. noRollbackFor:发生指定的异常不用回滚

1.5.1. 测试编译时异常不回滚

在商品保存方法中制造一个编译时异常:

重启测试,注意pms_spu表中数据:

控制台报异常:

pms_spu表中的数据新增成功了。

也就证明了编译时异常不回滚。

1.5.2. 定制回滚策略

经过刚才的测试,我们知道:

ArithmeticException异常(int i = 1/0)会回滚FileNotFoundException异常(new FileInputStream(“xxxx”))不回滚

接下来我们来改变一下这个策略:

测试:

FileNotFoundException:在程序中添加new FileInputStream(“xxxx”),然后测试。

还是id还是17,说明回滚了(回滚也会占用id=18)

ArithmeticException:在程序中添加int i = 1/0; 然后测试。

id是19,说明没有回滚。

1.6. 超时事务

@Transactional注解,还有一个属性是timeout超时时间,单位是秒。

timeout=3:是指第一个sql开始执行到最后一个sql结束执行之间的间隔时间。

即:超时时间(timeout)是指数据库超时,不是业务超时。

改造之前商品保存方法:SpuInfoServiceImpl类中

重启测试:控制台出现事务超时异常

1.7. 只读事务

@Transactional注解最后一个属性是只读事务属性

如果一个方法标记为readOnly=true事务,则代表该方法只能查询,不能增删改。readOnly默认为false

给商品新增的事务标记为只读事务:

测试:

到此这篇关于spring boot本地事务@Transactional参数详解的文章就介绍到这了,更多相关spring boot事务Transactional内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 五分钟教你手写 SpringBoot 本地事务管理实现

    白菜Java自习室 涵盖核心知识 1. SpringBoot 事务 一直在用 SpringBoot 中的 @Transactional 来做事务管理,但是很少没想过 SpringBoot 是如何实现事务管理的,今天从源码入手,看看 @Transactional 是如何实现事务的,最后我们结合源码的理解,自己动手写一个类似的注解来实现事务管理,帮助我们加深理解. 1.1. 事务的隔离级别 事务为什么需要隔离级别呢?这是因为在并发事务情况下,如果没有隔离级别会导致如下问题: 脏读 (Dirty Re

  • springboot中事务管理@Transactional的注意事项与使用场景

    前言:在service层的方法上使用@Transactional 即可实现处理数据库发生错误时触发事务回滚机制. 注意: Spring 基于注解的声明式事物 @Transactional 默认情况下只会对运行期异常(java.lang.RuntimeException及其子类)和 Error 进行回滚. 数据库引擎要支持事物,使用InnoDB. @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没

  • 详解在SpringBoot中@Transactional事物操作和事物无效问题排查

    目录 1.spring事务管理简述 2.SpringBoot中使用@Transactional注解 2.1.开启事务注解 2.2.在目标类.方法上添加注解@Transactional 2.3.细化事务配置 3.@Transactional事务实现机制 3.1.整体事务控制流程 3.2.Spring AOP的两种代理 3.3.事务操作的底层实现 4.@Transactional使用注释实现及问题排查 4.1.数据库引擎是否支持事务? 4.3.注解所在的类是否被加载成Bean? 4.2.注解所在方法

  • 深入学习Spring Boot排查 @Transactional 引起的 NullPointerException问题

    写在前面 这个demo来说明怎么排查一个@Transactional引起的NullPointerException. https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-Transactional-NullPointerException 定位 NullPointerException 的代码 Demo是一个简单的spring事务例子,提供了下面一个StudentDao,并用@Transactional来声明事务:

  • springboot中使用@Transactional注解事物不生效的坑

    一:在springboot中使用事物遇到的坑 1.我们知道spring中的事物分为两种:一种是编程式事物,一种是声明式事物.顾名思义,编程式事物是指通过代码去实现事物管理,这里不做过多说明.另一种是声明式事物,分为两种情况01:一种是通过传统xml方式配置,02:使用@Transaction注解方式配置,这是主要讲解的是通过注解方式配置.因为在springboot项目中,会自动配置DataSourceTransactionManager,我们只需要在对应的方法上或者类上加上@Transactio

  • 解决SpringBoot中使用@Transactional注解遇到的问题

    目录 使用@Transactional注解遇到的问题 1.不建议在接口上添加@Transactional注解 2.@Transactional注解 3.默认情况下 4.数据库引擎需要支持事务管理 5.同一类中methodA()方法 springboot 注解transactional失效 1.在方法中捕获了异常 2.spring中事务是代理模式 3.A方法如果有事务注解 4.本类中A方法调用 使用@Transactional注解遇到的问题 1.不建议在接口上添加@Transactional注解

  • 一文搞懂spring boot本地事务@Transactional参数

    目录 1. 本地事务 1.1. 基本概念 1.2. 隔离级别 1.3. 相关命令 1.4. 传播行为 1.4.1. 伪代码练习 1.4.2. 改造商品新增代码 1.4.3. 测试1:同一service + requires_new 1.4.4. 测试2:不同service + requires_new 1.4.5. 在同一个service中使用传播行为 1.5. 回滚策略 1.5.1. 测试编译时异常不回滚 1.5.2. 定制回滚策略 1.6. 超时事务 1.7. 只读事务 1. 本地事务 商品

  • 一文搞懂Spring中@Autowired和@Resource的区别

    目录 1.来源不同 2.依赖查找顺序不同 2.1 @Autowired 查找顺序 2.2 @Resource 查找顺序 2.3 查找顺序小结 3.支持的参数不同 4.依赖注入的支持不同 5.编译器提示不同 总结 @Autowired 和 @Resource 都是 Spring/Spring Boot 项目中,用来进行依赖注入的注解.它们都提供了将依赖对象注入到当前对象的功能,但二者却有众多不同,并且这也是常见的面试题之一,所以我们今天就来盘它. @Autowired 和 @Resource 的区

  • 一文搞懂Spring中的Bean作用域

    目录 概述 Singleton prototype request session application 概述 scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象. Spring容器bean的作用域类型: singleton:Spring IoC 容器的单个对象实例作用域都默认为singleton prototype:针对声明为拥有prototyp

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Spring Bean中的作用域和生命周期

    目录 一.Spring Bean 作用域 singleton(单例) prototype(原型) 小结 二.Spring Bean生命周期 如何关闭容器 生命周期回调 通过接口设置生命周期 通过xml设置生命周期 一.Spring Bean 作用域 常规的 Spring IoC 容器中Bean的作用域有两种:singleton(单例)和prototype(非单例) 注:基于Web的容器还有其他种作用域,在这就不赘述了. singleton(单例) singleton是Spring默认的作用域.当

  • 一文搞懂Spring中Bean的生命周期

    目录 一.使用配置生命周期的方法 二.生命周期控制——接口控制(了解) 小结 生命周期:从创建到消亡的完整过程 bean声明周期:bean从创建到销毁的整体过程 bean声明周期控制:在bean创建后到销毁前做一些事情 一.使用配置生命周期的方法 在BookDaoImpl中实现类中创建相应的方法: //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public v

  • 一文搞懂Spring循环依赖的原理

    目录 简介 循环依赖实例 测试 简介 说明 本文用实例来介绍@Autowired解决循环依赖的原理.@Autowired是通过三级缓存来解决循环依赖的. 除了@Autoired,还有其他方案来解决循环依赖的,见:Spring循环依赖的解决方案详解 概述 @Autowired进行属性注入可以解决循环依赖.原理是:Spring控制了bean的生命周期,先实例化bean,后注入bean的属性.Spring中记录了正在创建中的bean(已经实例化但还没初始化完毕的bean),所以可以在注入属性时,从记录

  • 一文搞懂Spring AOP的五大通知类型

    目录 一.通知类型 二.环境准备 添加AOP依赖 创建目标接口和实现类 创建通知类 创建Spring核心配置类 编写运行程序 三.添加通知 普通通知 环绕通知(重点) 一.通知类型 Advice 直译为通知,也有人翻译为 “增强处理”,共有 5 种类型,如下表所示. 通知类型 注解 说明 before(前置通知) @Before 通知方法在目标方法调用之前执行 after(后置通知) @After 通知方法在目标方法返回或异常后调用 after-returning(返回通知) @AfterRet

  • 一文搞懂Spring中的JavaConfig

    目录 配置类 注册组件 扫描包配置 事务注解驱动 单元测试加载配置类 properties配置文件加载(了解) aspectj注解开关 传统spring一般都是基于xml配置的,不过后来新增了许多JavaConfig的注解.特别是springboot,基本都是清一色的java config,不了解一下,还真是不适应.这里给大家普及下Spring中的JavaConfig知识. 什么是JavaConfig.通过注解和配置类完成Spring的相关配置 Spring配置都做了什么? 注册组件.其他配置(

  • 一文搞明白Java Spring Boot分布式事务解决方案

    目录 前言 1. 什么是反向补偿 2. 基本概念梳理 3. 什么是两阶段提交 4. AT 模式 5. TCC 模式 6. XA 模式 7. Saga 模式 前言 分布式事务,咱们前边也聊过很多次了,网上其实也有不少文章在介绍分布式事务,不过里边都会涉及到不少专业名词,看的大家云里雾里,所以还是有一些小伙伴在微信上问我. 那么今天,我就再来一篇文章,和大家捋一捋这个话题.以下的内容主要围绕阿里的 seata 来和大家解释. 1. 什么是反向补偿 首先,来和大家解释一个名词,大家在看分布式事务相关资

随机推荐