目录
一、什么是事务,为什么要用事务
二、Spring声明式事务
1、@Transactional的使用
事务回滚
注意:异常被捕获,不会发生事务回滚
2、@Transactional 作⽤范围
3、@Transactional 参数说明
4、@Transactional的工作原理
三、事务的特性以及隔离级别(重要)
事务的特性(ACID)
Spring隔离级别
四、Spring事务的传播机制
为什么需要事务传播机制
事务传播机制的种类
Propagation.REQUIRED:
Propagation.SUPPORTS
Propagation.NEVER
Propagation.NESTED
定义:事务就是将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败
为什么要用事务?举例如下:
转账分为两步:
第一步:A账户:-100¥
第二部:B账户:+100¥
如果没有事务,那么,第一步执行成功,第二部失败,就会导致100¥不见了,不知道转哪里去了。但是当有了事务,着两步操作要么一起成功要么一起失败,这样就可以解决这个问题
分类:Spring中的事务操作分为两类:
- 编程式事务(手动写代码操作事务,不常用,复杂)
- 声明式事务(利用注解自动开启和提交事务,常用,简便)
下面主要介绍声明式事务
声明式事务的使用很简单,只需要在方法上添加@Transactional注解就可以实现了
不需要手动开启事务和提交事务,进入方法时自动开启事务,方法执行完就会自动提交事务,如果中途发生了没有处理的异常就会自动回滚事务。
代码示例:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
@Mapper
public interface UserMapper {
@Insert("insert into userinfo(username,password) values (#{username},#{password})")
int add(UserInfo userInfo);
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int add(UserInfo userInfo){
return userMapper.add(userInfo);
}
}
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add")
@Transactional
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("huhu");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
System.out.println("result:" + result);
int num = 10/0;
// 3.将结果给前端
return result;
}
}
执行以上代码:结果如下,发生算数异常
但是sql执行没有异常:
这时候我们查看以下数据库有没有该数据,有没有进行数据回滚?
图中并没有我添加的数据,由此可见,@Transactional注解进行了数据回滚。
问题:当自己把程序捕获以后,代表着事务不会发现程序发生了异常,在这种情况下,事务不会发生回滚。
@RequestMapping("/add")
@Transactional
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("huhu");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
System.out.println("result:" + result);
try{
int num = 10/0;
}catch (Exception e){
}
// 3.将结果给前端
return result;
}
解决方案1: 将异常继续抛出去(代理对象就能感知到异常,也能够回滚到事务)
@RequestMapping("/add") @Transactional public int add(){ // 1.非空判断 UserInfo userInfo = new UserInfo(); userInfo.setUsername("万叶"); userInfo.setPassword("123"); // 2.调用service执行添加 int result = userService.add(userInfo); System.out.println("result:" + result); try{ int num = 10/0; }catch (Exception e){ throw e; } // 3.将结果给前端 return result; }
解决方案2:使用代码手动回滚事务
@RequestMapping("/add") @Transactional public int add(){ // 1.非空判断 UserInfo userInfo = new UserInfo(); userInfo.setUsername("万叶"); userInfo.setPassword("123"); // 2.调用service执行添加 int result = userService.add(userInfo); System.out.println("result:" + result); try{ int num = 10/0; }catch (Exception e){ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } // 3.将结果给前端 return result; }
@Transactional 可以用来修饰类或者方法(public)
一般情况下,以下参数都是默认的
@Transactional是基于AOP实现的,AOP又是基于动态代理实现的。
如果目标对象实现了接口,默认情况下就会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB的动态代理。
@Transactional在开始执行业务之前,通过代理先开始事务,在执行成功之后再提交事务。如果中途遇见异常,则回滚事务。
@Transactional实现思路预览:
@Transactional具体执行细节如下图所示:
脏读:一个事务读取到了另一个事务修改的数据以后,后一个事务又进行了回滚操作,从而导致第一个事务读取到的数据是错误的。
不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了
幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务又新增了一部分数据。
原子性(不可分割性)(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束中间某个环节。事务在执行过程中发生错误,就会回滚到事务开始前的状态,相当这个事务从来没有执行过
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,包含资料的精确度、串联性以及后续数据库可以自发性的完成预定的工作。
持久性(Isolation):事务处理结束以后,对数据的修改就是永久的,即便系统故障也不会丢失
隔离性(Durability):数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以放置多个事务并发执行时由于交叉执行导致数据的不一致。事务的隔离分为不同级别,包括读未提交,读提交,可重复读,串行化。
Spring事务隔离级别有五种:
Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复度。
Isolation.REPEATABLE_READ:可重复读,解决了不可重复度,但是存在幻读。
Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但是性能太低。
默认情况下,是以SQL的事务隔离级别为主的(Isolation.DEFAULT)。
但是当Sping设置了事务隔离级别以后,就会以Spring的事务隔离级别为主。以下就是以SERIALIZABLE为主的。
@RequestMapping("/add")
@Transactional(isolation = Isolation.SERIALIZABLE)
public int add(){
}
事务的隔离级别保证了多个并发事务执行的可控性。
定义:事务的传播机制定义了多个包含事务的方法,相互调用时,事务是如何在这些方法间进行传递的。(规定多个事务在相互调用时,事务的执行行为)
事务传播机制是保证一个事务在多个调用方法间的可控性的(稳定性)
事务的传播机制解决的是一个事务在多个节点(方法)中传递的问题:
Propagation.REQUIRED:默认的事务传播级别,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立互不干扰。
Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED
默认的事务传播级别,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
示例:
UserService:
@Transactional
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("add result -> "+result);
insert(userInfo);
return result;
}
@Transactional
public int insert(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("insert resullt -> "+ result);
int num = 10 / 0;
return result;
}
Controller:
@RequestMapping("/add")
@Transactional(propagation = Propagation.REQUIRED)
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("万叶");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
return result;
}
此时结果是,报错是算数异常,进行了事务回滚,数据库中没有添加任何数据
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
示例:
UserService
@Transactional(propagation = Propagation.SUPPORTS)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("add result -> "+result);
insert(userInfo);
return result;
}
@Transactional(propagation = Propagation.SUPPORTS)
public int insert(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("insert resullt -> "+ result);
int num = 10 / 0;
return result;
}
UserController:
@Transactional(propagation = Propagation.SUPPORTS)
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("万叶");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
return result;
}
当前调用链不存在事务,结果是数据库中添加了两条数据,并且报错是算数异常,但是并没有进行数据回滚
以非事务方式运行,如果当前存在事务,则抛出异常
UserController:
@RequestMapping("/add")
//调用链存在事务
@Transactional(propagation = Propagation.REQUIRED)
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("万叶");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
return result;
}
USerService:
@Transactional(propagation = Propagation.NEVER)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("add result -> "+result);
insert(userInfo);
return result;
}
@Transactional(propagation = Propagation.NEVER)
public int insert(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("insert resullt -> "+ result);
int num = 10 / 0;
return result;
}
说明程序在发现有事务以后,就没有运行了,不存在事务回滚,而是发现事务以后,程序就没有运行,而是直接抛出异常。
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED
示例:
UserService:
注意:为什么要try-catch
如果不try-catch就会报错,报错就会使得整个程序进行回滚,这里为了使得满足NESTED的情况,就进行了try-catch,就能使得回滚到事务保存点。
@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("add result -> "+result);
return result;
}
@Transactional(propagation = Propagation.NESTED)
public int insert(UserInfo userInfo){
int result = userMapper.add(userInfo);
System.out.println("insert resullt -> "+ result);
try{
int num = 10 / 0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
UserController:
@RequestMapping("/add")
@Transactional(propagation = Propagation.REQUIRED)
public int add(){
// 1.非空判断
UserInfo userInfo = new UserInfo();
userInfo.setUsername("影");
userInfo.setPassword("123");
// 2.调用service执行添加
int result = userService.add(userInfo);
userService.insert(userInfo);
return result;
}
结果如下: