02 事务伴生源-Propagation

本文主要介绍了Spring事务的传播行为,包括PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS等七种类型,阐述了它们的含义及特点。还通过同类和异类方法调用的示例,分析了不同传播行为在实际应用中的表现,帮助理解事务嵌套和传播性。


01 Spring ‘拌‘ 事务 中提到事务需要注意以下情况:

恰如其分的使用’propagation’, 下面进入传播行为的视界.

一、Propagation(传播行为)

key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:

PROPAGATION_REQUIRED --支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS --支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY --支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW --新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED --以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER --以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED --如果当前事务存在,则嵌套事务执行。

解惑 spring 嵌套事务

TransactionDefinition 接口定义***********

int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;


二 通过同类+异类方法调用玩转事务传播性

如下,经典的事务嵌套(涉及到同类方法调用,异类方法调用,不同的事务传播性)
sample********************************

class ServiceA {   
     void methodA() {   
           // methodB()
         ServiceB.methodB();   
     }   

    void methodB(){
      
    }
}   


class ServiceB {
     void methodB() {   
     }   
       
}      

我们这里一个个分析吧

2.1 PROPAGATION_REQUIRED

(1) ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED;
(2) 那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。
(3) 而假如ServiceB.methodB运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

第一种:

if( ServiceA.methodA有事务){
	result1:ServiceA.methodA调用ServiceB.methodB时,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚.
        
	result2:ServiceA.methodA调用ServiceA.methodB时,在ServiceA.methodA或者在ServiceA.methodB内的任何地方出现异常,事务都会被回滚。
}

第二种:

eif(  ServiceA.methodA无事务 &&(ServiceA.methodB 有事务 || ServiceB.methodB 有事务)){
    result1:ServiceA.methodA调用ServiceB.methodB时, ServiceB.methodB 有事务,并且不影响ServiceA.methodA。

	result2:(类调用内部方法事务不起作用)ServiceA.methodA调用ServiceA.methodB时,两者均没有事务。
}

如下代码:

public class ServiceA {
    //@Transactional(propagation = Propagation.REQUIRED)
    void methodA() {
        //methodB();
        new ServiceB().methodB();
    }

    void methodB() {

    }
}

class ServiceB {
    @Transactional(propagation = Propagation.REQUIRED)
    void methodB() {

    }
}

**第二种情况,result2: 的原因:

  通过 Spring 官方文档可以知道:在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截,就像上面的 methodA 方法直接调用了同一个类中 methodB 方法,methodB 方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题。

2.2 PROPAGATION_SUPPORTS

  如果当前在事务中,即以事务的形式运行; 如果当前不再一个事务中,那么全部以非事务的形式运行。这就跟平常用的普通非事务的代码只有一点点区别了。和PROPAGATION_REQURIED的唯一区别就是:

有事务调用,即事务有效; 无事务调用,即无事务.

if(ServiceA.methodA 有事务){
    ServiceA.methodA调用ServiceB.methodB时,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会回滚。
}
if(ServiceA.methodA 无事务){
    ServiceA.methodA调用ServiceB.methodB时,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都不会被回滚。
}

2.3 PROPAGATION_MANDATORY

  强制必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常:IllegalTransactionStateException

异常如下:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:358)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:417)

2.4 PROPAGATION_REQUIRES_NEW

  新建一个事务

  我们设计 ServiceA.methodA 的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB 的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB 的时候,ServiceA.methodA 所在的事务就会挂起,ServiceB.methodB 会起一个新的事务,等待ServiceB.methodB的事务完成以后,methodA 才继续执行。

​ 与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么之后ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

2.5 PROPAGATION_NOT_SUPPORTED

  当前不支持事务

  比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

  但是如果ServiceB.methodB失败回滚,如果他抛出的异常被 ServiceA.methodA 捕获,ServiceA.methodA 事务仍然可能提交,如果没有被捕获,且异常是运行时异常或者rollbackFor制定的异常,那么ServiceA.methodA会回滚。

2.6 PROPAGATION_NEVER

  不能在事务中运行

  假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。

异常在这里: IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'.

2.7 PROPAGATION_NESTED

  理解Nested的关键是savepoint 。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的; 他回滚,父类也需要回滚。

  而Nested事务的好处是他有一个savepoint

ServiceA {   
     void methodA() {   
         try {
      //savepoint   
             ServiceB.methodB();    //PROPAGATION_NESTED 级别
         } catch (SomeException) {   
             // 执行其他业务, 如 ServiceC.methodC();   
         }   
     }   
}   

  也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如 ServiceC.methodC,继续执行,来尝试完成自己的事务。

<think>好的,我现在需要帮助用户了解context-propagation在Spring Boot中的使用场景和方法。首先,用户提到之前的问题是关于Spring Boot升级后Sleuth的替代方案,其中提到了context-propagation用于日志集成。现在用户想更深入了解这个库的具体使用方法和场景。 首先,我应该先回忆一下context-propagation的基本概念。根据之前的知识,Micrometer的context-propagation库用于在异步或多线程环境中传递上下文信息,比如Trace ID和Span ID,这对于分布式追踪非常重要。特别是在日志中注入这些ID,方便链路追踪。 用户之前的回答中提到了在升级到Spring Boot 3.4后,使用context-propagation来替代Sleuth的日志Trace ID功能。现在用户可能想知道更详细的使用方法,比如如何配置,具体代码示例,以及适用场景。 接下来,我需要结合用户提供的引用内容。引用1提到dynamic-datasource-spring-boot-starter和多数据源配置,引用2提到全局事务管理和spring-tx依赖。不过用户这次的问题是关于context-propagation,可能这些引用并不是直接相关,但需要确保回答不偏离用户的实际需求,即context-propagation的使用,而不是多数据源或事务管理。 用户可能需要知道context-propagation在哪些场景下使用,比如异步线程、响应式编程、跨服务调用等。同时需要具体的使用步骤,比如添加依赖、配置日志模式、手动传递上下文等。 需要注意用户可能是在处理分布式系统中的上下文传递问题,尤其是在追踪和日志方面。所以应该详细说明如何集成到日志系统,比如Logback或Log4j2的配置,以及如何在代码中显式传递上下文。 另外,用户可能对如何验证上下文是否正确传递感兴趣,所以可以加入验证方法,比如检查日志输出或使用Actuator端点。 需要确保回答结构清晰,分点说明使用场景和方法,并提供代码示例,特别是配置文件和Java代码的例子。同时,根据用户之前的问题,要关联到Micrometer Tracing,说明context-propagation在其中的作用。 最后,生成的相关问题要引导用户深入理解,比如与其他库的集成、性能影响、异常处理等。确保问题与主题相关,帮助用户进一步探索。</think>#### Context-Propagation 在 Spring Boot 中的使用场景与方法 --- ### **一、Context-Propagation 的核心作用** `context-propagation` 是 Micrometer 提供的上下文传播工具,用于在多线程、异步任务、响应式编程等场景下**自动传递上下文信息**(如 Trace ID、Span ID、用户身份、事务上下文等),确保链路追踪、日志关联和跨服务调用的连贯性[^1]。 --- ### **二、典型使用场景** 1. **分布式追踪(Tracing)** 在异步线程池、CompletableFuture、WebFlux 响应式流中传递 Trace ID,确保跨线程的链路完整性(如替换 Sleuth 的 Trace ID 传递功能)。 2. **日志关联** 在日志中自动注入 Trace ID 或自定义上下文(如用户 ID),实现请求链路的快速定位。 3. **跨服务调用** 通过上下文传递关键参数(如认证令牌、租户 ID),避免显式参数透传。 4. **事务管理** 在多数据源或分布式事务场景中,传递事务上下文(需结合 `spring-tx` 等库)[^2]。 --- ### **三、Spring Boot 中的使用方法** #### **1. 添加依赖** ```xml <dependency> <groupId>io.micrometer</groupId> <artifactId>context-propagation</artifactId> <version>1.0.0</version> <!-- 使用与 Spring Boot 3.x 兼容的版本 --> </dependency> ``` #### **2. 基础配置** **日志集成(Logback 示例)** 在 `logback-spring.xml` 中配置日志格式以包含 Trace ID: ```xml <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%X{traceId},%X{spanId}] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration> ``` #### **3. 手动传播上下文** ```java import io.micrometer.context.ContextSnapshot; // 示例:在子线程中捕获上下文 Runnable task = () -> { try (ContextSnapshot.Scope scope = ContextSnapshot.capture().setThreadLocals()) { // 在此作用域内,Trace ID 等上下文会自动传递 log.info("异步任务执行"); } }; new Thread(task).start(); ``` #### **4. 自动传播(结合 Reactor 或异步框架)** 若使用 WebFlux 或 Project Reactor,自动启用上下文传播: ```java // 响应式流中自动传递上下文 Mono.fromCallable(() -> "data") .contextWrite(Context.of("key", "value")) // 写入上下文 .flatMap(value -> { log.info("接收到值: {}", value); // 日志中自动包含上下文 return Mono.just(value.toUpperCase()); }); ``` --- ### **四、高级场景与技巧** #### **1. 自定义上下文值** ```java // 添加自定义上下文 Context context = Context.of("user.id", "12345"); // 获取上下文值 String userId = ContextSnapshot.capture().get("user.id"); ``` #### **2. 结合 Micrometer Tracing** 与 Micrometer Tracing 集成实现全链路追踪: ```java Tracer tracer = ... // 注入 Tracer Span span = tracer.spanBuilder().name("custom-operation").start(); try (Scope scope = span.makeCurrent()) { // 执行操作,上下文自动传播 } finally { span.end(); } ``` #### **3. 多线程池适配** 通过包装线程池实现上下文自动传递: ```java ExecutorService executor = ContextExecutorService.wrap( Executors.newFixedThreadPool(4), ContextSnapshot::capture ); executor.submit(() -> log.info("线程池任务")); ``` --- ### **五、验证与调试** 1. **检查日志输出**:确认日志中是否包含 Trace ID 和自定义上下文。 2. **Actuator 端点**:通过 `/actuator/trace` 或 Zipkin UI 验证链路完整性。 3. **单元测试**:使用 `ContextSnapshot` 验证上下文是否正确传递。 --- #### 相关问题 1. **如何通过 Context-Propagation 在响应式编程中传递自定义参数?** 2. **Context-Propagation 与 ThreadLocal 的兼容性如何?** 3. **在多数据源场景下,如何结合事务管理传递上下文?** [^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值