广告位联系
返回顶部
分享到

SpringBoot事务传播机制介绍

java 来源:互联网 作者:佚名 发布时间:2024-12-23 21:40:22 人浏览
摘要

在 Spring Boot 开发中,事务是一个至关重要的概念,尤其是在涉及多层业务逻辑或者多个数据库操作时。Spring 提供了强大的事务管理功能,使得开发者可以方便地控制事务的行为。事务传播机制

在 Spring Boot 开发中,事务是一个至关重要的概念,尤其是在涉及多层业务逻辑或者多个数据库操作时。Spring 提供了强大的事务管理功能,使得开发者可以方便地控制事务的行为。事务传播机制作为 Spring 事务管理的一部分,是 Spring 事务管理中一个非常重要的概念。

本文将介绍 Spring Boot 中事务传播机制的原理及其常用配置,以帮助开发者更好地理解事务传播的工作方式。

一、什么是事务传播机制?

事务传播机制定义了在多个方法中调用事务时,事务的行为是如何传播的。换句话说,它决定了一个事务方法在被另一个方法调用时应该如何处理事务的开启、提交、回滚等操作。

事务传播机制通过 @Transactional 注解的 propagation 属性来配置,它有多个传播行为,开发者可以根据具体的需求来选择合适的传播方式。常见的传播行为包括:

  • REQUIRED
  • REQUIRES_NEW
  • SUPPORTS
  • MANDATORY
  • NOT_SUPPORTED
  • NEVER
  • NESTED

二、Spring 事务传播机制的传播行为

1. REQUIRED(默认传播行为)

传播行为: 如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入到现有事务中。

应用场景: 这是最常用的传播行为,通常在业务方法调用中使用,确保调用方法的一致性。

1

2

3

4

5

6

7

8

9

@Transactional(propagation = Propagation.REQUIRED)

public void methodA() {

    // 业务逻辑

}

 

@Transactional(propagation = Propagation.REQUIRED)

public void methodB() {

    // 业务逻辑

}

  • 如果 methodA() 没有事务,它会新建一个事务;
  • 如果 methodA() 已经在一个事务中,那么 methodB() 会加入到这个事务中。

2. REQUIRES_NEW

传播行为: 总是新建一个事务。如果当前有事务存在,则将当前事务挂起,等新事务提交或回滚后再恢复当前事务。

应用场景: 当我们希望某个方法独立于当前事务进行处理,通常用于一些不希望受到外部事务影响的操作,例如日志记录、通知等。

1

2

3

4

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void methodC() {

    // 业务逻辑

}

  • 无论 methodC() 调用时是否有事务,它都会开启一个新的事务;
  • 如果 methodC() 调用时已经有事务存在,它会将当前事务挂起,开启一个新的事务;
  • 当 methodC() 的事务结束后,原本挂起的事务会恢复继续执行。

3. SUPPORTS

传播行为: 如果当前有事务,则加入到现有事务中;如果当前没有事务,则以非事务方式执行。

应用场景: 当方法支持事务,但不强制要求事务存在时,可以使用 SUPPORTS。例如,一些方法可能不需要事务,但如果存在事务,它们会加入其中。

1

2

3

4

@Transactional(propagation = Propagation.SUPPORTS)

public void methodD() {

    // 业务逻辑

}

  • 如果 methodD() 调用时已有事务,它将加入该事务;
  • 如果没有事务,methodD() 以非事务方式执行。

4. MANDATORY

传播行为: 如果当前有事务,则加入到现有事务中;如果没有事务,则抛出异常。

应用场景: 如果方法依赖事务执行,但又不希望自行创建事务,则可以使用 MANDATORY。如果没有现有事务,将抛出 TransactionRequiredException 异常。

1

2

3

4

@Transactional(propagation = Propagation.MANDATORY)

public void methodE() {

    // 业务逻辑

}

  • 如果当前没有事务,methodE() 会抛出异常;
  • 如果当前有事务,methodE() 会加入到该事务中。

5. NOT_SUPPORTED

传播行为: 如果当前有事务,则将当前事务挂起,并以非事务方式执行方法。

应用场景: 当某个方法不希望参与事务操作时,可以使用 NOT_SUPPORTED,例如一些查询操作,它们无需事务支持。

1

2

3

4

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public void methodF() {

    // 业务逻辑

}

  • 如果当前有事务,methodF() 会挂起当前事务,执行时不支持事务;
  • 如果没有事务,methodF() 以非事务方式执行。

6. NEVER

传播行为: 如果当前有事务,则抛出异常;如果没有事务,则以非事务方式执行。

应用场景: 当一个方法不允许在事务中运行时使用。例如,一些特定的检查方法,它们要求事务完全不存在。

1

2

3

4

@Transactional(propagation = Propagation.NEVER)

public void methodG() {

    // 业务逻辑

}

  • 如果当前有事务,methodG() 会抛出 IllegalTransactionStateException 异常;
  • 如果没有事务,methodG() 以非事务方式执行。

7. NESTED

传播行为: 如果当前没有事务,则新建一个事务;如果当前已有事务,则在当前事务中嵌套一个事务。嵌套事务可以独立提交或回滚。

应用场景: 如果你希望事务能够嵌套,并且在嵌套事务回滚时不会影响外部事务的提交,可以使用 NESTED。

1

2

3

4

@Transactional(propagation = Propagation.NESTED)

public void methodH() {

    // 业务逻辑

}

  • 如果 methodH() 内部抛出异常并回滚,则不会影响外部事务;
  • 如果 methodH() 成功提交,外部事务也会提交。

三、事务传播机制原理分析

1. 事务的传播原理

Spring 的事务传播机制实际上是通过 AOP(面向切面编程)来实现的。Spring 在运行时会生成一个代理对象(通常是 JDK 动态代理或 CGLIB 代理),在事务方法执行时,代理会负责判断事务的传播行为并根据行为决定是否开启新的事务或加入到现有事务中。

  • 事务开始:当方法执行时,代理会检查是否已有事务存在。如果没有,则会根据传播行为决定是否需要创建新的事务。
  • 事务嵌套:对于 REQUIRES_NEW 或 NESTED 传播行为,Spring 会创建新的事务,这些事务与外部事务相互独立。
  • 事务回滚:如果方法发生异常且指定了回滚规则,则代理会回滚事务。
  • 事务提交:当方法执行成功,Spring 会提交事务。

2. 事务传播机制的执行顺序

假设方法 A 调用方法 B,方法 B 使用 REQUIRES_NEW 传播行为:

  • 方法 A 开始执行时,判断是否有事务,如果没有事务,则开启事务。
  • 方法 A 调用方法 B,方法 B 会暂停方法 A 的事务,并开启自己的事务。
  • 方法 B 执行完成后,提交自己的事务,并恢复方法 A 的事务。

这就是事务传播机制在嵌套调用中的行为。

在 Spring 中,事务传播机制的实现依赖于 AOP(面向切面编程),而 AOP 只会应用于通过 Spring 管理的 bean。如果我们直接调用同一个类中的方法(即同一个实例的方法),则事务传播机制可能会失效,因为 Spring 的代理对象并未被应用到这些内部方法调用中。以下是关于事务传播机制的一些代码示例,并且会展示事务传播机制失效的场景。

四、代码测试示例

1. REQUIRED 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.REQUIRED)

    public void methodA() {

        System.out.println("methodA: 开始事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodA: 完成事务");

    }

 

    @Transactional(propagation = Propagation.REQUIRED)

    public void methodB() {

        System.out.println("methodB: 开始事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodB: 完成事务");

    }

 

    public void testRequiredPropagation() {

        methodA();  // 这会触发事务的传播机制

        methodB();  // 也会加入到当前事务中

    }

}

预期输出:

methodA: 开始事务
methodA: 完成事务
methodB: 开始事务
methodB: 完成事务

2. REQUIRES_NEW 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void methodC() {

        System.out.println("methodC: 开始新事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodC: 完成新事务");

    }

 

    public void testRequiresNewPropagation() {

        methodC();  // 新事务会独立执行

    }

}

预期输出:

methodC: 开始新事务
methodC: 完成新事务

3. SUPPORTS 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.SUPPORTS)

    public void methodD() {

        System.out.println("methodD: 支持事务(如果有)");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodD: 完成");

    }

 

    public void testSupportsPropagation() {

        methodD();  // 如果存在事务,方法会加入到当前事务中

    }

}

预期输出:

如果没有事务:

methodD: 支持事务(如果有)
methodD: 完成

如果有事务:

methodD: 支持事务(如果有)
methodD: 完成

4. MANDATORY 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.MANDATORY)

    public void methodE() {

        System.out.println("methodE: 必须加入事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodE: 完成事务");

    }

 

    public void testMandatoryPropagation() {

        methodE();  // 调用时必须有事务存在,否则会抛出异常

    }

}

预期输出:

  • 如果没有事务,抛出 TransactionRequiredException 异常;
  • 如果有事务,方法会加入现有事务。

5. NOT_SUPPORTED 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.NOT_SUPPORTED)

    public void methodF() {

        System.out.println("methodF: 当前事务被挂起");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodF: 完成");

    }

 

    public void testNotSupportedPropagation() {

        methodF();  // 如果有事务,会被挂起,执行非事务操作

    }

}

预期输出:

如果方法在事务中调用,则事务会被挂起,并执行非事务操作:

methodF: 当前事务被挂起
methodF: 完成

6. NESTED 传播行为示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.NESTED)

    public void methodG() {

        System.out.println("methodG: 开始嵌套事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodG: 完成嵌套事务");

    }

 

    public void testNestedPropagation() {

        methodG();  // 方法会启动一个嵌套事务

    }

}

预期输出:

methodG: 开始嵌套事务
methodG: 完成嵌套事务

五、事务传播机制失效的场景

场景 1:同一个类中的方法直接调用

如果我们在一个类的实例中直接调用另一个被 @Transactional 注解的方法,事务传播机制可能会失效,因为事务代理是基于 Spring AOP 的,而 AOP 仅对外部方法调用起作用。

示例:事务失效

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@Service

public class UserService {

 

    @Transactional(propagation = Propagation.REQUIRED)

    public void methodA() {

        System.out.println("methodA: 开始事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodA: 完成事务");

    }

 

    public void methodB() {

        System.out.println("methodB: 事务不生效(直接调用)");

        methodA();  // 直接调用 methodA(),事务不会传播

    }

}

问题:

methodB() 会直接调用 methodA(),但是因为事务注解依赖 AOP 代理,而 methodB() 没有通过 Spring 代理调用 methodA(),因此事务不会生效。

预期输出:

methodB: 事务不生效(直接调用)
methodA: 开始事务
methodA: 完成事务

事务应该在 methodA() 中生效,但因为是直接调用,所以没有生效。

解决方案

为了让事务传播机制生效,方法应该通过 Spring 容器中的代理对象进行调用,可以通过 @Autowired 注入当前类实例并调用其方法,或者通过使用外部类实例来间接调用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Service

public class UserService {

 

    @Autowired

    private UserService self;  // 注入当前类的代理实例

 

    @Transactional(propagation = Propagation.REQUIRED)

    public void methodA() {

        System.out.println("methodA: 开始事务");

        // 模拟数据库操作

        try {

            Thread.sleep(1000);  // 模拟耗时操作

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("methodA: 完成事务");

    }

 

    public void methodB() {

        System.out.println("methodB: 事务会生效(通过代理调用)");

        self.methodA();  // 通过代理调用 methodA()

    }

}

六、总结

Spring Boot 的事务传播机制为开发者提供了灵活的事务管理方式,确保在复杂的业务逻辑中能够精准地控制事务的行为。通过合理选择事务传播行为,我们可以在多层业务逻辑中实现事务的一致性和隔离性。

  • REQUIRED:大多数情况下使用此传播行为,保证事务一致性。
  • REQUIRES_NEW:当需要独立事务时使用。
  • SUPPORTS 和 NOT_SUPPORTED:当方法支持或不支持事务时使用。
  • MANDATORY 和 NEVER:严格控制事务的参与。
  • NESTED:支持嵌套事务,可以独立提交和回滚。

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • Maven管理多模块应用的统一版本号实现
    在大型项目中,尤其是涉及多个子模块的Maven项目,统一管理版本号是一个棘手的问题。尤其当项目的子模块多达几十个时,手动修改每个模
  • SpringBoot增量部署发布的实现

    SpringBoot增量部署发布的实现
    由于项目依赖的jar越来越多,Springboot默认的打包方式是将整个项目打包成一个jar包,每次发布时,打包后的jar越来越大,更新一个很小的功
  • SpringBoot将多个Excel打包下载的实现
    在Spring Boot应用中,如果你需要将多个Excel文件打包成一个ZIP文件并提供下载,你可以使用一些Java库来帮助完成这个任务。这里我将展示如何
  • SpringBoot事务传播机制介绍
    在 Spring Boot 开发中,事务是一个至关重要的概念,尤其是在涉及多层业务逻辑或者多个数据库操作时。Spring 提供了强大的事务管理功能,使
  • Java使用JNA调用DLL文件
    1、什么是JNA? JNA(Java Native Access)是一个在 Java 中调用本地代码的开源框架,提供了一种简单、高效的方式来访问本地动态链接库(如.d
  • Java8 CompletableFuture异步编程解读介绍

    Java8 CompletableFuture异步编程解读介绍
    CompletableFuturede介绍 Java 8 引入了 CompletableFuture 类,这是 Java 异步编程的一个重要进展。 CompletableFuture 提供了一种基于未来结果的异步编程模
  • 基于Maven pom文件使用分析
    project Maven 是一个强大的构建和依赖管理工具,pom.xml 文件是 Maven 项目的核心配置文件,用于定义项目的构建、依赖关系、插件、目标等。它
  • Java-URLDecoder、URLEncoder使用及说明介绍

    Java-URLDecoder、URLEncoder使用及说明介绍
    前言 Java中的URLDecoder和URLEncoder是用于对URL进行编码和解码的类。 URL编码是将URL中的特殊字符转换成特定的格式,以便于在URL中传递参数。
  • SpringBoot内置Tomcat启动方式

    SpringBoot内置Tomcat启动方式
    一、Tomcat相关配置类如何加载的? 在springboot项目中,我们只需要引入spring-boot-starter-web依赖,启动服务成功,我们一个web服务就搭建好了,
  • Springboot接收Get参数实践过程

    Springboot接收Get参数实践过程
    一、参数直接在路径中 1.假设请求地址是如下这种 RESTful 风格 hangge 这个参数值直接放在路径里面: http://localhost:8080/helloworld/张三 1 2 3 4 5
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计