目录
一. Spring事务
1.1 事务定义
将一组操作封装为一个执行单元,(也就是将一组操作绑定到一起,)这些执行单元要么全部执行成功,要么全部执行失败.
我们通过转账操作来体会事务的重要性.
比如转账操作分为两步:
第一步操作:A账户-100元
第二部操作:B账户+100元
如果此时要是没有事务的话,第一步操作执行完毕,但是第二步操作可以因为一些原因没有执行成功,那么A账户的100元就不见了,B账户也没有进行增加100元.而如果采用事务就可以解决这个问题,事务就会让这组操作进行绑定,要么一起执行成功,要么一起执行失败.
在Java应用程序中,Spring框架提供了对事务的支持.事务本质是一组数据库操作,他们要么全部执行成功,要么全部执行失败.以确保数据的一致性和完整性,Spring框架的事务管理功能可以帮助我们简化处理事务的复杂性
1.2 Spring中事务实现
- 编程式事务(手动写代码进行事务操作)
- 声明式事务(利用注解的方式进行事务操作)
我们主要学习的是声明式事务
1.3 声明式事务
声明式事务的实现很简单,只需要在响应的方法上添加@Transactional注解就可以实现了,无需进行手动的开启事务和提交事务.
@Transantional注解会在进入方法时自动开启事务,方法执行完成会自动的提交事务,如果中途发生了没有处理的异常就会自动的回滚事务.
具体代码如下:
@Autowired
private UserService userService;
@Transactional //当发生异常之后,但是异常被捕获了,就不会发生回滚 如果不捕获异常,就会发生回滚
@RequestMapping("/add")
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhangsan");
userinfo.setPassword("123");
int i = userService.add(userinfo);
System.out.println(i);
System.out.println(10 / 0);
return i;
}
可以看到上面的代码中我们手动的写了一个异常, 由于我们设置了注解,并且这个异常我们也没有进行处理,所以当发生了没有处理的异常之后,事务就会自动的进行回滚.数据库中也并没有插入对应的数据.
如果我们把这个异常进行了处理,那么事务就不会进行回滚.
代码如下:
@Autowired
private UserService userService;
@Transactional //当发生异常之后,但是异常被捕获了,就不会发生回滚 如果不捕获异常,就会发生回滚
@RequestMapping("/add")
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhangsan");
userinfo.setPassword("123");
int i = userService.add(userinfo);
System.out.println(i);
try {
System.out.println(10 / 0);
} catch (Exception e) {
e.printStackTrace();
}
return i;
}
由于事务没有进行回滚,所以数据就插入到数据库中了
1.4 @Transactional注解的作用范围
@Transactional 可以用来修饰类和方法:
- 修饰方法时,需要注意该注解只能应用到public方法上,否则不会生效
- 修饰类时,表明该注解对该类中的所有public方法生效
1.5 注意事项
通过上述的代码我们可以看到,当发生了异常,但是这个异常已经被捕获的情况下,事务是不会进行回滚的.只有发生了异常,并且这个异常没有被捕获的情况下,就会发生回滚.
我们想要的是只要发生了异常,尽管已经捕获了该异常,也要进行事务的回滚.
解决方案1:
对于捕获的异常,事务是不会自动回滚的,我们只要将这个异常重新抛出去,事务就会自动回滚.
@Autowired
private UserService userService;
@Transactional //当发生异常之后,但是异常被捕获了,就不会发生回滚 如果不捕获异常,就会发生回滚
@RequestMapping("/add")
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhsan");
userinfo.setPassword("123");
int i = userService.add(userinfo);
System.out.println(i);
try {
System.out.println(10 / 0);
} catch (Exception e) {
e.printStackTrace();
throw e; //重新抛出去
}
return i;
}
当我们把这个异常重新抛出去之后,因为这个异常并没有被捕获,所以事务就自动的进行回滚.数据库里面也是没有添加进去任何数据的.
解决方案2:
手动的回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus()就可以得到当前的事务.然后设置回滚方法setRollbackOnly就可以实现回滚了.
@Autowired
private UserService userService;
@Transactional //当发生异常之后,但是异常被捕获了,就不会发生回滚 如果不捕获异常,就会发生回滚
@RequestMapping("/add")
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhsan");
userinfo.setPassword("123");
int i = userService.add(userinfo);
System.out.println(i);
try {
System.out.println(10 / 0);
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return i;
}
1.6 @Transactional的工作原理
@Transactional是基于AOP实现的,AOP又是基于动态代理实现的.如果目标对象实现了接口,默认情况下使用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理.
@Transactional在开始执行事务之前,会先通过代理开启事务,在执行成功后再提交事务,如果中途遇到了异常,则回滚事务.
二. 事务的隔离级别
2.1 事务的特性
事务有4大特性:原子性,持久性,一致性,隔离性.
- 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成.不会结束在中间的某个环节.事务在执行的过程中如何发生了错误,就会回滚到事务执行之前的状态.
- 一致性:在事务执行开始之前和结束之后,数据库的完整性没有被破坏.
- 持久性:事务处理结束后,对数据的修改是永久的.
- 隔离性:数据库允许多个事务并发的执行,对其进行读写和修改的能力.隔离性是防止多个事务之间并发执行而导致的数据不一致.
可以发现事务的特性和mysql中事务的特性是完全一致的.
关于MySQL中的事务可以参考:MySQL索引、事务_陌上 烟雨齐的博客-CSDN博客
在上面的4种特性中,只有事务的隔离级别是可以进行设置的.设置事务的隔离级别可以有助于我们保障多个事务并发执行更加的可控.
2.2 事务隔离级别
Spring中事务的隔离级别有5种:
- lsolation.DEFAULT: 以连接的数据库的事务隔离级别为准.
- lsolation.REAC_COMMITTED:读未提交,可以读到没有提交的事务,存在脏读
- Isolation.READ_COMMITTED: 读以提交,解决了脏读,只能读到已经提交的事务,而且同一时刻读到的数据可能不一致,存在不可重复读.
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但是存在幻读.
- Isolation.SERIALIZABLE:串行化.彻底放弃并发执行事务,可以解决所有的事务并发问题.
可以看出,相比于MySQL,spring的事务隔离级别只是多了一个以数据库的全局事隔离级别为准.
spring中设置事务的隔离级别只需要设置@Teansactional里面的isolation属性即可
@Autowired
private UserService userService;
@Transactional(isolation = Isolation.SERIALIZABLE) //当发生异常之后,但是异常被捕获了,就不会发生回滚 如果不捕获异常,就会发生回滚
@RequestMapping("/add")
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhsan");
userinfo.setPassword("123");
int i = userService.add(userinfo);
System.out.println(i);
try {
System.out.println(10 / 0);
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return i;
}
上述代码就是把事务的隔离级别设置为串行化了.
事务的隔离级别解决的是多个事务同时调用数据库的问题
三. 事务的传播机制
3.1 事务的传播机制是什么
spring事务的传播机制定义了多个包含事务的方法,在相互调用时,事务是如何在这些方法中进行传递的.
为什么要有事务的传播机制?
事务的传播机制是保证一个事务在多个方法之间调用的可控性的.
3.2 事务的传播机制有哪些
在Spring框架中,事务的传播机制是用于控制多个事务方法之间的事务行为的方法,当一个事务方法调用了另一个事务方法时,传播机制定义了新方法如何参与现有事务或者启动新事物.
Spring事务传播机制共有7种:
-
REQUIRED(默认): 如果当前存在事务,则加入该事务,否则创建一个新事务。这是最常见的传播行为,确保多个方法都在同一个事务中执行。
-
SUPPORTS: 如果当前存在事务,则加入该事务,否则以非事务方式执行。这种传播行为适用于非事务方法,它们可以在事务内或事务外执行,但不会启动新事务。
-
MANDATORY: 要求当前存在事务,否则会抛出异常。该传播行为确保方法只能在已经存在的事务中执行,否则会引发异常。
-
REQUIRES_NEW: 无论当前是否存在事务,都会启动一个新事务。如果当前存在事务,会将现有事务挂起。这样的传播行为用于需要独立事务的情况。
-
NOT_SUPPORTED: 以非事务方式执行方法,如果当前存在事务,则将其挂起。这种传播行为适用于需要避免事务干扰的情况。
-
NEVER: 要求当前不存在事务,否则会抛出异常。该传播行为确保方法不会在事务内执行。
-
NESTED: 如果当前存在事务,则在嵌套事务中执行。如果没有现有事务,则与REQUIRED行为相同。嵌套事务是独立于外部事务的内部事务,可以部分回滚。
事务的传播机制可以使用@Transactional注解进行指定当前事务的传播机制.
@Transactional(propagation = Propagation.REQUIRED)
public void parentMethod() {
// ...
childMethod();
// ...
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
// ...
}