一、事务的基本性质(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(){
}
本地事务失效原因
同一个对象内,事务方法互调默认失效。原因:绕过了代理对象,事务使用代理对象来控制的
解决:使用代理对象来调用事务方法。
-
引入
aop-starter;spring-boot-starter-aop;
引入了aspectj -
@EnableAspectJAutoProxy(exposeProxy = true),开启aspectj动态代理功能。以后所有的动态代理都是aspectj创建的,即使没有接口也可以创建动态代理。默认使用jdk按照接口的动态代理
exposeProxy = true :对外暴露代理对象
-
用代理对象本类互调
OrderServiceImpl o = (OrderServiceImpl) AopContext.currentProxy();//aop上下文拿到当前的代理对象,
o.b(); //用代理对象调,每个方法不同的事务设置才会生效。
o.b();