Spring事务和事务传播机制
目录
- 一.事务
- 1.1 介绍
- 1.2 事务的操作
- 二.Spring事务
- 2.1 Spring编程式事务(了解)
- 2.2 Spring声明式事务 @Transactional
- 三.@Transactional详解
- 3.1 异常回滚属性 rollbackFor
- 3.2 MySQL事务隔离级别
- 3.3 Spring事务隔离级别 isolation
- 3.4 事务传播机制 propagation
- 3.5 NESTED和REQUIRED的区别
一.事务
1.1 介绍
事务是一组操作的集合,是一个不可分割的操作,事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求,最终这组操作要么同时成功,要么同时失败
1.2 事务的操作
事务的操作主要分为:
- 开启事务:在一组操作开始前开启事务
- 提交事务:这组操作全部成功,则提交事务
- 回滚事务:这组操作中任何一个操作出现异常,则回滚事务
// 开启事务
start transaction;
//提交事务
commit;
//回滚事务
rollback;
二.Spring事务
Spring也对事务进行了实现,其中事务操作分为两类:编程式事务
和声明式事务
。编程式事务通过手动编写代码来进行事务操作,而声明式事务利用注解
自动开启和提交事务
2.1 Spring编程式事务(了解)
Spring内置了两个对象:
- DataSourceTransactionManager:事务管理器,用去获取事务,提交事务或回滚事务
- TransactionDefinition:事务的属性,在获取事务时传递给事务管理器
import lombok.extern.slf4j.Slf4j;
import org.example.transdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//JDBC事务管理器
@Autowired
private DataSourceTransactionManager transactionManager;
//定义事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/register")
public Boolean register(String name,String password) {
// 开启事务
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
Integer result=userService.register(name,password);
log.info("插入操作影响行数为:{}", result);
// 回滚事务
transactionManager.rollback(transaction);
// 提交事务
transactionManager.commit(transaction);
return true;
}
}
2.2 Spring声明式事务 @Transactional
Spring声明式事务即在需要事务的方法上添加@Transactional
注解,进入方法时自动开启事务,方法执行完自动提交事务,如果方法在执行过程中出现异常,且异常未被捕获
,则回滚事务
;如果异常被程序捕获,则提交事务。@Transaction注解即可以修饰方法
也可以修饰类
,当修饰方法时,方法需要使用public
修饰,否则不生效;当修饰类时,对类中所有public方法都生效
import lombok.extern.slf4j.Slf4j;
import org.example.transdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
@Transactional
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public Boolean register(String name,String password) {
try{
Integer result = userService.register(name,password);
log.info("插入操作影响行数为:{}", result);
return true;
} catch (Exception e){
// e.printStackTrace(); 捕获异常,提交事务
// throw e; 抛出异常,回滚事务
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 回滚事务
return false;
}
}
}
三.@Transactional详解
@Transaction注解中含有三个常见属性:
rollbackFor
:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型isolation
:事务的隔离级别,默认值为Isolation.DEFAULT
propagation
:事务的传播几种,默认值为Propagation.REQUIRED
3.1 异常回滚属性 rollbackFor
@Transactional
默认只在遇到运行时异常(RuntimeException及其子类)
和Error
时才会回滚,非运行时异常不进行回滚,如果需要指定事务回滚的异常类型,就需要通过rollbackFor属性来配置
import lombok.extern.slf4j.Slf4j;
import org.example.transdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
// 对于所有异常的抛出都进行回滚
@Transactional(rollbackFor = Exception.class)
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public Boolean register(String name,String password) {
try{
Integer result = userService.register(name,password);
log.info("插入操作影响行数为:{}", result);
return true;
} catch (Exception e){
// e.printStackTrace(); 捕获异常,提交事务
// throw e; 抛出异常,回滚事务
return false;
}
}
}
3.2 MySQL事务隔离级别
在并发场景下,MySQL事务会一些数据问题:
脏读
:A事务正在修改数据但未提交,此时B事务去读取这条数据,这时A事务回滚,B事务读取的就是脏数据幻读
:B事务前后两次读取同一个范围的数据,在B事务两次读取的过程中A事务新增了数据,导致B事务后一次读取到前一次查询没有看到的行不可重复读
:B事务读取了两次数据,在这两次读取过程中A事务修改了数据,导致B事务这两次读取出来的数据不一致
了解完这些数据问题后,我们接着来介绍MySQL的事务隔离级别。SQL标准定义了四种隔离级别:
读未提交(READ UNCOMMITED)
:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据读提交(READ COMMITED)
:读已提交,也叫提交读,该隔离级别的事务能读到已经提交事务的数据可重复读(REPEATABLE READ)
:MySQL的默认事务隔离级别
,事务不会读到其他事务对已有数据的修改,即便其他事务已经提交,这样确保了同一事物多次查询结果一致,但是其他事务插入新数据是可以感知到的串行化(SERIALIZABLE)
:序列化,事务最高隔离级别,强制事务排序使得冲突不会发生,从而解决脏读,幻读和不可重复读问题,但执行效率较低
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITED) | √ | √ | √ |
读提交(READ COMMITED) | × | √ | √ |
可重复读(REPEATABLE READ) | × | × | √ |
串行化(SERIALIZABLE) | × | × | × |
3.3 Spring事务隔离级别 isolation
Spring中事务隔离级别包含五种:
Isolation.DEFAULT
:以连接数据库的事务隔离级别为主Isolation.READ_UNCOMMITED
:读未提交,对应SQL标准中的读未提交Isolation.READ_COMMITED
:读已提交,对应SQL标准中的读已提交Isolation.REPEATABLE_READ
:可重复读,对应SQL标准的可重复读Isolation.SERIALIZABLE
:串行化,对应SQL标准的串行化
3.4 事务传播机制 propagation
事务的传播机制指的是当多个事务方法存在调用关系
时,事务是如何在这些方法之间进行传播的,Spring的事务传播机制有七种,以下结合类比的场景(一对新人要结婚,是否需要房子)进行介绍:
Propagation.REQUIRED
:默认的事务传播级别,如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新事务(需要有房子,如果你有房就一起住;如果没有房就一起买房)Propagation.SUPPORTS
:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行(可以有房子,如果有房就一起住,如果没房就租房)Propagation.MANDATORY
:强制性,如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常(必须有房子,如果没房就不结婚)Propagation.REQUIRES_NEW
:创建一个新事务,如果当前存在事务,则把当前事务挂起,即不管外部方法是否开启事务,都会新增开启自己的事务,且开启的事务相互独立,互不干扰(必须买新房,不管有没有房子,必须要一起买新房,即使有房也不住)Propagation.NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不需要房,不管有没有房都不住,必须租房)Propagation.NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常(不能有房子)Propagation.NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果没有事务,则等价于Propagation.REQUIRED
(如果没房就一起买房,如果有房就把房作为根据地来做点生意)
3.5 NESTED和REQUIRED的区别
- 整个事务如果全部执行成功,二者结果相同
- 如果事务一部分执行成功,REQUIRED加入事务会导致
整个事务回滚
,NESTED嵌套事务可以实现局部回滚
,不会影响上一个方法中执行的结果
- 使用NESTED
//UserController类
import lombok.extern.slf4j.Slf4j;
import org.example.transdemo.service.LogService;
import org.example.transdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
@Transactional
public class UserController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@RequestMapping("/register")
public Boolean register(String name,String password) {
Integer result = userService.register(name,password);
log.info("用户表插入操作影响行数为:{}", result);
Integer logCount = logService.insertLog(name,"register");
log.info("日志表插入操作影响行数为:{}", result);
return true;
}
}
//UserService类
import org.example.transdemo.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer register(String name,String password) {
return userInfoMapper.insert(name,password);
}
}
//LogService类
import org.example.transdemo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer insertLog(String name,String op){
Integer result = logInfoMapper.insertLog(name,op);
try{
int num = 10/0;
} catch (Exception e){
// 回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
使用NESTED可以让嵌套事务部分回滚,即仅回滚insertLog方法,用户表插入数据成功,日志表插入失败
- 使用REQUIRED
//只需要修改insertLog方法的事务传播机制,其他类保持不变
import org.example.transdemo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer insertLog(String name,String op){
Integer result = logInfoMapper.insertLog(name,op);
try{
int num = 10/0;
} catch (Exception e){
// 回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
使用REQUIRED会致整个事务回滚,用户表和日志表都插入失败