欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Spring Boot] 事务隔离级别和事务传播特性详解:如何在 Spring 中使用事务?事务隔离级别和事务传播特性详解:如何在 Spring 中使用事务?不同隔离级别之间的区别?-1事务

最编程 2024-06-21 18:21:23
...

1.1 事务简介与 mysql 中的事务使用

事务这个词在学习 MySQL 和多线程并发编程的时候,想必大家或多或少接触过。那么什么是事务呢?

事务是指一组操作作为一个不可分割的执行单元,要么全部成功执行,要么全部失败回滚。在数据库中,事务可以保证数据的一致性、完整性和稳定性,同时避免了数据的异常和不一致情况。常见的事务包括插入、更新、删除等数据库操作。事务的核心要素是ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

比如,常见的转账操作,以小明给小红转账100元为例,分为如下两个操作:

  1. 小明的账户 -100元;
  2. 小红的账户 +100元。

如果没有事务,第一步操作执行成功,而第二步执行失败,就会导致小明账户平白无故的扣款而小红账户没有收到款项的问题。因此,事务的存在是必要的,这一组操作要么全部执行成功,要么一起失败~
转账示意图

在 MySQL 中,事务有三个重要的操作,分别为:开启事务、提交事务、回滚事务,对应的操作命令如下:

-- 开启事务
start transaction;
-- 业务执行
...
-- 提交事务
commit;
-- 回滚事务
rollback;

1.2 Spring 编程式事务(手动操作)

与 MySQL 操作事务类似,Spring 手动操作事务也需要三个重要的操作:开启事务(获取事务)、提交事务、回滚事务。

SpringBoot 内置了两个对象:

  • DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的;
  • TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus

实现代码如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    // 事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        // 非空校验
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 1. 开始事务
        TransactionStatus transactionStatus =
                transactionManager.getTransaction(transactionDefinition);
        int result = userService.add(userInfo);
        System.out.println("添加: " + result);
//        // 2. 回滚事务
//        transactionManager.rollback(transactionStatus);
        // 3. 提交事务
        transactionManager.commit(transactionStatus);
        return result;
    }
}

从上述代码可以看出,虽然可以实现事务,但是操作很繁琐。因此,我们 常常使用另一种更简单的方式:基于注解的声明式事务。

1.3 Spring 声明式事务(自动操作)

相比手动操作事务来说,声明式事务非常简单,只需要在需要的方法上添加 @Transactional 注解,无需手动开启事务和提交事务。

示例代码如下:

@Transactional // 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
    // 非空校验
    if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
        return 0;
    }
    int result = userService.add(userInfo);
    return result;
}

对于 @Transactional 的几点说明:

  1. 该注解可以加在方法或者类上,若加在类上,则说明该类的所有公共方法可以自动的开启和提交事务 ,无论修饰方法还是类,都只对 public 方法有效;
  2. 在方法执行前自动开启事务,在方法执行完毕(没有发生任何异常)自动提交事务。如果 在方法执行期间出现异常,会自动回滚事务。

附:@Transactional 的常见参数:

参数 说明
propagation 定义了事务方法被嵌套调用时,事务如何传播到被调用的方法。常见取值包括:
- REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- REQUIRES_NEW:每次调用方法时都会创建一个新的事务,如果存在当前事务,则将其挂起。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
isolation 定义了事务并发执行时,事务之间的隔离程度。常见取值包括:
- DEFAULT(默认):使用数据库默认的隔离级别。
- READ_UNCOMMITTED:最低的隔离级别,事务可以读取未提交的数据。
- READ_COMMITTED:事务只能读取已提交的数据。
- REPEATABLE_READ:事务在整个过程中保持一致的读取视图,防止脏读和不可重复读。
- SERIALIZABLE:最高的隔离级别,事务串行执行,避免脏读、不可重复读和幻读。
timeout 定义了事务执行的最长时间,单位为秒。默认值为-1,表示没有超时限制。
readOnly 如果设置为true,表示事务只读,不会修改数据库的数据。默认值为false
rollbackFor 触发事务回滚的异常类数组。当方法抛出指定的异常时,事务将回滚。
noRollbackFor 不触发事务回滚的异常类数组。当方法抛出指定的异常时,事务将不会回滚。
rollbackForClassName 触发事务回滚的异常类名数组。当方法抛出指定的异常时,事务将回滚。
noRollbackForClassName 不触发事务回滚的异常类名数组。当方法抛出指定的异常时,事务将不会回滚。
value 用于指定事务管理器的名称。如果应用程序中存在多个事务管理器,可以使用该参数指定要使用的事务管理器的名称。默认情况下,事务将使用默认的事务管理器。
transactionManager 用于指定事务管理器的引用。可以直接将一个事务管理器对象传递给该参数,以指定要使用的事务管理器。默认情况下,事务将使用默认的事务管理器。

对于上述表格中的事务隔离级别需要重点掌握,具体后面详细说。

需要特别注意的是,如果方法中的异常被 try-catch 异常捕获处理后,则不会再进行事务的回滚。

当然,我们可以通过 throw 将异常抛出,使得事务能够正常自动回滚。但是这样子做,try-catch 还有意义吗?表情包

因此,对于这种情况,更偏向于使用另一种优雅的方式,进行手动回滚事务来解决~

如何在声明式事务中进行手动回滚事务?
使用代码进行手动回滚事务:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

示例代码如下:
代码示例

1.4 @Transactional 的工作原理

  1. 当调用被@Transactional注解标记的方法时,事务管理器会检查当前是否存在一个事务。如果存在事务,则该方法将在该事务的上下文中执行;如果不存在事务,则会创建一个新的事务。
  2. 在方法执行期间,如果发生了受检查异常(checked exception),事务管理器会捕获该异常,并根据配置的回滚规则决定是否回滚事务。如果异常被捕获并且需要回滚事务,则事务将被回滚,方法的执行将终止,并将异常传播给调用方。
  3. 如果方法成功执行并且没有抛出受检查异常,事务管理器将提交事务,将数据库中的更改持久化。
  4. 如果方法执行期间抛出了未受检查异常(unchecked exception)或错误(Error),事务管理器会将事务标记为回滚,并将异常传播给调用方。
  5. 如果方法执行期间没有抛出异常,但在方法内部调用了其他被@Transactional注解标记的方法,事务管理器将根据事务的传播行为决定如何处理这些方法。例如,如果传播行为设置为REQUIRED,则内部方法将加入当前事务;如果传播行为设置为REQUIRES_NEW,则内部方法将创建一个新的事务。

具体来看,@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。实现细节的执行流程如图所示:
实现细节


推荐阅读