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种。
相关推荐
RealmElysia8 分钟前
谷粒商城基础篇完结
java·微服务
lxl_h20 分钟前
IDEA 打包普通JAVA项目为jar包
java·intellij-idea·jar
好奇的菜鸟21 分钟前
解决 IntelliJ IDEA 启动错误:插件冲突处理
java·ide·intellij-idea
what_201822 分钟前
idea添加作者注释和方法注释、属性注释
java·ide·intellij-idea
m0_7482540923 分钟前
2024.1.4版本的IntelliJ IDEA创建Spring Boot项目的详细步骤
java·spring boot·intellij-idea
m0_7482394726 分钟前
Spring IDEA 2024 安装Lombok插件
java·spring·intellij-idea
dr李四维34 分钟前
PO、VO、DAO、BO、DTO、POJO 你能分清吗?
java·po·dao·dto·vo·pojo·bo
程序员大金38 分钟前
基于SpringBoot+Vue的驾校管理系统
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
m0_7482396338 分钟前
Java实战:Spring Boot application.yml配置文件详解
java·网络·spring boot
小殷要努力刷题!1 小时前
每日一刷——12.10——学习二叉树解题模式(1)
java·学习·算法·leetcode·二叉树·二叉树的建立