Spring事务和事务传播机制

目录

一、事务

[1.1 什么是事务?](#1.1 什么是事务?)

[1.2 为什么需要事务?](#1.2 为什么需要事务?)

[1.3 事务的操作](#1.3 事务的操作)

二、Spring中事务的实现

[2.1 Spring编程式事务](#2.1 Spring编程式事务)

[2.2 Spring声明式事务@Transactional](#2.2 Spring声明式事务@Transactional)

​三、@Transactional详解

[3.1 rollbackFor](#3.1 rollbackFor)

[3.2 事务隔离级别](#3.2 事务隔离级别)

[3.2.1 MySQL事务隔离级别](#3.2.1 MySQL事务隔离级别)

[3.2.2 Spring事务隔离级别](#3.2.2 Spring事务隔离级别)

[3.3 Spring事务传播机制](#3.3 Spring事务传播机制)

[3.3.1 什么是事务传播机制](#3.3.1 什么是事务传播机制)

[3.3.2 事务的传播机制有哪些?](#3.3.2 事务的传播机制有哪些?)

[3.3.3 NESTED和REQUIRED区别](#3.3.3 NESTED和REQUIRED区别)

总结


一、事务

1.1 什么是事务?

事务是一组操作的集合 ,是一个不可分割的操作。

事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求,所有这组操作要么同时成功,要么同时失败。

1.2 为什么需要事务?

我们在进行程序开发时,也会有事务的需求。

比如转账操作:

第一步:A账户 -100元

第二部:B账户 +100元。

如果没有事务,第一步执行成功了,第二步执行失败了,那么A账户的100元就平白无故消失了,如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

1.3 事务的操作

事务的操作主要有三步:

  1. 开启事务start transaction(一组操作前开启事务)
  2. 提交事务:commit(这组操作全部成功,提交事务)
  3. 回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)

二、Spring中事务的实现

Spring中的事务操作分为两类:

  1. 编程式事务(手动写代码操作事务)
  2. 声明式事务(利用注解自动开启和提交事务)

2.1 Spring编程式事务

Spring手动操作事务有三个重要操作步骤:

  • 开启事务(获取事务)
  • 提交事务
  • 回滚事务

SpringBoot内置了两个对象:

  1. DataSourceTransactionManager事务管理器,用来获取事务(开启事务),提交或回滚事务
  2. TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition传递过去从而获得一个事务 TransactionStatus

我们还是给根据代码的实现来学习:

java 复制代码
package com.example.trans.controller;

import com.example.trans.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RestController
@RequestMapping("/user")
public class UserController {
    //JDBC 事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    //定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;

    @RequestMapping("/register")
    public boolean register(String userName, String password) {
        //开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Integer result = userService.insert(userName, password);
        System.out.println("插入用户表,result:" + result);
        //回滚事务
        //dataSourceTransactionManager.rollback(transaction);

        //提交事务
        dataSourceTransactionManager.commit(transaction);
        return true;
    }
}

观察事务提交

//提交事务

复制代码
dataSourceTransactionManager.commit(transaction);

运行程序:http://127.0.0.1:8080/user/register?userName=admin\&password=admin11

观察数据库的结果,数据插入成功

观察事务回滚

//回滚事务
dataSourceTransactionManager.rollback(transaction);

运行程序:http://127.0.0.1:8080/user/register?userName=admin\&password=admin11

观察数据库,虽然程序返回true,但是数据库并没有新增数据。

注意:这条记录是上个案例提交事务的结果,不是会回滚事务的结果。

2.2 Spring声明式事务@Transactional

声明式事务的实现很简单,只需要在需要事务的方法上添加@Transactional注解就可以实现了,无需手动开启事务和提交事务,进入方法时开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。

代码实现:

java 复制代码
package com.example.trans.controller;

import com.example.trans.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping("/trans")
public class TransController {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/register")
    public boolean register(String userName, String password) {
        Integer result = userService.insert(userName, password);
        System.out.println("插入用户表:" + result);
        return true;
    }
}

运行程序,观察数据库

修改程序,使之出现异常

运行程序,观察数据库

数据库中没有想要插入的数据

发现虽然日志显示数据插入成功,但数据库却没有新增数据,事务进行了回滚。

@Transaction作用

@Transactional可以用来修饰方法或类:

  • 修饰方法时:只有修饰public方法时才失效
  • 修饰类时:对@Transactional修饰的类中的所有public方法都生效

方法/类被@Transactional注解修饰时,在目标方法执行开始之前,会自动开启事务,方法执行结束后,自动提交事务。

如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。

如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。

修改上述代码,对异常进行捕获

运行程序,观察数据库

运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。

如果需要事务进行回滚,有以下两种方式:

1、重新抛出异常

2、手动回滚事务
使用TransactionAspectSupport.currentTransactionStatus()得到当前的事务,并使用setRollbackOnly设置setRollbackOnly

三、@Transactional详解

通过上面的代码,我们学习了@Tansactional的基本使用,接下来我们学习@Transactional注解的使用细节。

我们主要学习@Transactional注解当中的三个常见属性

  1. rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型
  2. isolation:事务的隔离级别,默认值Isolation.DEFAULT
  3. propagation:事务的传播机制,默认值Propagation.REQUIRED

3.1 rollbackFor

@Transational默认只有在遇到运行时异常和Error时才会回滚,非运行时异常不回滚,也就是Exception的子类中,除了RuntimeException及其子类会回滚。

接下来我们把异常改为如下代码

运行程序:

观察数据库表数据:

虽然程序抛出了异常,但是事务依然进行了提交。

如果我们需要所有异常都回滚,需要配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性指定出现何种异常类型时事务进行回滚。

结论:

  • 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException及其子类和Error时才会回滚。
  • 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定。

3.2 事务隔离级别

3.2.1 MySQL事务隔离级别

SQL标准定义了四种隔离级别,MySQL全都支持,这四种隔离级别分别是:

  1. 读未提交(READ UNCOMMITTED):读未提交,也叫未提交读,该隔离级别的事务可以看到其它事务中未提交的数据。(因为其它事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读)。
  2. 读提交(READ COMMITTED):读已提交,也叫提交读。该隔离级别的事务能读取到已经提交事务的数据(该隔离级别不会有脏读的问题,但由于在事务的执行中可以读取到其它事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果,这种现象叫做不可重复读)
  3. 可重复读(REPEATABLE READ):事务不会读到其它事务对已有数据的修改,即使其它事务已提交,也就可以确保同一事务多次查询的结果一致,但是其它事务新插入的数据是可以感知到的,这也就引发了幻读问题。可重复读,是MySQL的默认事务隔离级别。(例如在一个事务中,多次读取同一查询结果时,由于其它事务的插入或删除操作,导致两次读取的记录数不一致)。
  4. 串行化(SERIALIZABLE):序列化,事务最高的隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读的问题,但因为执行效率低,所以用到的场景不多。

3.2.2 Spring事务隔离级别

Spring中事务隔离级别有5中:

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
  2. Isolation.READ_UNCOMMITTED:读未提交,对应的SQL标准中READ_UNCOMMITTED
  3. Isolation.READ_COMMITTED:读已提交,对应SQL标准中READ_COMMITTED
  4. Isolation.REPEATABLE_READ:可重复读,对应SQL标准中REPEATABLE_READ
  5. Isolation.SERIALIZABLE:串行化,对应SQL标准中SERIALIZABLE

3.3 Spring事务传播机制

3.3.1 什么是事务传播机制

事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。

比如有两个方法A,B都被@Transactional修饰,A方法调用B方法

A方法运行时,会开启一个事务,当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,还是创建一个新的事务呢?

这就涉及到了事务的传播机制。

3.3.2 事务的传播机制有哪些?

@Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为。

Spring事务传播机制有以下7种:

  1. Propogation.REQUIRED:默认的事务传播级别,如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。
  2. Propogation.SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行。
  3. Propogation.MANDATORY:强制性,如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。
  4. Propogation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起,也就是说不管外部方法是否开启事务,Propogation.REQUIRES_NEW修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5. Propogation.NOT_SUPPORTS:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6. Propogation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7. Propogation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于Propogation.REQUIRED。

比如一对新人要结婚了,关于是否需要房子

  1. Propogation.REQUIRED:需要有房子,如果你有房,我们就一起住,如果你没房,我们就一起买房
  2. Propogation.SUPPORTS:可以有房子,如果你有房,那就一起住。如果没房,那就租房。
  3. Propogation.MANDATORY:必须有房子,要求必须有房
  4. Propogation.REQUIRES_NEW:必须要买房,不管你有没有房子,必须两个人一起买房
  5. Propogation.NOT_SUPPORTS:不需要房子,不管有没有房,都选择租房方式
  6. Propogation.NEVER:不能有房子
  7. Propogation.NESTED:如果你没房,就一起买房。如果你有房,我们就以房子为根据地,做点其它生意。

3.3.3 NESTED和REQUIRED区别

  • 整个事务如果全部执行成功,二者的结果是一样的
  • 如果事务一部分执行成功,REQUIRED加入事务会导致整个事务全部回滚。NESTED嵌套事务可以实现局部回滚,不会影响上一个方法中执行的结果。

总结

  1. Spring中使用事务,有两种方式:编程事务和声明式事务,其中声明式事务时使用较多,在方法手上添加@Transactional就可以实现了。
  2. 通过@Transactional(isolation = Isolation.***)设置事务的隔离级别。Spring中的事务隔离级别有5种。
  3. 通过@Transactional(propogation = Propagation.**)设置事务的传播机制,Spring种的事务传播机制有7种。
相关推荐
三个蔡几秒前
Java求职者面试:从Spring Boot到微服务的技术深度探索
java·大数据·spring boot·微服务·kubernetes
sniper_fandc12 分钟前
JVM(Java虚拟机)详解
java·开发语言·jvm
小鸡脚来咯12 分钟前
SpringBoot 常用注解通俗解释
java·spring boot·后端
AI的魔盒17 分钟前
基于Java与MAVLink协议的多无人机(Cube飞控)集群控制与调度方案问题
java·开发语言·无人机
北执南念1 小时前
项目代码生成工具
java
中国lanwp1 小时前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
苹果酱05672 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
Java致死2 小时前
单例设计模式
java·单例模式·设计模式
胡子发芽2 小时前
请详细解释Java中的线程池(ThreadPoolExecutor)的工作原理,并说明如何自定义线程池的拒绝策略
java
沫夕残雪2 小时前
Tomcat的安装与配置
java·tomcat