文章目录
- Spring事务(@Transactional详解)
- 什么是事务
- 为什么需要事务
- 事务的操作(sql)
- Spring中的事务实现
- Spring编程式事务
- Spring声明式事务 @Transactional
- @Transactional可以用来修饰方法或类
- 对异常进行捕获细节:
- @Transactional详解
- 三个属性
- 1. rooback
- 2. Isolation
- 事务的隔离级别:
- MySQL事务隔离级别
- Spring事务隔离级别
- 3. propagation
- 什么是事务传播机制
- 传播机制有哪些
Spring事务(@Transactional详解)
什么是事务
为什么需要事务
转账操作:
第一步:A账户 -100元
第二步:B账户 +100元
如果没有事务,第一步执行成功了,第二步执行失败了,那么A账户的100元就平白无故的消失了.
秒杀系统
第一步:下单成功
第二步:扣减库存
下单成功后,库存要么同步减少,要么扣减失败,所以得把这两个操作放在同一个事务中,要么一起成功,要么一起失败
事务的操作(sql)
- 开启事务“start transaction /begin(一组操作前开启事务)
- 提交事务:commit(这组操作全部成功,提交事务)
- 回滚事务:rollback(这组操作中间的任何一个操作出现异常,回滚事务)
--开启事务
start transaction;
--提交事务
commit;
--回滚事务
rollback;
Spring中的事务实现
- 编程式事务(手动写代码操作事务)
- 声明式事务(通过注解自动开启和提交事务)
Spring编程式事务
1.在controller 层 @Autowired 注入事务管理器
2.开启事务
3.提交事务
package com.example.mybatisdemo.controller;
import com.example.mybatisdemo.model.User;
import com.example.mybatisdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 手动提交事务
*/
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionController {
@Autowired
private UserService userService;
//获取数据库事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//数据库事务默认配置
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/addUser")
public Integer addUser(String username,String password){
//获取一个事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
User user=new User(username,password);
Integer result = userService.insert(user);
log.info("影响行数:"+result);
//回滚到transaction状态
//dataSourceTransactionManager.rollback(transaction);
//事务提交
dataSourceTransactionManager.commit(transaction);
return result;
}
}
Spring声明式事务 @Transactional
@Transactional可以用来修饰方法或类
-
修饰方法时:只有修饰public方法时才生效,修饰其他方法时不会报错,也不生效
-
修饰类时:对改类的所以public修饰的方法都生效
执行流程 :被@Transactional修饰时,在目标方法开始执行之前,会自动开启事务,方法执行结束之后,自动提交事务。
如果在方法执行过程中,出现异常,而且异常未被捕获,就会进行回滚操作。
如果异常被程序捕获,方法就被认为是执行成功,依然会提交事务。
对异常进行捕获细节:
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//制造程序抛出异常
int a = 10/0;
}catch (Exception e){
e.printStackTrace();
}
return "注册成功";
}
上述例子,因为程序被捕获了,所以事务依然可以提交
如果需要事务进行回滚:
- 重新抛出异常
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//制造程序抛出异常
int a = 10/0;
}catch (Exception e){
//将异常重新抛出去
throw e;
}
return "注册成功";
}
- 手动回滚事务
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//制造程序抛出异常
int a = 10/0;
}catch (Exception e){
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
@Transactional详解
三个属性
1. rooback
异常回滚属性.指定能够触发事务回滚的异常类型。可以指定多个异常
@Transactional 只有运行时异常和Error才能进行回滚
需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通
过 rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚
Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
if (true){
throw new IOException();
}
return "r2";
}
2. Isolation
事务的隔离级别:
MySQL事务隔离级别
- 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中
未提交的数据.
因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数
据称之为脏数据, 这个问题称之为脏读. - 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数
据,
该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不
同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读 - 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也
就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引
发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.
⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是
⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务
中查询不到这条信息,但⾃⼰就是插⼊不进去,这个现象叫幻读 - 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解
决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多.
Spring事务隔离级别
Spring 中事务隔离级别有5 种:
- Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
- Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
- Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
- Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
- Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE
Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
//... 代码省略
return "r3";
}
3. propagation
事务的传播机制
什么是事务传播机制
事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。
比如两个方法A,B都被@Transactional修饰
- A方法运行时,开启一个事务,当A调用B时,B方法本身也有事务
- 此时B方法运行时,是加入A的事务,还是创建一个新的事务呢?
这就设计到事务的传播机制
传播机制有哪些
Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.
Spring 事务传播机制有以下 7 种:
- Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没
有事务, 则创建⼀个新的事务. - Propagation.SUPPORTS : 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的
⽅式继续运⾏. - Propagation.MANDATORY :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则
抛出异常. - Propagation.REQUIRES_NEW : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也
就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开
启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰. - Propagation.NOT_SUPPORTED : 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不
⽤). - Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.
比如⼀对新⼈要结婚了, 关于是否需要房⼦
- Propagation.REQUIRED : 需要有房⼦. 如果你有房, 我们就⼀起住, 如果你没房, 我们就⼀起
买房. (如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则创建⼀个新的事务) - Propagation.SUPPORTS : 可以有房⼦. 如果你有房, 那就⼀起住. 如果没房, 那就租房. (如果
当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的⽅式继续运⾏) - Propagation.MANDATORY : 必须有房⼦. 要求必须有房, 如果没房就不结婚. (如果当前存在事
务, 则加⼊该事务. 如果当前没有事务, 则抛出异常) - Propagation.REQUIRES_NEW : 必须买新房. 不管你有没有房, 必须要两个⼈⼀起买房. 即使
有房也不住. (创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起) - Propagation.NOT_SUPPORTED : 不需要房. 不管你有没有房, 我都不住, 必须租房.(以⾮事务
⽅式运⾏, 如果当前存在事务, 则把当前事务挂起) - Propagation.NEVER : 不能有房⼦. (以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常)
- Propagation.NESTED : 如果你没房, 就⼀起买房. 如果你有房, 我们就以房⼦为根据地,做点下
⽣意. (如果如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏. 如果当前没有事
务, 则该取值等价于 PROPAGATION_REQUIRED )