Spring 事务是 Spring 框架提供的一种机制,用于管理数据库操作或其他资源的一组相关操作,以确保它们在一个原子、一致、可靠和隔离的执行单元内进行。事务用于维护数据的完整性并支持并发访问数据库时的数据一致性。
Spring 事务的主要特点包括:
Spring 事务通过 AOP(面向切面编程)实现,Spring事务操作分为了两种方式:
使用 Spring 事务可以将多个数据库操作或其他资源访问操作组织成一个逻辑单元,并确保这些操作要么全部成功执行,要么全部回滚。这有助于提高应用程序的可靠性、一致性和并发性。
其实在之前的MySQL部分,我们对事务已经有了初步的了解,忘记的小伙伴可以查看: MySQL:索引事务
Spring编程式事务是是一种通过编写代码显式管理事务的方法,而不依赖于注解或配置文件。它提供了更细粒度的事务控制,允许在方法级别或代码块级别指定事务的起始、提交和回滚。
Spring手动操作事务和MySQL 操作事务类似,主要有三个操作步骤:
SpringBoot
内置了两个对象,DataSourceTransactionManager
用来获取事务(开启事务)、提交或回滚事务的,而TransactionDefinition
是事务的属性,在获取事务的时候需要将
TransactionDefinition
传递进去从而获得一个事务TransactionStatus
,实现代码如下:
package com.example.demo.controller;
import com.example.demo.service.UserService;
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;
import javax.annotation.Resource;
//编程式事务:
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
//Jdbc事务管理器
@Resource
private DataSourceTransactionManager transactionManager;
//定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/del")
public int del(Integer id){
if(id == null || id < 0){
return 0;
}
//1. 开启事务
TransactionStatus transactionStatus = null;
int result = 0;
try{
transactionStatus = transactionManager.getTransaction((transactionDefinition));
//业务操作,删除用户
result = userService.del(id);
System.out.println("删除: "+result);
//2. 提交事务/回滚事务
transactionManager.commit(transactionStatus);//提交事务
}catch (Exception e){
if(transactionStatus != null){
transactionManager.rollback(transactionStatus);//回滚事务
}
}
return result;
}
}
启动程序,然后在浏览器中输入:即为删除id为1的admin
可以看到管理员成功被删除.
若要实现回滚操作:
@RequestMapping("/del")
public int del(Integer id){
if(id == null || id < 0){
return 0;
}
//1. 开启事务
TransactionStatus transactionStatus = null;
int result = 0;
transactionStatus = transactionManager.getTransaction((transactionDefinition));
//业务操作,删除用户
result = userService.del(id);
System.out.println("删除: "+result);
//2. 提交事务/回滚事务
// transactionManager.commit(transactionStatus);//提交事务
transactionManager.rollback(transactionStatus);//回滚事务
return result;
}
我们可以看到,虽然编程式事务可以实现事务,但是操作很繁琐,接下来,我们可以使用较为简单的声明式事务.
声明式事务是利用注解自动开启和提交事务.
声明式事务的实现很简单,只需在方法上加入@Transactional
注解就可以实现,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果途中发生了没有处理的异常就会自动回滚事务.
package com.example.demo.controller;
import com.example.demo.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;
@RestController
@RequestMapping("/user2")
public class UserController2 {
@Autowired
private UserService userService;
@Transactional(timeout = 3) // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
@RequestMapping("/del")
public int del(Integer id) {
if (id == null || id <= 0) return 0;
return userService.del(id);
}
}
@Transactional(timeout = 3) // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
@RequestMapping("/del")
public int del(Integer id) {
if (id == null || id <= 0) return 0;
int num = 10/0;
return userService.del(id);
}
但是如果加上Try catch
来处理异常,此时事务就不会回滚了,数据也就正式被删除了.但是出现这种情况,它就是出现了异常,我们是想要它进行回滚操作的.我们有两种处理办法:
1. 手动抛出异常
@Transactional(timeout = 3) // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
@RequestMapping("/del")
public int del(Integer id) {
if (id == null || id <= 0) return 0;
int result = 0;
try{
result = userService.del(id);
System.out.println(result);
int num = 10/0;
}catch (Exception e){
throw e;//手动将异常抛出
}
return result;
}
2. 手动回滚事务
@Transactional(timeout = 3) // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
@RequestMapping("/del")
public int del(Integer id) {
if (id == null || id <= 0) return 0;
int result = 0;
try{
result = userService.del(id);
System.out.println(result);
int num = 10/0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
TransactionAspectSupport.currentTransactionStatus()
用于获取到当前事务,setRollbackOnly()
是用于回滚的方法.
@Transactional 可以用来修饰方法或类:
参数 | 作用 |
---|---|
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
propagation | 事务的传播行为,默认值为Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为lsolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly | 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
@Transactional
的工作原理如下:
@Transactional
注解的配置来创建一个事务上下文,并将其与当前线程关联起来。@Transactional
注解的配置,启动一个新的事务。@Transactional
注解的配置,如果异常属于回滚规则中指定的类型,则发起事务回滚操作,撤销已经执行的数据库操作。需要注意的是:@Transactional 注解只对公共方法生效,因此仅能应用于公共的非静态方法.
除了直接将 @Transactional
注解应用于方法上,还可以将其应用于类上。在这种情况下,所有在该类中声明的方法都将具有相同的事务属性。
@Transactional
注解通过简化事务管理过程,提供了一种声明式的事务管理方式,使开发者能够更轻松地实现对数据库操作的事务性控制。
总之,@Transactional
是基于AOP
实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现了接口,会使用CGLIB动态代理。@Transactional
在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
事务的四大特性(ACID)是指原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),它们是关系型数据库管理系统(RDBMS)中用于保证数据操作的可靠性和一致性的基本属性。
这四个特性确保了事务的安全性、可靠性和一致性。它们是关系型数据库管理系统保证数据操作的基本要求,同时也是在设计应用程序时需要考虑和遵循的事务规范。
这四种特性中,只有隔离性(隔离级别)是可以设置的。
之前在MySQL中我们学过的事务隔离级别有四种.忘记的小伙伴可以查看:MySQL:索引事务.
此处我们要讲解的是Spring中的事务隔离级别,在 Spring 中,可以通过 @Transactional
注解的isolation
属性来设置事务的隔离级别。isolation
属性接受一个枚举值,用于指定所需的隔离级别。
Spring中的事务隔离级别分为五种,分别是:
DEFAULT
:使用底层数据库的默认隔离级别。READ_UNCOMMITTED
:读取未提交的数据。最低的隔离级别,在此级别下,一个事务可以读取到其他事务尚未提交的数据。READ_COMMITTED
:读取已提交的数据。该级别下,一个事务只能读取到其他事务已经提交的数据,避免了脏读。REPEATABLE_READ
:可重复读。在此级别下,事务开始时读取的数据集合将被固定,即使其他事务修改了数据,当前事务仍然看到最初读取的数据集合。防止了脏读和不可重复读。SERIALIZABLE
:串行化。在此级别下,事务是按顺序一个接一个地执行,避免了脏读、不可重复读和幻读。具体实现格式如下:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void doSomething() {
// 事务操作
}
事务传播机制是指在多个事务操作嵌套执行时,各事务之间如何相互关联和影响的规则。
事务传播机制的存在是为了**处理多个事务操作之间的关系和影响,以确保数据的一致性和完整性。**以下是一些需要事务传播机制的情况:
总之,事务传播机制允许我们在多个事务操作中控制事务的行为方式,确保数据的正确处理和一致性。通过合理配置事务传播行为,可以满足不同的应用场景下对事务处理的需求。
Spring框架提供了七种事务传播行为,用于控制事务方法的执行方式:
(包含例子:假设你正在计划一次旅行。你需要预订机票、酒店和租车,并确保这些操作在旅行期间的可用性和连贯性。)
REQUIRED
(默认):如果当前已经存在事务,则加入该事务中执行;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为。
当你预订机票时,系统首先检查目标航班的余票情况,然后从你的账户中扣除相应金额作为机票费用。如果你的预订操作已经在一个事务中,那么检查余票和扣款将加入到该事务中。如果没有事务,则创建一个新的事务来执行这两个操作。
SUPPORTS
:如果当前已经存在事务,则加入该事务中执行;如果当前没有事务,则以非事务方式执行。适合于不需要强制事务的场景。
当你查询酒店房间的可用性时,系统根据你选择的日期和地点返回相关信息。如果你的 查询操作已经在一个事务中,那么查询房间可用性将加入到该事务中。如果没有事务,则以非事务方式执行查询。
MANDATORY
:如果当前已经存在事务,则加入该事务中执行;如果当前没有事务,则抛出异常。适合于必须依赖事务的场景。
当你要修改预订的租车时间时,系统需要验证该租车订单是否存在。在租车订单修改方法中,通过判断当前是否存在事务来决定是否允许修改操作。如果你已经在事务中,则允许修改;如果没有事务,则抛出异常。
REQUIRES_NEW
:无论当前是否存在事务,都会挂起当前事务,创建一个新的事务执行。适合于需要独立的事务执行的场景。(事务挂起是指在执行一个事务的过程中,暂时中断该事务的执行,并将其保存在一个临时状态下,以便执行其他事务。当挂起的事务恢复执行时,会从挂起的点继续执行。)
当你取消酒店预订时,需要先将预订状态设为取消,并返还预付款项。无论你当前是否处于一个事务中,这两个操作都将在一个新的事务中执行,并将原来的事务挂起。
NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,则将其挂起。适合于不需要事务支持的场景。
当你查询机票价格时,系统根据出发地和目的地返回相关信息。如果你当前处于一个事务中,该查询操作将以非事务方式执行,而不会干扰原有事务的状态。
NEVER
:以非事务方式执行操作,如果当前存在事务,则抛出异常。适合于不能在事务中执行的场景。
当你申请旅行保险时,需要确保该操作不会在任何事务中执行。如果你当前处于一个事务中,则抛出异常并禁止执行保险申请操作,以确保操作的独立性。
NESTED
:如果当前已经存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。嵌套事务有自己的保存点,并可以回滚到保存点。适合于需要嵌套事务支持的场景。
当你进行机票改签操作时,需要先将原机票设为无效,然后生成一张新的机票。如果你当前已经在一个事务中,那么这两个操作将在嵌套事务中执行,嵌套事务有自己的保存点,并可以回滚到保存点。如果没有事务,则创建一个新的事务来执行这两个操作。
例如:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 事务操作
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 事务操作
}
在上面的例子中,methodA()
使用了默认的传播行为REQUIRED
,如果当前存在事务,则methodB()
将加入该事务;而methodB()
使用了传播行为REQUIRES_NEW
,无论当前是否存在事务,都将创建一个新的事务来执行。
通过选择合适的事务传播行为并进行配置,可以确保在多个事务操作中正确地管理事务的行为和关系,从而维护数据的一致性和完整性。
嵌套事务和加入事务的区别:
嵌套事务和加入事务都是事务传播机制的不同实现方式,它们之间有以下区别: