Spring事物/事物传播机制
1.事物定义
将⼀组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败。
2.Spring中的事物的实现
Spring 中的事务操作分为两类:
1. ⼿动操作事务。
2. 声明式⾃动提交事务。
2.1 手动式操作事物
步骤(类似MySql事物):
- 开启事物(获取事物)
- 提交事物
- 回滚事物
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus
代码:
@RestController
public class UserController {
@Autowired
public UserService userService;
@Autowired
public LogService logService;
//Spring手动操作事物
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public int add(UserInfo userInfo){
// 非空效验【验证用户名和密码不为空】
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) return 0;
// 开启事务(获取事务)
TransactionStatus transactionStatus =
dataSourceTransactionManager.getTransaction(transactionDefinition);
int result=userService.add(userInfo); //调用了Service->Mapper里面的方法操作数据库
//返回一个受影响的行数
System.out.println(result);
// transactionManager.rollback(transactionStatus); // 回滚事务
dataSourceTransactionManager.commit(transactionStatus); //提交事物
return result;
}
}
2.2 Spring声明式事物(自动事物)
实现声明式事物只需添加 @Transactional 注解就可以实现
@Transactional 可以⽤来修饰⽅法或类作用范围:
- 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。
- 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效。
代码:
// 使用声明式事务
@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
// 非空效验【验证用户名和密码不为空】
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:" + result);
try {
int num = 10 / 0; //此处手动故意算数异常,测试回滚事物
} catch (Exception e) {
//解决的两种办法(回滚)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// throw e;
}
return result;
}
对于声明式事物需注意:
对于声明式事物,捕获到异常需要回滚事物时,需要特殊处理,分为几种情况:
1.捕获到异常但是未做处理,
2.捕获到异常,并且抛出异常,
3.捕获到异常,并且手动回滚事务.
三种情况代码演示如下:
第一种情况对应代码:
// 使用声明式事务
@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
// 非空效验【验证用户名和密码不为空】
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:" + result);
try {
int num = 10 / 0;
} catch (Exception e) {
//解决的两种办法(回滚)
//TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// throw e;
System.out.println(e.getMessage()); //此处并未对捕获到异常之后做出处理 ,事物不会回滚
}
return result;
}
第二种情况对应代码:
// 使用声明式事务
@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
// 非空效验【验证用户名和密码不为空】
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:" + result);
try {
int num = 10 / 0;
} catch (Exception e) {
throw e; //此处对捕获到的异常重新抛出,事物会自动回滚
}
return result;
}
第三种情况对应代码:
// 使用声明式事务
@Transactional // 在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚事务
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
// 非空效验【验证用户名和密码不为空】
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:" + result);
try {
int num = 10 / 0;
} catch (Exception e) {
//此处是捕获到异常手动回滚事物
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
2.3 @Transactional ⼯作原理
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路预览:
3. 事物隔离级别
3.1 事物特性
事务有4 ⼤特性(ACID),原⼦性、持久性、⼀致性和隔离性
原⼦性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。
⼀致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯
作。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
隔离性:数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏(Serializable)。
3.2 Spring 中设置事务隔离级别
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置,事物隔离级别有五种:
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
- Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
- Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
- Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低
4. Spring 事物传播机制
Spring 事务传播机制定义了多个包含了事务的⽅法,相互调⽤时,事务是如何在这些⽅法间进⾏传递的。
事物传播机制的作用:事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。
事物隔离级别:解决的是多个事物同时调用数据库的问题
事物传播机制:解决一个事物中多个方法传递的问题:
4.1 Spring事物传播机制包括:
- Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
- Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
- Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
- Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存事务,则把当前事务挂起,就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的务,且开启的事务相互独⽴,互不⼲扰。
- Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
- Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
- Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED
以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:
4.2 Spring事物传播机制演示
4.2.1 支持当前的事物的代码演示:
//支持当前事物
@RestController
public class UserController {
@Autowired
public UserService userService;
@Autowired
public LogService logService;
@Transactional(propagation = Propagation.REQUIRED)//默认
@RequestMapping("/add4")
public int add4(UserInfo userInfo){ //此处的UserInfo 是一个普通类对象,包含用户的一些信息
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int userResult=userService.add(userInfo); //添加一个用户在数据库
System.out.println("添加用户:" + userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDe("添加用户结果:" + userResult);
int logResult =logService.add(logInfo); //保存一个添加用户日志
return userResult;
}
}
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
}
@Service
public class LogService {
@Resource
private LogMapper logMapper;
//支持当前事物
@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
//此处用意是当发生异常时,整个事物rollback
int number = 10 / 0;
return result;
}
}
在上述三个代码块流程中:
- UserService 中的保存⽅法正常执⾏完成。
- LogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚。
- 数据库中没有插⼊任何数据,也就是UserService ⽤户插⼊⽅法也回滚了。
4.2.2 不支持当前事物代码演示
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/add5")
public int add5(UserInfo userInfo){
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int userResult=userService.add(userInfo);
System.out.println("添加用户:" + userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDe("添加用户结果:" + userResult);
int logResult =logService.add(logInfo);
return userResult;
}
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
}
@Service
public class LogService {
@Resource
private LogMapper logMapper;
//不支持当前事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
int number = 10 / 0;
return result;
}
}
不支持当前事物代码演示的结果为:数据库中用户添加成功了,日志打印被rollback了,因为添加用户(UserService)与添加日志(LoginService)都是不支持当前Controller的事物,UserService和LoginService都有自己的事物,如果自己的事物崩了,不会影响到其他事物.
4.2.3 NESTED 嵌套事务 代码演示
//嵌套事物
@Transactional
@RequestMapping("/add6")
public int add6(UserInfo userInfo){
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int userResult=userService.add(userInfo);
System.out.println("添加用户:" + userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDe("添加用户结果:" + userResult);
int logResult =logService.add(logInfo);
return userResult;
}
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
return result;
}
}
//嵌套事物
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
int number = 10 / 0; //出现异常
return result;
}
嵌套事物原理是当前方法的事物嵌套进调用当前方法的那个方法的事物,同时建立一个回滚点
1.UserController 中调⽤了 UserService 的添加⽅法,UserService 使⽤ NESTED 修饰循环嵌套UserController 的事务,并成功执⾏了添加⽅法。
2.LogService 使⽤ NESTED 修饰循环嵌套了上⼀个调⽤类(UserController )的事务,执⾏了添加⽅法,但在执⾏的过程中出现了异常,回滚当前事务,⽇志添加失败。
3.因为是嵌套事务,所以⽇志添加回滚之后,往上找调⽤它的⽅法和事务回滚了⽤户添加,所以⽤户添加也失败了,所以最终的结果是⽤户表和⽇志表都没有添加任何数据。
如果把出现异常手动回滚,就只会回滚到上一个事物的回滚点,如下代码:
//嵌套事物
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
int result = logMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
try {
int number = 10 / 0;
} catch (Exception e) {
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//int number = 10 / 0;
return result;
}
此代码运行之后的结果为:添加用户成功,但是添加日志会失败,说明了嵌套事务可以实现部分事务回滚。
4.2.4 嵌套事物与加入事物的区别
1.整个事务如果全部执⾏成功,⼆者的结果是⼀样的.
2.如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;⽽嵌套事务会局部回滚,不会影响上⼀个⽅法中执⾏的结果.