Spring中的事务传播机制

目录

前言

1、Spring(Spring Boot)实现事务

1.1、通过代码的方式手动实现事务

 1.2、通过注解@Transactional的方式实现声明式事务

 1.2.1、实现:

 1.2.2、程序中有try-catch时,程序发生异常事务不会回滚

 解决方案一:将异常抛出去

 解决方案二:使用代码手动回滚事务

1.2.3、@Transactional参数说明

1.2.4、@Transactional工作原理

2、事务隔离级别

2.1、事务特性

2.2、Spring中设置事务隔离级别

2.2.1、MySQL中事务隔离级别(4种)

2.2.2、Spring事务隔离级别(5种)

3、Spring事务传播机制

3.1、事务传播机制是什么

3.2、为什么需要事务传播机制

 3.3、事务传播机制有哪些

3.4、事务传播的分类 

3.5、Spring事务传播机制使用场景演示

3.5.1、支持当前事务(REQUIRED)

3.5.2、不支持当前事务(REQUIRES_NEW)

3.5.3、嵌套事务(NESTED)

3.6、嵌套事务和加入事务有什么区别?


前言

        为什么需要事务呢?首先需要了解什么是事务,事务就是一组操作封装成一个单元,执行时要么全部成功,要么全部失败~

        就好比,你给我转账,你那边的金额减少了,我这边突然出故障了,钱就凭空没了,你当然是第一个不乐意呀~  所以需要事务,当我这边失败时,事务回滚,你那边的金额就会回到转账前的金额~


1、Spring(Spring Boot)实现事务

1.1、通过代码的方式手动实现事务

Spring手动操作事务就和MySQL操作事务类似,包括三个步骤:

  • 开启事务
  • 提交事务
  • 回滚事务

SpringBoot内置了两个对象:

  • DataSourceTransactionManager用来获取事务(开启事务)、提交事务或回滚事务
  • TransactionDefinition是事务的特性,获取事务时,需要将TransactionDefinition传递进去从而获取一个事务TransactionStatus

代码:

package com.example.demo.controller;

import com.example.demo.entity.Userinfo;
import com.example.demo.service.UserService;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-03-30
 * Time:17:07
 */
@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.2、通过注解@Transactional的方式实现声明式事务

@Transactional的特点:

  • 可以添加在类上或方法上。修饰方法时,只能应用到public方法上,否则不生效;修饰类时,表明注解对该类中的所有的public方法都生效
  • 在方法执行前自动开启事务,在方法执行完(无任何异常)自动提交事务,当方法在执行期间出现异常,将自动回滚事务

 1.2.1、实现:

代码;

    @RequestMapping("/add")
    @Transactional   //声明式事务(自动提交)
    public Integer add(Userinfo userinfo) {
        //非空校验
        if(userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) || !StringUtils.hasLength(userinfo.getPassword())) {
            return 0;
        }
        int result = userService.add(userinfo);
        System.out.println("添加成功!" + result);
        return  result;
    }

 1.2.2、程序中有try-catch时,程序发生异常事务不会回滚

添加一个异常; 

程序启动,前端:

 数据库:

 自动回滚

 但此时,我们如果将这个异常捕捉掉,事务就不会回滚了~

程序启动,前端:

 

数据库:

不会回滚~

 解决方案一:将异常抛出去

 解决方案二:使用代码手动回滚事务

1.2.3、@Transactional参数说明

参数作用
value当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
transactionManager  当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
propagation事务传播行为【事务传播机制是,下文中有详解】
isolation事务的隔离级别,默认值为Isolation.DEFAULT
timeout事务的超时时间,默认值为-1【无时间限制】。如果超过该时长但事务还没有完成,则自动进行回滚事务
readOnly指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据可以设置read-only为true
rollbackFor用户指定能够触发事务回滚的异常类型,可以指定多个异常类型
rollbackForClassName用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
noRollbackFor抛出指定的异常类型,不回滚事务,也可以指定多个异常类型
noRollbackForClassName抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

1.2.4、@Transactional工作原理

        @Transactional是基于AOP实现的,AOP是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现了接口,会使用CGLIB动态代理

        @Transactional在开始执行业务之前,通过代理先开启事务,在执行之后再提交事务。如果中途遇到异常,则回滚事务

@Transactional实现思路:


2、事务隔离级别

2.1、事务特性

4大特性:原子性、持久性、一致性、隔离性~

具体这篇文章下有详细说明:http://t.csdn.cn/fIugI

2.2、Spring中设置事务隔离级别

2.2.1、MySQL中事务隔离级别(4种)

这篇文章中有详细说明:http://t.csdn.cn/fIugI

2.2.2、Spring事务隔离级别(5种)

Spring事务隔离级别,4种和MySQL一致,另外多了一种:

Isolation.DEFAULT:以连接的数据库的隔离级别为主

Spring中如何设置事务隔离级别:


3、Spring事务传播机制

3.1、事务传播机制是什么

        Spring事务中传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的

3.2、为什么需要事务传播机制

        事务隔离级别是保证多个并发事务执行的可控性的(稳定性),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性)

        事务隔离解决的是多个事务同时调用一个数据库的问题,如图:

事务传播机制解决的是一个事务在多个节点(方法)中的传递问题,如下:

 3.3、事务传播机制有哪些

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

  • Propagation.REQUIRED:默认的事务传播机制,他表示如果当前存在事务,则加入事务;如果当前没有事务,则创建一个新的事务
  • Propagation.SUPPORTS:如果当前存在事务,则加入事务;如果当前没有事务,则以非事务的方式继续运行
  • Propagation.MANDATORY:(mandatory强制性)如果单前存在事务,则加入事务;如果当前没有事务,则抛出异常
  • Propagation.REQUIRES_NEW:表示创建一个新的事务,如果对当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且启动的事务互相独立,互不干扰
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED

3.4、事务传播的分类 

事务传播分为3类:

  • 支持当前事务:REQUIRED + SUPPORTS + MANDATORY
  • 不支持当前事务:REQUIRES_NEW  +  NOT_SUPPORTED  +  NEVER
  • 嵌套事务 : NESTED

3.5、Spring事务传播机制使用场景演示

3.5.1、支持当前事务(REQUIRED)

我们先开启事务,然后成功插入一条数据,然后执行日志时,报错,观察结果:

UserController:

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Userinfo userinfo) {
        //非空校验
        if(userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) || !StringUtils.hasLength(userinfo.getPassword())) {
            return 0;
        }
        //插入数据
        int result = userService.add(userinfo);
        //插入日志
        logService.saveLog("日志内容~");
        return result;
    }
}

UserService:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public int add(Userinfo userinfo) {
        return userMapper.add(userinfo);
    }
}

LogService:

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveLog(String content) {
       int i= 1/0;
        System.out.println(content);
    }
}

 程序运行:

报异常 ,查看数据库,看事务是否回滚:

回滚了~ 

  • UserService中的保存方法正常执行完成
  • LogService保存日志程序报错,应为使用的是Controller中的事务,所以整个事务也会回滚
  • 数据库中没有插入数据,也就是步骤1中的用户插入方法也回滚了

3.5.2、不支持当前事务(REQUIRES_NEW)

        上述的UserController代码中将事务的传播机制更改为REQUIRES_NEW,将LogService中添加日志的方法前的事务传播机制更改为REQUIRES_NEW,代码更改:

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String content) {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            //手动回滚当前事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        System.out.println(content);
    }
}

程序执行:

前端:

 

查看数据库:

 Userinfo表中成功插入数据,Log执行失败,但不影响UserController中的事务

3.5.3、嵌套事务(NESTED)

UserController:

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.NESTED)
    public int add(Userinfo userinfo) {
        //非空校验
        if(userinfo == null || !StringUtils.hasLength(userinfo.getUsername()) || !StringUtils.hasLength(userinfo.getPassword())) {
            return 0;
        }
        //插入数据
        int result1 = userService.add(userinfo);
        int result2 = userService.insert();
        return result1 + result2;
    }
}

UserSevice:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public int add(Userinfo userinfo) {
        return userMapper.add(userinfo);
    }

    @Transactional(propagation = Propagation.NESTED)
    public int  insert() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("李四");
        userinfo.setPassword("789");
        int ret = userMapper.add(userinfo);
        int i = 1/0;
        return ret;
    }
}

程序启动:

 数据库:

 结果:无数据添加

        因为是嵌套事务,所以日志添加回滚之后,往上找调用它的方法和事务回滚了用户添加,所以用户数据添加都失败了

3.6、嵌套事务和加入事务有什么区别?

  • 嵌套事务,可以实现部分事务回滚,也就是说回滚时,一直往上找调用它的方法和事务回滚,而不会回滚嵌套之前的事务
  • 加入事务,相当我已经成了他的一部分,回滚时,整个一起回滚
  • 整个事务如果全部执行成功,二者的结果是一样的

好啦,本期到这里就结束咯,我们下期见~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙洋静

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值