【Spring】Spring事务和事务传播机制

🔥个人主页: 中草药

🔥专栏:【Java】登神长阶 史诗般的Java成神之路


一、Spring事务

我们在MySQL阶段已经学习了MySQL的事务相关知识,详情可见

【MySQL数据库】索引与事务-CSDN博客

1、概念

我们在此做一个简单回顾

事务(Transaction)是指一组操作的集合,这些操作作为一个整体,要么全部成功,要么全部失败,具有以下四大特性(ACID):

  1. 原子性(Atomicity):事务中的操作要么全部成功,要么全部失败,不可分割。
  2. 一致性(Consistency):事务执行前后,数据处于一致状态。
  3. 隔离性(Isolation):事务相互独立执行,不受其他事务的干扰。
  4. 持久性(Durability):事务完成后,数据的更改会被永久保存。

在分布式环境或复杂业务逻辑中,事务可以有效防止数据异常,提高系统的可靠性。

2、事务的实现

1、编程式事务

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

java 复制代码
package org.example.controller;

import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //⽤⼾注册
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
        userService.registryUser(name, password);
        //提交事务
        //transactionManager.commit(status);
        //回滚事务
        transactionManager.rollback(status);
        return "注册成功";
    }
}

2、声明式事务(推荐):

需要先在Maven中添加依赖

XML 复制代码
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
</dependency>
  • 通过 @Transactional 注解实现事务管理,配置简单,非侵入式。
  • 配合 AOP 切面拦截方法调用,统一控制事务逻辑。
java 复制代码
@Transactional
public void someTransactionalMethod() {
    // 业务逻辑
}

在需要事务的⽅法上添加 @Transactional 注解就可以实现了. ⽆需⼿动开启事务和提交事
务, 进⼊⽅法时⾃动开启事务, ⽅法执⾏完会⾃动提交事务, 如果中途发⽣了没有处理的异常会⾃动
回滚事务.

测试代码

java 复制代码
package org.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;

    /**
     * 不加事务的情况下, 数据插入成功
     * @param name
     * @param password
     * @return
     */
//    @Transactional
    @RequestMapping("/r1")
    public String registry(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        int a = 10/0;
        return "注册成功";
    }

    /**
     * 添加事务的情况, 事务回滚
     */
    @Transactional
    @RequestMapping("/r2")
    public String r2(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        int a = 10/0;
        return "注册成功";
    }

    /**
     * 异常被捕获: 事务提交
     */
    @Transactional
    @RequestMapping("/r3")
    public String r3(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        try {
            int a = 10/0;
        }catch (Exception e){
            log.error("发生异常");
        }

        return "注册成功";
    }

    /**
     * 异常被捕获并且throw: 事务回滚
     */
    @Transactional
    @RequestMapping("/r4")
    public String r4(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        try {
            int a = 10/0;
        }catch (Exception e){
            log.error("发生异常");
            throw e;
        }
        return "注册成功";
    }

    /**
     * 手动回滚事务
     */
    @Transactional
    @RequestMapping("/r5")
    public String r5(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        try {
            int a = 10/0;
        }catch (Exception e){
            log.error("发生异常");
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return "注册成功";
    }


    /**
     * 发生非运行时异常, 事务提交
     */
    @Transactional
    @RequestMapping("/r6")
    public String r6(String name,String password) throws IOException {
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        if (true){
            throw new IOException();
        }

        return "注册成功";
    }
    /**
     * 设置rollbackFor, 发生非运行时异常, 事务回滚
     */
    @Transactional(rollbackFor = Exception.class)
    @RequestMapping("/r7")
    public String r7(String name,String password) throws IOException {
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        if (true){
            throw new IOException();
        }

        return "注册成功";
    }


    /**
     * 设置事务的隔离级别
     *
     */
    @Transactional(isolation = Isolation.DEFAULT)
    @RequestMapping("/r8")
    public String r8(String name,String password) throws IOException {
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        if (true){
            throw new IOException();
        }
        return "注册成功";
    }
}

@Transactional 默认只在遇到运行时异常和Error时才会回滚, ⾮运行时异常不回滚. 即
Exception的⼦类中, 除了RuntimeException及其⼦类

3、Spring事务的隔离级别

事务隔离级别描述了并发事务之间数据可见性的限制程度,直接影响事务操作的正确性和系统性能。隔离级别越高,数据一致性越强,但性能越低。SQL 标准定义了以下四种隔离级别:

隔离级别 问题 性能 说明
读未提交(Read Uncommitted) 脏读、不可重复读、幻读 事务可以读取其他事务未提交的数据,数据一致性最低。
读已提交(Read Committed) 不可重复读、幻读 较高 事务只能读取其他事务已经提交的数据,避免了脏读,但仍存在不可重复读和幻读问题。
可重复读(Repeatable Read) 幻读 中等 事务在读取数据时,保证同一事务内多次读取结果一致,避免了脏读和不可重复读,但可能出现幻读。
串行化(Serializable) 无问题 最高隔离级别,事务顺序执行,完全避免脏读、不可重复读和幻读,但性能较差,可能导致大量锁冲突。

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
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置

java 复制代码
    @Transactional(isolation = Isolation.READ_COMMITTED)
    @RequestMapping("/r3")
    public String r3(String name,String password){
        //用户注册
        userService.registryUser(name,password);
        log.info("数据插入成功");
        try {
            int a = 10/0;
        }catch (Exception e){
            log.error("发生异常");
        }

        return "注册成功";
    }

二、事务传播机制

1、概念

事务传播机制定义了在一个事务方法被另一个事务方法调用时,事务如何在这些方法之间传播。例如,当一个事务方法A调用另一个事务方法B时,B方法是加入A方法的现有事务,还是开启一个新的事务,或者以其他方式处理事务,这就是由事务传播机制决定的。

Spring 定义了七种事务传播行为,通过@Transactional注解的propagation属性来指定。

事务隔离级别解决的是多个事务同时调用同一个数据库的问题

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

2、传播行为类型

@Transaction 注解支持事务传播机制的设置,通过propagation 属性来制定传播机制的设置,事务的传播机制有以下7种:

1、Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没 有事务, 则创建⼀个新的事务.

2、Propagation.SUPPORTS : 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以⾮事务的方式继续运行.

3、Propagation.MANDATORY :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务,抛出异常.

4、Propagation.REQUIRES_NEW : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开启⾃⼰的事务, 且开启的事务相互独⽴, 互不干扰。

5、Propagation.NOT_SUPPORTED : 以⾮事务⽅式运行, 如果当前存在事务, 则把当前事务挂起(不⽤).
6、 Propagation.NEVER : 以非事务的方式运行,如果当前存在事务,则抛出异常。
7、Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏.
如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED
举例
REQUIRED(默认)

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的事务传播行为。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        innerService.innerMethod();
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
    }
}

在上述示例中,当outerMethod方法被调用时,如果当前存在事务(例如,在一个更大的业务流程中已经开启了事务),则innerMethod方法将加入该事务;如果outerMethod方法是在没有事务的环境下被调用,则会为outerMethodinnerMethod创建一个新的事务。
REQUIRES_NEW

总是创建一个新的事务。如果当前存在事务,则将当前事务挂起,新事务执行完毕后再恢复原事务。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        try {
            innerService.innerMethod();
        } catch (Exception e) {
            // 处理innerMethod方法抛出的异常,但不影响outerMethod方法所在的事务
            //...
        }
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
        if (true) {
            throw new RuntimeException("Inner method error");
        }
    }
}

在这个例子中,innerMethod方法总是会开启一个新的事务,即使outerMethod方法已经在一个事务中。如果innerMethod方法抛出异常并回滚,outerMethod方法所在的事务不会受到影响,因为innerMethod的事务是独立的。
SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        innerService.innerMethod();
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
    }
}

outerMethod在事务中调用innerMethod时,innerMethod会加入outerMethod的事务;如果outerMethod没有在事务中调用innerMethod,则innerMethod会以非事务方式执行。

NOT_SUPPORTED

总是以非事务方式执行,如果当前存在事务,则将当前事务挂起。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        innerService.innerMethod();
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
    }
}

在这种情况下,innerMethod永远不会在事务中执行,即使outerMethod处于事务环境中。

MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        innerService.innerMethod();
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.MANDATORY)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
    }
}

如果outerMethod没有在事务中调用innerMethod,将会抛出IllegalTransactionStateException异常,因为innerMethod要求必须在事务环境中执行。

NEVER

总是以非事务方式执行,如果当前存在事务,则抛出异常。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        innerService.innerMethod();
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.NEVER)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
    }
}

如果outerMethod在事务中调用innerMethod,将会抛出IllegalTransactionStateException异常。

NESTED

如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一个子事务,它有自己的保存点。如果子事务回滚,只会回滚到保存点,而不会影响外部事务;如果外部事务回滚,则子事务也会回滚。例如:

java 复制代码
@Service
@Transactional
public class OuterService {

    @Autowired
    private InnerService innerService;

    public void outerMethod() {
        // 执行一些业务操作
        //...
        try {
            innerService.innerMethod();
        } catch (Exception e) {
            // 处理innerMethod方法抛出的异常,但不影响outerMethod方法所在的事务
            //...
        }
        // 继续执行其他业务操作
        //...
    }
}

@Service
@Transactional(propagation = Propagation.NESTED)
public class InnerService {

    public void innerMethod() {
        // 执行内部业务操作
        //...
        if (true) {
            throw new RuntimeException("Inner method error");
        }
    }
}

在上述示例中,如果innerMethod抛出异常并回滚,只会回滚innerMethod中的操作,outerMethod中的其他操作不受影响;如果outerMethod整体回滚,则innerMethod的操作也会回滚。


Spring 事务管理及其事务传播机制为企业级应用开发提供了强大而灵活的事务处理能力。通过合理地配置事务和选择合适的事务传播行为,可以有效地保证数据的一致性、完整性和隔离性,同时满足复杂业务逻辑和分布式系统的需求。在实际开发中,我们需要深入理解事务传播机制的原理和应用场景,根据业务需求进行准确的配置,以构建健壮、可靠的应用程序。


梦想家命长,实干家寿短。------约.奥赖利

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

相关推荐
m0_7482368341 分钟前
Spring Boot日志:从Logger到@Slf4j的探秘
java·spring boot·spring
码字哥2 小时前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
m0_548514774 小时前
前端打印功能(vue +springboot)
前端·vue.js·spring boot
雨中奔跑的小孩5 小时前
爬虫学习案例3
爬虫·python·学习
customer085 小时前
【开源免费】基于SpringBoot+Vue.JS加油站管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·maven
Hello.Reader5 小时前
Spring Retry 与 Redis WATCH 结合实现高并发环境下的乐观锁
java·redis·spring
西岭千秋雪_5 小时前
设计模式の单例&工厂&原型模式
java·单例模式·设计模式·简单工厂模式·工厂方法模式·抽象工厂模式·原型模式
冷环渊5 小时前
React基础学习
前端·学习·react.js
fanchael_kui5 小时前
使用elasticsearch-java客户端API生成DSL语句
java·大数据·elasticsearch
m0_748256565 小时前
[CTF夺旗赛] CTFshow Web1-14 详细过程保姆级教程~
java