本地事务——事务特性的自我理解、被忽视的点

本文围绕SpringBoot事务展开,介绍了事务的基本性质(ACID),包括原子性、一致性、隔离性和持久性;阐述了事务的隔离级别,如读未提交、读提交等及其可能出现的问题;说明了事务的传播行为;还指出SpringBoot事务的自动配置及本地事务失效的原因与解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、事务的基本性质(ACID)

原子性、一致性、隔离性、持久性

原子性:一系列的操作不可分隔,要么同时成功,要么同时失败

一致性:数据在事务的前后,业务整体一致

  • 转账 A:1000,B:1000。转:200。事务成功:A:800,B:1200

隔离性:事务之间相互隔离

持久性:一旦事务成功,数据一定会落盘在数据库

在单体应用中,多个业务操作使用同一条连接操作不同的数据表,一旦有异常,很容易整体回滚。

一个事务开始,代表以上的所有操作都在同一个连接里面。

二、事务的隔离级别

@Transactional(isolation=xxx)

  • READ_UNCOMMITTED(读未提交):会出现脏读,A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。脏读、不可重复读解释的很好
  • READ_COMMITED(读提交):出现不可重复读,Oracle和Sql Server默认隔离级别,前后多次读取,数据内容不一致。后面读取和之前读取的数据不一样,也就是数据不重复了。系统不可以读取到重复的数据,称为不可重复读系统实际应该读取到重复的数据
  • REPEATABLE_READ(可重复读):出现幻读现象,MySql默认的隔离级别。事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
  • SERLALIZABLE(序列化):为解决幻读现象。在这种隔离级别下,所有的事务顺序执行,所以他们之间不存在冲突,从而能有效地解决脏读、不可重复读和幻读的现象。但是安全和效率不能兼得,这样事务隔离级别,会导致大量的操作超时和锁竞争,从而大大降低数据库的性能,一般不使用这样事务隔离级别。

事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡

三、事务的传播行为

@Transactional(propagation=xxx)

传播行为指的是b(),c()两个小事务,要不要和a共用一个事务

@Transactional(timeout = 30)  //a事务的所有设置会传播到和他共用一个事务的方法。
public void a(){
       b(); //a事务 
       c(); //新事务(不回滚)
       int i = 10/0;  //a回滚,c不回滚。
}

@Transactional(propagation = Propagation.REQUIRED,timeout = 2) //b和a共用一个事务
public void b(){
    //即使设置了回滚时间,也不生效。按照主方法的回滚时间。a事务设置了什么,b继承它的设置
}

@Transactional(propagation = Propagation.REQUIRES_NEW) //c和a不共用一个事务,
public void c(){

}
事务行为特性
PROPAGATION_REQUIRED如果当前存在事务,就加入当前事务。如果当前没有事务,就新建一个事务
PROPAGATION_SUPPORTS如果当前存在事务,就加入当前事务。如果当前没有事务,就以非事务进行
PROPAGATION_MANDATORY支持当前事务,假设当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW创建新事务,无论当前存不存在事务,都创建新事务
PROPAGATION_NOT_SUPPORTED以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
PROPAGATION_NEVER以非事务方式运行操作。假设当前存在事务,则抛出异常
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

四、SpringBoot事务的关键点

(一)事务的自动配置

TransactionAutoConfiguration

(二)事务的坑

在同一个类里面,编写两个方法,内部调用的时候,会导致事务设置失效。原因是没有用到代理对象的缘故。(绕过了代理对象)

事务是用代理对象来控制的,直接调用自己的b(),c()方法是不可以的

@Transaction(timeout=30)  // a事务的所有设置会传播到和他共用一个事务的方法
public void a(){
//b,c做任何设置都没用,都是和a共用一个事务。他们(a(),b(),c()同在一个类(在同一个service)中。本质上,下面的代码和将b(),c()方法的代码复制进来没用区别。
//this.b();
//this.a();

OrderServiceImpl o = (OrderServiceImpl) AopContext.currentProxy();//aop上下文拿到当前的代理对象,
o.b();  //用代理对象调,每个方法不同的事务设置才会生效。  
o.c();
int i = 10/0;  //b回滚,c不回滚。
}

@Transaction(propagation=Propagation.REQUIRED, timeout=2)  //b和a共用一个事务
public void b(){
// 即使设置了回滚时间,也不生效。按照主方法的回滚时间。a事务设置了什么,b继承它的设置
}


@Transaction(propagation=Propagation.REQUIRES_NEW) //c和a不共用一个事务
public void c(){
}

本地事务失效原因

同一个对象内,事务方法互调默认失效。原因:绕过了代理对象,事务使用代理对象来控制的

解决:使用代理对象来调用事务方法。

  1. 引入aop-starter;spring-boot-starter-aop;引入了aspectj

  2. @EnableAspectJAutoProxy(exposeProxy = true),开启aspectj动态代理功能。以后所有的动态代理都是aspectj创建的,即使没有接口也可以创建动态代理。默认使用jdk按照接口的动态代理

    exposeProxy = true :对外暴露代理对象

  3. 用代理对象本类互调

    ​ OrderServiceImpl o = (OrderServiceImpl) AopContext.currentProxy();//aop上下文拿到当前的代理对象,
    ​ o.b(); //用代理对象调,每个方法不同的事务设置才会生效。
    ​ o.b();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值