为什么需要事务?
事务是将一组操作封装成一个执行单元,该组操作要么全部执行成功,要么全部执行失败,不能只执行成功一部分
如转账:A账户扣100,B账户增100,该组操作得全部执行成功,不能执行一条,否则要么A平白无故少100,要么B平白无故多100
所以需要事务来保障该组操作全部执行成功或全部执行失败
回顾MySQL中事务的使用
-- 开启事务
begin;
-- 执行一组操作
-- 提交事务
commit;
--回滚事务
rollback;
Spring中事务的实现
Spring中操作事务有两种方式:
- 声明式事务(使用注解自动开启事务与提交事务)
- 编程式事务(手动写代码实现事务)
编程式事务
@RestController
public class UserController {
//JDBC事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/delete")
public int delete(Integer id){
if(id==null && id<=0) return 0;
TransactionStatus transactionStatus = null;
try{
//开启事务
transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
int n = userService.delete(id);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
return n;
}catch (Exception e){ //出现异常,进行事务回滚操作
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
}
return 0;
}
}
声明式事务
只需在方法上加@Transactional
注解,在进行方式时开启事务,当不发生异常时,待方法执行完提交事务,如果有异常发生会自动进行事务回滚操作
说明: @Transactional注解如果加在测试方法上,方法执行完会进行回滚操作,目的为了不污染数据库
@RestController
public class UserController2 {
@RequestMapping("/delete")
@Transactional
public int delete(Integer id){
if(id==null || id<=0){
return 0;
}
int n = 0;
n = userService.delete(id);
return n;
}
}
注意事项:
- @Transactional可以修饰方法和类,修饰类时,该注解只对类中的
public方法
生效,修饰方法时,只对public方法
生效 - @Transactional在异常被捕获时,不会对事务进行回滚操作
如果异常被捕获,我们有两种解决方式:
- 在catch代码块中,再将异常抛出
@RequestMapping("/delete")
public int delete(Integer id){
if(id==null || id<0){
return 0;
}
try{
int n = 0;
n = userService.delete(id);
return n;
}catch (Exception e){
throw e; //再将异常抛出
}
return 0;
}
- 手动进行回滚事务的操作
@RequestMapping("/delete")
public int delete(Integer id){
if(id==null || id<0){
return 0;
}
try{
int n = 0;
n = userService.delete(id);
return n;
}catch (Exception e){
//手动进行事务回滚操作
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 0;
}
声明式事务失效场景
- @Transactional修饰的方法为非public修饰的方法
- @Transactional设置了超时时间timeout,当方法的执行时间超过了该超时时间,事务会自动进行回滚
- 代码中有try-catch代码块,当异常被捕获时,不会自动进行回滚操作
- 数据库如果本身不支持事务,@Transactional只是给数据库通知开启,提交,回滚事务
- 在方法中(没使用@Transactional)调用了别的使用@Transactional的方法,事务也会失效
Spring中事务的隔离级别
Spring中事务的隔离级别有以下五种:
- Isolation.
DEFAULT
:以连接数据库的隔离级别为准 - Isolation.
READ_UNCOMMITTED
:读未提交 - Isolation.
READ_COMMITTED
:读已提交(解决了脏读
) - Isolation.
REPEATABLE_READ
:可重复读(解决了脏读
与不可重复读
) - Isolation.
SERIALIZABLE
:串行化(解决了脏读
,不可重复读
,幻读
)
Spring中设置事务的隔离级别:
@Transactional(isolation = Isolation.SERIALIZABLE) //在@Transactional中添加isolation 属性
Spring事务的传播机制
Spring事务的传播机制描述的是,多个开启事务的方法,在相互调用时,事务是如何在这些方法之间传递的
在搞清楚事务传播机制之前,先搞懂两个概念:
非事务
方式运行:就是方法中每条操作执行完就会自动提交,不会再将该方法的多个操作打包成一个原子的操作执行,不保证其原子性事务挂起
:比如A方法调用B方法,会将A方法开启的Transaction挂起,方法B中任何操作数据库的操作都不在该Transaction的管理之下,也就是两个事务是相互独立,互不干扰
方法A中调用方法B,下述描述中的
当前
指的的方法A
Spring事务的传播机制有以下七种:
- Propagation.
REQUIRED
:默认的事务传播机制
,如果当前(A)存在事务,则(B)加入该(A)事务,如果当前(A)不存在事务,则(B)创建新事务运行 - Propagation.
SUPPORTS
:如果当前存在事务,则加入该事务,如果当前不存在事务,则以非事务方式运行 - Propagation.
MANDATORY
:如果当前存在存在事务,则加入该事务,如果当前不存在事务,则抛异常 - Propagation.
REQUIRES_NEW
:如果当前(A)存在事务,将当前事务挂起,(B)会创建新的事务执行,两个事务相互独立,互不干扰,如果房前不存在事务,(B)也会创建新事务执行 - Propagation.
NOT_SUPPORTED
:(B)以非事务方式运行,如果当前(A)存在事务,将(A)事务挂起 - Propagation.
NEVER
:以非事务方式运行,如果当前存在事务,则抛异常 - Propagation.
NESTED
:如果当前(A)存在事务,则(B)创建一个事务当作当前(A)事务的嵌套事务来运行,如果当前不存在事务,则(B)创建新事物运行(等价于REQUIRED)
Spring中设置事务的传播机制:
@Transactional(propagation = Propagation.REQUIRED)
以上传播机制分为三类:
- 支持当前事务:REQUIRED,SUPPORTS,MANDATORY
- 不支持当前事务:REQUIRES_NEW,NOT_SUPPORTED,NEVER
- 嵌套事务:NESTED
面试题: 加入事务,嵌套事务,事务挂起的区别?
- 加入事务:如果该事务中某个操作执行失败,那整个事务就会进行回滚操作
- 事务挂起:两个事务相互不影响,某个事务中的操作只会影响到自己所在事务,不会对别的事务产生影响
- 嵌套事务:A调用B,B是以嵌套事务的方式执行,此时如果B中某个操作执行失败,那此时只有嵌套的事务进行回滚,不会影响外部的A事务,如果A事务中的某个操作执行失败,那A事务连同嵌套事务都会进行回滚