目录
需要知道:
1、什么是事务?
2、事务的主要操作3个
一、Spring中事务的实现方式
1、编程式事务(手动写代码操作事务)(了解)
2、声明式事务(用注解自动开启和提交事务)(重点)
(1)@Transactional注解的使用
(2)@Transactional 参数说明
(3)异常被捕获不会回滚的演示
(4)要求对于所有的异常都要求回滚的办法
二、事务的隔离级别(5种)
三、事务的传播机制
1、事务的传播机制是什么?
2、Spring中事务传播机制的分类(7种)
3、 Spring 事务传播机制使用和部分场景演示
将一组操作封装成⼀个执行(封装到一起),要么全部成功,要么全部失败。
比如转账分为两个操作:
(1):A账户+100元;
(2):B账户-100元;
如果没有事务,AB两个账户是分离的,当B账户给A转账成功,B账户少了100,但是A账户却没有反应;如果使用事务,那么AB两个账户的钱数是联动的,B账户给A转账少100元与A账户增加100元这个操作,是一起成功或者失败的。
-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransactionController {
@Autowired
private UserService userService;
//1、拿到事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//2、定义事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/addUser")
public Integer addUser(String username,String password){
//3、开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
//4、插入数据
User user = new User(username,password);
Integer result = userService.insert(user);
log.info("影响的行数是:"+result);
//5、提交事务
dataSourceTransactionManager.commit(transaction);
//6、回滚事务
// dataSourceTransactionManager.rollback(transaction);
return result;
}
}
测试结果:(1)提交事务:url端传参,事务提交成功,数据库成功插入一条记录;
/**
* 自动提交事务
*/
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransactionController {
@Autowired
private UserService userService;
//声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动
//开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务;如果中途发⽣了没有处
//理的异常会⾃动回滚事务
@RequestMapping("/addUser2")
@Transactional
public Integer addUser2(String username,String password){
//插入数据
User user = new User(username,password);
Integer result = userService.insert(user);
//模拟异常
int a = 10/0;
return result;
}
}
使用:
(1)声明式事务只要在需要的方法上添加@Transactional 注解就可以实现了;
(2)进入方法时自动开启事务,方法执行成功会自动提交事务;
(3)如果中途发生了没有处理的异常会自动回滚事务。默认在运行时异常和Error的时候才会回滚,也就是exception的子类中的RuntimeException会回滚。非运行时异常不回滚。
(4)如果异常被捕获,也不会进行处理;(后面演示)
(5)@Transactional 可以用来修饰方法或类:
修饰方法时:需要注意只能应用到 public 方法上,否则不生效。(推荐这种用法)。
修饰类时:表明该注解对该类中所有的 public 方法都生效。
(1)情况1:正常情况:使用注解@Transactional会自动开启和提交事务。url端传参,数据库添加数据成功。
(2)当有异常情况的时候,使用@Transactional会自动回滚事务,数据库也不会添加数据。
(3)当不使用注解@Transactional,但是有异常存在,数据依然会执行成功。
加一个被捕获的算数异常,观察结果:
/**
* @Transactional 在异常被捕获的时候,不会进行回滚。
*/
@RestController
@RequestMapping("/trans")
public class TransactionController{
@Resource
private UserService userService;
@RequestMapping("/addUser")
@Transactional
public Object addUser(String username,String password) {
//插入数据
User user = new User(username,password);
Integer result = userService.insert(user);
try {
// 执⾏了异常代码(0不能做除数)
int i = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return result;
}
}
方式1:使用rollbackfor参数,指定对于所有异常都回滚。
@Transactional(rollbackFor = Exception.class)
方式2:手动回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法setRollbackOnly 就可以实现回滚了。
// ⼿动回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
1、Spring比MySQL的隔离级别多了一个default。
2、MySQL的默认隔离级别:可重复读;Oracle是读已提交。
3、Spring 中事务隔离级别只需要设置 @Transactional ⾥的 isolation 属性即可:
@Transactional(isolation = Isolation.SERIALIZABLE)
4、事务隔离级别解决的是多个事务同时调用一个数据库的问题。
5、Spring中事务隔离级别的分类
(1)Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
(2)Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
(3)Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
(4) Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
(5) Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。
举个栗子
(1)A和B正在写作业,B不想写,就照着A的作业抄,抄完之后B就开开心心的出去玩了,此时A检查发现了一个错误,就对错误进行了修正,那么B此时读到的就是错误的数据,这个现象就是“读未提交”,存在脏读问题;
(2)读已提交:A在写作业的过程中,给A上把锁,B不能抄,要等到A写完提交之后才能抄。那么当早上10.00A开始写作业,这个过程中B只能等着,10.30的时候A把作业写完了,B开始抄,等到10.40 A 又开始检查作业了,发现有问题,又开始修正,于是B抄作业,抄着抄着就突然发现数据变了。这就是出现了“幻读”现象。
脏读与不可重复读的区别:脏读是某一个事务读取了另一个事务未提交的脏数据,但是不可重复读是读取了前一事务提交的数据。
(3)可重复读:A在写作业的时候,B不能抄;B在抄作业的时候,A也不能修改。相当于A写作业的时候加了一个排它锁,B抄作业的时候加了一个共享锁,那么AB互不干扰。
(4)串行化:虽然B在抄作业的时候,A不能进行修改,但是A想着,闲着也是闲着,你抄语文作业,那么我就去修改数学作业,虽然没有直接修改B抄的作业,但是也影响到了B的最终的抄作业的效果,这就是出现了幻读现象。要用串行化来解决:也就是B抄语文作业,A所有的作业都不能修改,抄和写严格执行,不能相互响应。
Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。事务传播机制是保证⼀个事务在多个调用方法间的可控性的(稳定性的)。
设置事务的传播机制:
@Transactional(propagation = Propagation.REQUIRED
事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题。
Spring中默认的传播机制是Propagation.REQUIRED。
controller中代码
@RestController
@RequestMapping("/trans1")
public class UserLogController {
@Autowired
private UserService userService;
@Autowired
private UserLogService userLogService;
//事务定义
@Transactional
@RequestMapping("/addUser1")
public boolean addUser1(String username,String password){
//插入用户表
User user = new User(username,password);
userService.insert(user);
//插入用户日志表
UserLog userLog = new UserLog(username);
userLogService.insertLog(userLog);
return true;
}
}
(1)设置为Required
情况1:当两个都设置为Required,两个代码都正常,则两个都执行成功。
情况2:对userlogService中添加一条异常,另一个userService中的代码正常不做修改,此时观察两个表的情况:
情况3:在userLogService对发生的异常自己进行捕获并回滚,另一个代码正常不做修改,观察是否会影响另一个userinfo表。
总结:
(1)两个代码要么一起执行成功;
(2)要么当有一个发生异常,两个就都进行回滚;
(3)就算对其中一个发生的异常进行捕获并回滚处理,也是两个都进行回滚。
(2)设置为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
情况1:当两个代码都正常的时候,数据都插入成功。
情况2:当有一个有异常的时候,不影响另一个。userLog表没有插入数据,userinfo表插入成功。
(3)设置为NEVER
给userLogService和userService都设置为NEVER级别,代码正常,也抛出异常。
@Transactional(propagation = Propagation.NEVER)
(4)设置嵌套事务
@Transactional(propagation = Propagation.NESTED)
情况1:当userService和userLogService都正常的时候,执行成功。
情况2:当userLogService有异常的时候,都执行失败;
情况3:当userLogService有异常但是自动进行捕获并手动回滚的时候,uerLog表没有插入数据,但是userinfo表不受影响,数据插入成功。
问题1:嵌套事务NESTED和加入事务REQUIRED有什么区别?
(1)共同点:整个事务如果全部执行成功,二者的结果是⼀样的。
(2)不同点:如果事务执行到一半失败了,那么REQUIRED事务整个事务会全部回滚;而嵌套事务NESTED会局部回滚,不会影响上⼀个方法中执行的结果。
问题2:NESTED实现部分回滚的原因?
嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了⼀个保存点,回滚时只回滚到当前保存点,因此之前的事务是不受影响的;而REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别。