Spring事务与事务传播

在这里插入图片描述

文章目录

  • 一、什么是事务?
  • 二、Spring事务实现
    • 编程式事务
    • 声明式事务
  • 三、@Transactional的使用
    • 参数作用
    • Spring事务的隔离级别
    • 事务失效的场景
    • @Transactional工作原理
  • 四、Spring事务传播机制
    • Spring有哪些事务传播机制?

一、什么是事务?

事务:事务是一组操作的集合,是不可分割的基本单位,要么全部成功,要么全部失败
我们在MySQL中使用事务有三步操作:开启事务,提交事务,回滚事务

-- 开启事务
start transaction;

-- 执行具体业务

-- 提交事务
commit;

-- 回滚事务
rollback

事务的四大特性(ACID): 原子性、一致性、持久性、隔离性
原子性(Atomicity):一个事务的操作,要么全部完成,要么全部不完成
一致性(Consistency):事务开始之前和事务结束之后,数据库的完整性没有被破坏
持久性(Isolation):事务提交后,对数据的修改就是永久的
隔离性(Durability):数据库允许多个并发事务同时对其数据读写和修改的能力,隔离性可以防止多个事务并发执行时导致的数据不一致
隔离级别是可以设置的

事务的隔离级别: 在数据库中,多个事务并发执行时,各个事务之间是相互隔离的,每个事务之间都不能相互干扰。为了保证并发事务的正确性和一致性,数据库管理系统(DBMS)提供了四种事务隔离级别,分别为读未提交、读已提交、可重复读和串行化。

读未提交(Read Uncommitted)
在该隔离级别下,一个事务可以读取另一个事务尚未提交的数据,不仅如此,它可能还会读到一些脏数据(Dirty Read)。在该级别下,数据的一致性和完整性得不到保障,不推荐使用。

读已提交(Read Committed)
在该隔离级别下,一个事务只能读取另一个事务已经提交的数据,避免了脏读的问题,但是可能会出现不可重复读(Non-Repeatable Read)的问题。即同一事务中,多次读取同一数据可能会得到不同的结果,因为其他事务可能在两次读取之间提交了修改。

可重复读(Repeatable Read)
在该隔离级别下,一个事务在执行期间多次读取同一数据时,能够保证所读取的数据一定是事务开始时的状态,避免了不可重复读的问题,但是可能会出现幻读(Phantom Read)问题,即两次查询间新增数据的影响。

串行化(Serializable)
在该隔离级别下,所有事务串行执行,避免了脏读、不可重复读和幻读的问题,但是实际应用中可能会导致性能严重下降,因此该级别一般只在特殊情况下使用。

默认隔离级别为REPEATABLE READ
Spring事务与事务传播_第1张图片

我们可以使用以下SQL查询全局事务隔离级别和当前连接的事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;

Spring事务与事务传播_第2张图片

二、Spring事务实现

Spring中的事务分为两类:
1.编程式事务(手动操作)
2.声明式事务(自动提交事务)

编程式事务

SpringBoot内置了两个对象,DataSourceTransactionManager用来获取事务(开启事务、提交事务、回滚事务)。TransactionDefinition是事务的属性,在获取事务时,需要将TransactionDefinition传递进去获取一个TransactionStatus

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

    @Autowired
    private UserService userService;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public int del(Integer id) {
        if(id != null && id > 0) {
            //开启事务
            TransactionStatus transactionStatus =
                    transactionManager.getTransaction(transactionDefinition);
            //删除用户业务操作
            int result = userService.del(id);
            System.out.println("删除了: " + result);
            // 提交事务/回滚事务
//            transactionManager.commit(transactionStatus); //提交事务
            transactionManager.rollback(transactionStatus); //回滚事务
        }
        return 0;
    }
}

Spring事务与事务传播_第3张图片
Spring事务与事务传播_第4张图片
Spring事务与事务传播_第5张图片
数据库原始数据:
Spring事务与事务传播_第6张图片
Spring事务与事务传播_第7张图片
Spring事务与事务传播_第8张图片
这里显示操作成功,我们来看看我们数据库数据发生变化了没
Spring事务与事务传播_第9张图片
我们可以发现,事务成功的进行了回滚,但是这样的方式太繁琐了,我们来学习更简单的声明式事务

声明式事务

声明式事务我们只需要在方法上@Transactional注解就可以实现,无需手动进行开启事务和提交事务,进入方法自动开启,执行完毕自动提交,发生异常后会自动回滚事务

@RestController
@RequestMapping("/user2")
public class UserController2 {

    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/del")
    public int del(Integer id) {
        if(id == null || id <= 0) {
            return 0;
        }
        return userService.del(id);
    }
}

这种是没有异常的异常,我们会进行commit,我们目前数据库数据:
Spring事务与事务传播_第10张图片
Spring事务与事务传播_第11张图片

Spring事务与事务传播_第12张图片

我们可以发现成功的删除了数据库的一条数据:
Spring事务与事务传播_第13张图片
我们来在业务中加上一段异常代码:

@RestController
@RequestMapping("/user2")
public class UserController2 {

    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/del")
    public int del(Integer id) {
        if(id == null || id <= 0) {
            return 0;
        }
        int result = userService.del(id);
        int n = 1 / 0; //异常业务
        return result;
    }
}

我们再来进行删除操作:
Spring事务与事务传播_第14张图片

Spring事务与事务传播_第15张图片
我们可以看到我们的控制台信息,成功的进行了删除操作
Spring事务与事务传播_第16张图片
同时报了一个算术异常
Spring事务与事务传播_第17张图片
我们查看数据库信息,发现当发生异常时会自动进行回滚操作。

三、@Transactional的使用

@Transactional的作用范围:@Transactional既可以用来修饰方法也可以用来修饰类
1.修饰方法:只能应用到public方法上,否则不生效,推荐用法
2.修饰类:表明该注解对所有的public方法都生效

参数作用

我们可以通过设置@Transactional的一些参数来决定事务的一些具体的功能
Spring事务与事务传播_第18张图片

Spring事务的隔离级别

Spring的事务隔离级别是有以下5种的
Spring事务与事务传播_第19张图片
1.DEFAULT:使用底层数据库默认的隔离级别。
2.READ_UNCOMMITTED:允许读取还未提交的数据,会出现脏读、不可重复读和幻读等问题。
3.READ_COMMITTED:只能读取已经提交的数据,避免了脏读的问题,但是可能出现不可重复读和幻读的问题。
4.REPEATABLE_READ:保证同一事务中多次读取同一记录结果是一致的,避免了脏读和不可重复读的问题,但是仍然可能出现幻读的问题。
5.SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读等问题,但是影响系统性能。

默认情况下,Spring 会使用底层数据库的默认隔离级别,通常是READ_COMMITTED 级别。可以通过事务管理器的 setDefaultTransactionIsolation() 方法或在事务注解中使用 isolation 属性来设置隔离级别,例如:

@Transactional(isolation = Isolation.DEFAULT)
    @RequestMapping("/del")
    public int del(Integer id) {
    }

Spring事务与事务传播_第20张图片

Spring事务与事务传播_第21张图片

事务失效的场景

常见的事务失效场景有以下三种:
1.异常捕获处理: @Transactional 在异常被捕获的情况下,不会进行事务自动回滚
Spring事务与事务传播_第22张图片
我们进行操作之前的数据库数据,我们进行try,catch处理
Spring事务与事务传播_第23张图片
Spring事务与事务传播_第24张图片
Spring事务与事务传播_第25张图片
我们发现程序抛出了异常,但并没有进行回滚操作
Spring事务与事务传播_第26张图片
原因:事务通知只有自己捕捉到了目标抛出的异常,才能进行后续的回滚操作,如果目标自己处理掉了异常,事务无法知悉

解决方案: 在catch块中添加throw new RuntimeException(e)抛出
Spring事务与事务传播_第27张图片
我们将数据恢复
Spring事务与事务传播_第28张图片
Spring事务与事务传播_第29张图片
Spring事务与事务传播_第30张图片
我们来进行操作时,进行了回滚操作

2.抛出检查异常:
Spring事务与事务传播_第31张图片
Spring事务与事务传播_第32张图片
Spring事务与事务传播_第33张图片
我们的事务未进行回滚操作
原因:Spring默认值只会回滚非检查异常

解决方案: 配置rollbackFor属性
Spring事务与事务传播_第34张图片
Spring事务与事务传播_第35张图片
在进行操作时,事务成功的进行了回滚操作

3.非public方法导致的事务失效:
Spring事务与事务传播_第36张图片
Spring事务与事务传播_第37张图片
Spring事务与事务传播_第38张图片
事务没有进行回滚操作
原因:Spring为方法创建代理,添加事务通知、前提条件都是该方法为public

解决方案: 改为Public

@Transactional工作原理

@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到异常,会进行回滚业务
Spring事务与事务传播_第39张图片
@Transactional 具体执行细节如下图所示:
Spring事务与事务传播_第40张图片

四、Spring事务传播机制

Spring事务传播机制定义了多个包含事务的方法,相互调用时
为什么需要事务传播机制?事务隔离级别不够用吗?
事务隔离级别是保证多个并发事务执行是可控的,而事务传播机制是保证一个事务在多个调用方法间是可控的。
事务隔离级别是为了解决多个事务同时调用一个数据库的问题:

Spring事务与事务传播_第41张图片
事务传播机制是解决一个事务在多个方法中传递的问题:

Spring事务与事务传播_第42张图片

Spring有哪些事务传播机制?

Spring 支持以下七种事务传播机制:

1.PROPAGATION_REQUIRED:默认值,如果当前存在一个事务,则加入该事务;否则新建一个事务。这是最常见的传播机制,也是大多数情况下使用的传播机制。如果外围方法已经启动了事务,那么内部方法就会在该事务中运行;如果外围方法尚未启动事务,则内部方法会启动一个新的事务。

2.PROPAGATION_REQUIRES_NEW:将当前事务挂起,开启一个新的事务进行执行。如果当前存在事务,则将当前事务挂起并启动一个新事务;如果当前没有事务,则开启一个新的独立事务。
3.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式继续运行。若外围方法有事务,则使用该事务;若外围方法没有事务,则直接执行。
4.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将该事务挂起。若外围有事务,则将当前事务挂起,然后以非事务方式执行;若外围没有事务,则直接以非事务方式执行。
5.PROPAGATION_NEVER:表示当前方法不应该执行在事务上下文中。如果当前事务存在,则抛出异常。即表示当前方法不能够在事务环境中运行。
6.PROPAGATION_MANDATORY:当前方法必须在事务上下文中运行,否则抛出异常。要求当前环境中必须存在事务,否则会抛出异常。
7.PROPAGATION_NESTED:如果当前存在事务,则开启一个子事务进行执行;如果当前不存在事务,则新建一个事务进行执行。嵌套事务和普通事务的区别是,嵌套事务可以独立于外围事务进行提交或回滚,而不影响外围事务的状态。如果外围事务已经提交或回滚,则子事务也会被提交或回滚,但如果子事务发生异常而回滚,则外围事务可以选择回滚或不回滚。

事务传播机制可以分为以下三类:
Spring事务与事务传播_第43张图片
我们可以更形象的看一下下述图来理解(别的大佬的图):
Spring事务与事务传播_第44张图片

你可能感兴趣的:(Spring框架,spring,数据库,java)