This is an automated email from the ASF dual-hosted git repository.

zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 131872eee70 Fixes the problem that the Seata integration module cannot 
correctly determine the status of distributed transactions in 
multi-microservice scenarios (#32095)
131872eee70 is described below

commit 131872eee709be840452a54809bcf73fbc0d89bb
Author: Ling Hengqian <[email protected]>
AuthorDate: Sun Jul 14 16:39:44 2024 +0800

    Fixes the problem that the Seata integration module cannot correctly 
determine the status of distributed transactions in multi-microservice 
scenarios (#32095)
---
 .../special-api/transaction/seata.cn.md            | 284 ++++++++++++++++---
 .../special-api/transaction/seata.en.md            | 308 ++++++++++++++++++---
 .../startup/graalvm-native-image.cn.md             |   2 +-
 .../startup/graalvm-native-image.en.md             |   3 +-
 .../SeataATShardingSphereTransactionManager.java   |   2 +-
 5 files changed, 517 insertions(+), 82 deletions(-)

diff --git 
a/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.cn.md
 
b/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.cn.md
index 4d9c27207d3..62ff6a93a97 100644
--- 
a/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.cn.md
+++ 
b/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.cn.md
@@ -6,6 +6,7 @@ weight = 7
 ## 背景信息
 
 Apache ShardingSphere 提供 BASE 事务,集成了 Seata 的实现。本文所指 Seata 集成均指向 Seata AT 模式。
+对于 Seata TCC 模式的集成,被 
https://github.com/apache/shardingsphere/discussions/32023 阻塞。
 
 ## 前提条件
 
@@ -81,15 +82,25 @@ ALTER TABLE `undo_log` ADD INDEX `ix_log_created` 
(`log_created`);
 
 ### 修改配置
 
+在自有项目的 ShardingSphere 的 YAML 配置文件写入如下内容,参考 
[分布式事务](/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/transaction)。
+若初始化 ShardingSphere JDBC DataSource 时使用的是 Java API,参考 
[分布式事务](/cn/user-manual/shardingsphere-jdbc/java-api/rules/transaction)。
+
+```yaml
+transaction:
+   defaultType: BASE
+   providerType: Seata
+```
+
 在 classpath 的根目录中增加 `seata.conf` 文件, 
 配置文件格式参考 `io.seata.config.FileConfiguration` 的 
[JavaDoc](https://github.com/apache/incubator-seata/blob/v2.0.0/config/seata-config-core/src/main/java/io/seata/config/FileConfiguration.java)。
 
 `seata.conf` 存在四个属性,
 
-1. `shardingsphere.transaction.seata.at.enable`,当此值为`true`时,开启 ShardingSphere 
的 Seata AT 集成,存在默认值为 `true`
-2. `shardingsphere.transaction.seata.tx.timeout`,全局事务超时(秒),存在默认值为 `60`
-3. `client.application.id`,应用唯一主键
-4. `client.transaction.service.group`,所属事务组,存在默认值为 `default`
+1. `shardingsphere.transaction.seata.at.enable`,当此值为`true`时,开启 ShardingSphere 
的 Seata AT 集成。存在默认值为 `true`
+2. `shardingsphere.transaction.seata.tx.timeout`,全局事务超时(秒)。存在默认值为 `60`
+3. `client.application.id`,应用唯一主键,用于设置 Seata Transaction Manager Client 和 
Seata Resource Manager Client 的 `applicationId`
+4. `client.transaction.service.group`,所属事务组, 用于设置 Seata Transaction Manager 
Client 和 Seata Resource Manager Client 的 `transactionServiceGroup`。
+存在默认值为 `default`
 
 一个完全配置的 `seata.conf` 如下,
 
@@ -126,8 +137,8 @@ ShardingSphere 的 Seata 集成将获取到的 Seata 全局事务置入线程的
 
 针对 ShardingSphere 数据源,讨论 6 种情况,
 
-1. 手动获取从 ShardingSphere 数据源创建的 `java.sql.Connection` 实例,
-并手动调用 `setAutoCommit()`, `commit()` 和 `rollback()` 方法,这是被允许的。
+1. 手动获取从 ShardingSphere 数据源创建的 `java.sql.Connection` 实例,并手动调用 
`setAutoCommit()`, `commit()` 和 `rollback()` 方法,
+这是被允许的。
 
 2. 在函数上使用 Jakarta EE 8 的 `javax.transaction.Transactional` 注解,这是被允许的。
 
@@ -140,22 +151,9 @@ ShardingSphere 的 Seata 集成将获取到的 Seata 全局事务置入线程的
 6. 手动从 `io.seata.tm.api.GlobalTransactionContext ` 创建 
`io.seata.tm.api.GlobalTransaction` 实例,
 调用 `io.seata.tm.api.GlobalTransaction` 实例的 `begin()`, `commit()` 和 
`rollback()` 方法,这是**不被允许的**。
 
-对于Seata Server 2.0.0,
-Seata Server 不会为同一 **transaction group** 的所有已连接的 Seata Client 实例传递 
`io.seata.core.context.RootContext.getXID()` 的返回值,
-参考 https://seata.apache.org/docs/user/api/ 。
-这需要讨论两种情况,
-
-1. 在使用 ShardingSphere JDBC 的场景下,
-   跨多个微服务的事务场景需要考虑在起点微服务的上下文使用 `io.seata.core.context.RootContext.getXID()` 获取 
Seata XID 后通过 RPC 传递给终点微服务,
-   并在终点微服务的业务函数中调用 `io.seata.core.context.RootContext.bind(rpcXid)`。
-
-2. 在使用 ShardingSphere Proxy 的场景下,多个微服务均对着 ShardingSphere Proxy 的逻辑数据源操作本地事务,
-   这将在 ShardingSphere Proxy 的服务端来转化为对分布式事务的操作,不需要考虑额外的 Seata XID。
-
-在使用 Spring Boot OSS 的实际情景中,
+在使用 Spring Boot 的实际情景中,
 `com.alibaba.cloud:spring-cloud-starter-alibaba-seata` 和 
`io.seata:seata-spring-boot-starter` 常常被其他 Maven 依赖传递引入。
-为了避开事务冲突,你需要手动关闭 Seata 的自动配置类,
-并在 Spring Boot OSS 的配置文件中将 `seata.enable-auto-data-source-proxy` 的属性置为 
`false`。一个可能的依赖关系如下。
+为了避开事务冲突,用户需要在 Spring Boot 的配置文件中将 `seata.enable-auto-data-source-proxy` 的属性置为 
`false`。一个可能的依赖关系如下。
 
 ```xml
 <project>
@@ -185,30 +183,242 @@ Seata Server 不会为同一 **transaction group** 的所有已连接的 Seata C
 </project>
 ```
 
-对应的 Spring Boot OSS 启动类可能如下。
+classpath 下对应的 `application.yml` 需要包含以下配置。
+在此情况下,在 Spring Boot 的 `application.yaml` 内定义 Seata 的 `registry.conf` 的等价配置依然有效。
+当下游项目使用 `org.apache.shardingsphere:shardingsphere-transaction-base-seata-at` 的 
Maven 模块时,总是被鼓励使用 `registry.conf` 配置 Seata Client。
+
+```yaml
+seata:
+  enable-auto-data-source-proxy: false
+```
+
+### 跨服务调用的事务传播
+
+跨服务调用场景下的事务传播,并不像单个微服务内的事务操作一样开箱即用。
+对于 Seata Server,跨服务调用场景下的事务传播,要把 XID 通过服务调用传递到服务提供方,并绑定到 
`io.seata.core.context.RootContext` 中去。
+参考 https://seata.apache.org/docs/user/api/ 。这需要讨论两种情况,
+
+1. 在使用 ShardingSphere JDBC 的场景下,跨多个微服务的事务场景需要考虑在起点微服务的上下文使用 
`io.seata.core.context.RootContext.getXID()` 获取 Seata XID 后,
+   通过 HTTP 或 RPC 等手段传递给终点微服务,并在终点微服务的 Filter 或 Spring WebMVC 
HandlerInterceptor 中处理。
+   Spring WebMVC HandlerInterceptor 仅适用于 Spring Boot 微服务,对 Quarkus,Micronaut 
Framework 和 Helidon 无效。
+
+2. 在使用 ShardingSphere Proxy 的场景下,多个微服务均对着 ShardingSphere Proxy 的逻辑数据源操作本地事务,
+   这将在 ShardingSphere Proxy 的服务端来转化为对分布式事务的操作,不需要考虑额外的 Seata XID。
+
+引入简单场景来继续讨论在使用 ShardingSphere JDBC 的场景下,跨服务调用的事务传播。假设存在以下已知微服务和中间件的 Docker 
Image 实例。
+
+1. MySQL 数据库实例 `a-mysql`,所有 database 均已创建 `UNDO_LOG` 表和业务表。
+2. MySQL 数据库实例 `b-mysql`,所有 database 均已创建 `UNDO_LOG` 表和业务表。
+3. 使用 `file` 作为配置中心和注册中心的 Seata Server 实例 `a-seata-server`。
+4. 微服务实例 `a-service`。此微服务创建仅配置数据库实例 `a-mysql` 的 ShardingSphere JDBC DataSource。
+此 ShardingSphere JDBC DataSource 配置使用连接到 Seata Server 实例 `a-seata-server` 的 
Seata AT 集成,其 Seata Application Id 为 `service-a`,
+其 Seata 事务分组为 `default_tx_group`,其 `Virtual Group Mapping` 指向的 Seata 
Transaction Coordinator 集群分组为 `default`。
+此微服务实例 `a-service` 暴露单个 Restful API 的 GET 端点为 `/hello`,此 Restful API 端点的业务函数 
`aMethod` 使用了普通的本地事务注解。
+若此微服务基于 Spring Boot,
+
+```java
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class DemoController {
+   @Transactional
+   @GetMapping("/hello")
+   public String aMethod() {
+      // ... 对数据库实例 `a-mysql` 做 UPDATE 操作
+      return "Hello World!";
+   }
+}
+```
+
+5. 微服务实例 `b-service`。此微服务创建仅配置数据库实例 `b-mysql` 的 ShardingSphere JDBC DataSource。
+此 ShardingSphere JDBC DataSource 配置使用连接到 Seata Server 实例 `a-seata-server` 的 
Seata AT 集成,其 Seata Application Id 为 `service-b`,
+其 Seata 事务分组为 `default_tx_group`,其 `Virtual Group Mapping` 指向的 Seata 
Transaction Coordinator 集群分组为 `default`。
+此微服务实例 `b-service` 的业务函数 `bMethod` 使用普通的本地事务注解,并在 `bMethod` 通过 HTTP Client 
调用微服务实例 `a-service` 的 `/hello` Restful API 端点。
+若此微服务基于 Spring Boot,
 
 ```java
-import io.seata.spring.boot.autoconfigure.SeataAutoConfiguration;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class DemoService {
+   @Transactional
+   public void bMethod() {
+      RestTemplate restTemplate = new RestTemplateBuilder().build();
+      restTemplate.getForEntity("http://a-service/hello";, String.class);
+      // ... 对数据库实例 `b-mysql` 做 UPDATE 操作
+   }
+}
+```
+
+对于此简单场景,此时存在单个 Seata Server Cluster,其包含单个 `Virtual Group` 为 `default`。 此 
`Virtual Group` 包含单个 Seata Server 实例为 `a-seata-server`。
+
+讨论单服务调用的事务传播。当微服务实例 `a-service` 的业务函数 `aMethod` 抛出异常,在业务函数内对 MySQL 数据库实例 
`a-mysql` 的更改将被正常回滚。
+
+讨论跨服务调用的事务传播。当微服务实例 `b-service` 的业务函数 `bMethod` 抛出异常,在业务函数内对 MySQL 数据库实例 
`b-mysql` 的更改将被正常回滚,
+而微服务实例 `a-service` 的 `io.seata.core.context.RootContext` 未绑定微服务实例 `b-service` 
的业务函数 `bMethod` 的 Seata XID,
+因此在业务函数内对 MySQL 数据库实例 `a-mysql` 的更改将不会被回滚。
 
-@SpringBootApplication(exclude = SeataAutoConfiguration.class)
-public class ExampleApplication {
+为了实现当微服务实例 `b-service` 的业务函数 `bMethod` 抛出异常,在业务函数内对 MySQL 数据库实例 `a-mysql` 和 
`b-mysql` 的更改均被正常回滚,
+讨论不同场景下的常见处理方案。
 
-    public static void main(String[] args) {
-        SpringApplication.run(ExampleApplication.class, args);
+1. 微服务实例 `a-service` 和 `b-service` 均为基于 Jakarta EE 8 的 Spring Boot 2 微服务。
+用户可在微服务实例 `b-service` 的业务函数 `bMethod` 使用 
`org.springframework.web.client.RestTemplate` 把 XID 通过服务调用传递到微服务实例 `a-service`。
+可能的改造逻辑如下。
+
+```java
+import io.seata.core.context.RootContext;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class DemoService {
+    @Transactional
+    public void bMethod() {
+        RestTemplate restTemplate = new 
RestTemplateBuilder().additionalInterceptors((request, body, execution) -> {
+                    String xid = RootContext.getXID();
+                    if (null != xid) {
+                        request.getHeaders().add(RootContext.KEY_XID, xid);
+                    }
+                    return execution.execute(request, body);
+                })
+                .build();
+        restTemplate.getForEntity("http://a-service/hello";, String.class);
+        // ... 对数据库实例 `b-mysql` 做 UPDATE 操作
     }
+}
+```
+
+此时在微服务实例 `a-service` 和 `b-service` 均需要添加自定义的 
`org.springframework.web.servlet.config.annotation.WebMvcConfigurer` 实现。
+
+```java
+import io.seata.integration.http.TransactionPropagationInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
 
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new TransactionPropagationInterceptor());
+    }
 }
 ```
 
-classpath 下对应的 `application.yml` 需要包含以下配置。
-在此情况下,在 Spring Boot OSS 的 `application.yaml` 内定义  Seata 的 `registry.conf` 
的等价配置不一定有效。
-这取决于 Seata Client。
-当下游项目使用 `org.apache.shardingsphere:shardingsphere-transaction-base-seata-at` 的 
Maven 模块时,
-总是被鼓励使用 `registry.conf` 配置 Seata Client。
+此时,当微服务实例 `b-service` 的业务函数 `bMethod` 抛出异常,在业务函数内对 MySQL 数据库实例 `a-mysql` 和 
`b-mysql` 的更改均被正常回滚。
 
-```yaml
-seata:
-  enable-auto-data-source-proxy: false
+2. 微服务实例 `a-service` 和 `b-service` 均为基于 Jakarta EE 9/10 的 Spring Boot 3 微服务。
+用户可在微服务实例 `b-service` 的业务函数 `bMethod` 使用 
`org.springframework.web.client.RestClient` 把 XID 通过服务调用传递到微服务实例 `a-service`。
+可能的改造逻辑如下。
+
+```java
+import io.seata.core.context.RootContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestClient;
+
+@Service
+public class DemoService {
+    @Transactional
+    public void bMethod() {
+        RestClient restClient = 
RestClient.builder().requestInterceptor((request, body, execution) -> {
+                    String xid = RootContext.getXID();
+                    if (null != xid) {
+                        request.getHeaders().add(RootContext.KEY_XID, xid);
+                    }
+                    return execution.execute(request, body);
+                })
+                .build();
+        
restClient.get().uri("http://a-service/hello";).retrieve().body(String.class);
+        // ... 对数据库实例 `b-mysql` 做 UPDATE 操作
+    }
+}
+```
+
+此时在微服务实例 `a-service` 和 `b-service` 均需要添加自定义的 
`org.springframework.web.servlet.config.annotation.WebMvcConfigurer` 实现。
+
+```java
+import io.seata.integration.http.JakartaTransactionPropagationInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
+
+   @Override
+   public void addInterceptors(InterceptorRegistry registry) {
+      registry.addInterceptor(new JakartaTransactionPropagationInterceptor());
+   }
+}
+```
+
+此时,当微服务实例 `b-service` 的业务函数 `bMethod` 抛出异常,在业务函数内对 MySQL 数据库实例 `a-mysql` 和 
`b-mysql` 的更改均被正常回滚。
+
+3. 微服务实例 `a-service` 和 `b-service` 均为 Spring Boot 微服务,但使用的 API 网关中间件阻断了所有包含 
`TX_XID` 的 HTTP Header 的 HTTP 请求。
+用户需要考虑更改把 XID 通过服务调用传递到微服务实例 `a-service` 使用的 HTTP Header,或使用 RPC 框架把 XID 
通过服务调用传递到微服务实例 `a-service`。
+参考 https://github.com/apache/incubator-seata/tree/v2.0.0/integration 。
+
+4. 微服务实例 `a-service` 和 `b-service` 均为 Quarkus,Micronaut Framework 和 Helidon 
等微服务。此情况下无法使用 Spring WebMVC HandlerInterceptor。
+可参考如下 Spring Boot 3 的自定义 WebMvcConfigurer 实现,来实现 Filter。
+
+```java
+import io.seata.common.util.StringUtils;
+import io.seata.core.context.RootContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new HandlerInterceptor() {
+            @Override
+            public boolean preHandle(@NonNull HttpServletRequest request, 
@NonNull HttpServletResponse response, @NonNull Object handler) {
+                String rpcXid = request.getHeader(RootContext.KEY_XID);
+                String xid = RootContext.getXID();
+                if (StringUtils.isBlank(xid) && 
StringUtils.isNotBlank(rpcXid)) {
+                    RootContext.bind(rpcXid);
+                }
+                return true;
+            }
+
+            @Override
+            public void afterCompletion(@NonNull HttpServletRequest request, 
@NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
+                if (RootContext.inGlobalTransaction()) {
+                    String rpcXid = request.getHeader(RootContext.KEY_XID);
+                    String xid = RootContext.getXID();
+                    if (StringUtils.isNotBlank(xid)) {
+                        String unbindXid = RootContext.unbind();
+                        if (!StringUtils.equalsIgnoreCase(rpcXid, unbindXid)) {
+                            if (StringUtils.isNotBlank(unbindXid)) {
+                                RootContext.bind(unbindXid);
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+}
 ```
+
+5. 微服务实例 `a-service` 和 `b-service` 均为 Spring Boot 微服务,但使用的组件是 Spring WebFlux 
而非 Spring WebMVC。
+在反应式编程 API 下 ShardingSphere JDBC 无法处理 R2DBC DataSource,仅可处理 JDBC DataSource。
+在使用 WebFlux 组件的 Spring Boot 微服务中应避免创建 ShardingSphere JDBC DataSource。
+
+6. 微服务实例 `a-service` 和 `b-service` 使用的 Seata Client 为 
`org.apache.seata:seata-all`, 而非 `io.seata:seata-all`。
+将所有对 `io.seata` package 的调用更改为 `org.apache.seata` package 即可。
diff --git 
a/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.en.md
 
b/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.en.md
index c676c154611..3b28addf258 100644
--- 
a/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.en.md
+++ 
b/docs/document/content/user-manual/shardingsphere-jdbc/special-api/transaction/seata.en.md
@@ -7,6 +7,7 @@ weight = 7
 
 Apache ShardingSphere provides BASE transactions that integrate the Seata 
implementation.
 All references to Seata integration in this article refer to Seata AT mode.
+Integration of Seata TCC mode is blocked by 
https://github.com/apache/shardingsphere/discussions/32023 .
 
 ## Prerequisites
 
@@ -83,15 +84,28 @@ ALTER TABLE `undo_log` ADD INDEX `ix_log_created` 
(`log_created`);
 
 ### Modify configuration
 
+Write the following content in the YAML configuration file of ShardingSphere 
of your own project, 
+refer to [Distributed 
Transaction](/en/user-manual/shardingsphere-jdbc/yaml-config/rules/transaction).
+
+If Java API is used when initializing ShardingSphere JDBC DataSource, 
+refer to [Distributed 
Transaction](/en/user-manual/shardingsphere-jdbc/java-api/rules/transaction).
+
+```yaml
+transaction:
+  defaultType: BASE
+  providerType: Seata
+```
+
 Add the `seata.conf` file to the root directory of the classpath.
 The configuration file format refers to the 
[JavaDoc](https://github.com/apache/incubator-seata/blob/v2.0.0/config/seata-config-core/src/main/java/io/seata/config/FileConfiguration.java)
 of `io.seata.config.FileConfiguration`.
 
 There are four properties in `seata.conf`,
 
-1. `shardingsphere.transaction.seata.at.enable`, when this value is `true`, 
ShardingSphere's Seata AT integration is enabled, there is a default value of 
`true`
-2. `shardingsphere.transaction.seata.tx.timeout`, global transaction timeout 
in SECONDS, there is a default value of `60`
-3. `client.application.id`, apply the only primary key
-4. `client.transaction.service.group`, the transaction group it belongs to, 
there is a default value of `default`
+1. `shardingsphere.transaction.seata.at.enable`, when this value is `true`, 
enable ShardingSphere's Seata AT integration. The default value is `true`
+2. `shardingsphere.transaction.seata.tx.timeout`, global transaction timeout 
(seconds). The default value is `60`
+3. `client.application.id`, application unique primary key, used to set 
`applicationId` of Seata Transaction Manager Client and Seata Resource Manager 
Client
+4. `client.transaction.service.group`, transaction group, used to set 
`transactionServiceGroup` of Seata Transaction Manager Client and Seata 
Resource Manager Client.
+The default value is `default`
 
 A fully configured `seata.conf` is as follows,
 
@@ -129,7 +143,7 @@ That is, when using ShardingSphere's Seata integration, 
users should avoid using
 For ShardingSphere data source, discuss 6 situations,
 
 1. Manually obtain the `java.sql.Connection` instance created from the 
ShardingSphere data source,
-   and manually calling the `setAutoCommit()`, `commit()` and `rollback()` 
methods is allowed.
+and manually calling the `setAutoCommit()`, `commit()` and `rollback()` 
methods is allowed.
 
 2. Using the Jakarta EE 8 `javax.transaction.Transactional` annotation on the 
function is allowed.
 
@@ -142,25 +156,10 @@ For ShardingSphere data source, discuss 6 situations,
 6. Manually create `io.seata.tm.api.GlobalTransaction` instance from 
`io.seata.tm.api.GlobalTransactionContext`,
 calling the `begin()`, `commit()` and `rollback()` methods of an 
`io.seata.tm.api.GlobalTransaction` instance is **not allowed**.
 
-For Seata Server 2.0.0,
-Seata Server does not pass the return value of 
`io.seata.core.context.RootContext.getXID()` to all connected Seata Client 
instances of the same **transaction group**,
-Reference https://seata.apache.org/docs/user/api/ .
-This requires discussing two situations,
-
-1. In the scenario of using ShardingSphere JDBC,
-   transaction scenarios across multiple microservices need to consider using 
`io.seata.core.context.RootContext.getXID()` in the context of the starting 
microservice to obtain the Seata XID and then pass it to the ending 
microservice through RPC,
-   and call `io.seata.core.context.RootContext.bind(rpcXid)` in the business 
function of the endpoint microservice.
-
-2. In the scenario of using ShardingSphere Proxy,
-   Multiple microservices operate local transactions against the logical 
dataSource of ShardingSphere Proxy,
-   this will be converted into distributed transaction operations on the 
server side of ShardingSphere Proxy,
-   there are no additional Seata XIDs to consider.
-
-In the actual scenario of using Spring Boot OSS,
-`com.alibaba.cloud:spring-cloud-starter-alibaba-seata` and 
`io.seata:seata-spring-boot-starter` are often introduced transitively by other 
Maven dependencies.
-In order to avoid transaction conflicts, users need to manually turn off 
Seata's auto-config class.
-And set the `seata.enable-auto-data-source-proxy` property to `false` in the 
Spring Boot OSS configuration file. 
-A possible dependency is as follows.
+In actual scenarios where Spring Boot is used, 
+`com.alibaba.cloud:spring-cloud-starter-alibaba-seata` and 
`io.seata:seata-spring-boot-starter` are often transitively imported by other 
Maven dependencies.
+To avoid transaction conflicts, users need to set the property 
`seata.enable-auto-data-source-proxy` to `false` in the Spring Boot 
configuration file. 
+A possible dependency relationship is as follows.
 
 ```xml
 <project>
@@ -190,30 +189,257 @@ A possible dependency is as follows.
 </project>
 ```
 
-The corresponding Spring Boot OSS bootstrap class may be as follows.
+The corresponding `application.yml` under classpath needs to contain the 
following configuration.
+In this case, the equivalent configuration of Seata's `registry.conf` defined 
in Spring Boot's `application.yaml` is still valid.
+When downstream projects use the Maven module of 
`org.apache.shardingsphere:shardingsphere-transaction-base-seata-at`, 
+it is always encouraged to use `registry.conf` to configure Seata Client.
+
+```yaml
+seata:
+   enable-auto-data-source-proxy: false
+```
+
+### Transactional propagation across service calls
+
+Transactional propagationn in cross-service call scenarios is not as 
out-of-the-box as transaction operations within a single microservice.
+For Seata Server, transactional propagation in cross-service call scenarios 
requires passing XID to the service provider through service calls and binding 
it to `io.seata.core.context.RootContext`.
+Refer to https://seata.apache.org/docs/user/api/ . This requires discussing 
two situations,
+
+1. In the scenario of using ShardingSphere JDBC, 
+transaction scenarios across multiple microservices need to consider using 
`io.seata.core.context.RootContext.getXID()` to obtain Seata XID in the context 
of the starting microservice,
+and passing it to the end microservice through HTTP or RPC, and processing it 
in the Filter or Spring WebMVC HandlerInterceptor of the end microservice.
+Spring WebMVC HandlerInterceptor is only applicable to Spring Boot 
microservices and is invalid for Quarkus, Micronaut Framework and Helidon.
+
+2. In the scenario of using ShardingSphere Proxy, multiple microservices 
operate local transactions against the logical data source of ShardingSphere 
Proxy.
+This will be converted into distributed transaction operations on the server 
side of ShardingSphere Proxy, without considering additional Seata XID.
+
+Introduce a simple scenario to continue discussing the transactional 
propagation across service calls in the scenario of using ShardingSphere JDBC.
+
+1. MySQL database instance `a-mysql`, all databases have created `UNDO_LOG` 
table and business table.
+2. MySQL database instance `b-mysql`, all databases have created `UNDO_LOG` 
table and business table.
+3. Seata Server instance `a-seata-server` using `file` as configuration center 
and registration center.
+4. Microservice instance `a-service`. This microservice creates a 
ShardingSphere JDBC DataSource that only configures the database instance 
`a-mysql`.
+This ShardingSphere JDBC DataSource configuration uses the Seata AT 
integration connected to the Seata Server instance `a-seata-server`, 
+whose Seata Application Id is `service-a`, whose Seata transaction group is 
`default_tx_group`, 
+and the Seata Transaction Coordinator cluster group pointed to by its `Virtual 
Group Mapping` is `default`.
+This microservice instance `a-service` exposes a single Restful API GET 
endpoint as `/hello`,
+and the business function `aMethod` of this Restful API endpoint uses a common 
local transaction annotation.
+If this microservice is based on Spring Boot,
+
+```java
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class DemoController {
+   @Transactional
+   @GetMapping("/hello")
+   public String aMethod() {
+      // ... Perform an UPDATE operation on the database instance `a-mysql`
+      return "Hello World!";
+   }
+}
+```
+
+5. Microservice instance `b-service`. This microservice creates a 
ShardingSphere JDBC DataSource that only configures the database instance 
`b-mysql`.
+This ShardingSphere JDBC DataSource configuration uses the Seata AT 
integration connected to the Seata Server instance `a-seata-server`, 
+whose Seata Application Id is `service-b`, whose Seata transaction group is 
`default_tx_group`, 
+and whose `Virtual Group Mapping` points to the Seata Transaction Coordinator 
cluster group as `default`.
+The business function `bMethod` of this microservice instance `b-service` uses 
a normal local transaction annotation, 
+and calls the `/hello` Restful API endpoint of the microservice instance 
`a-service` through the HTTP Client in `bMethod`.
+If this microservice is based on Spring Boot,
 
 ```java
-import io.seata.spring.boot.autoconfigure.SeataAutoConfiguration;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class DemoService {
+   @Transactional
+   public void bMethod() {
+      RestTemplate restTemplate = new RestTemplateBuilder().build();
+      restTemplate.getForEntity("http://a-service/hello";, String.class);
+      // ... Perform an UPDATE operation on the database instance `b-mysql`
+   }
+}
+```
+
+For this simple scenario, there is a single Seata Server Cluster, which 
contains a single `Virtual Group` as `default`. 
+This `Virtual Group` contains a single Seata Server instance as 
`a-seata-server`.
+
+Discuss transaction propagation for single service calls. When the business 
function `aMethod` of the microservice instance `a-service` throws an 
exception, 
+the changes to the MySQL database instance `a-mysql` in the business function 
will be rolled back normally.
 
-@SpringBootApplication(exclude = SeataAutoConfiguration.class)
-public class ExampleApplication {
+Discuss transaction propagation for cross-service calls. When the business 
function `bMethod` of the microservice instance `b-service` throws an 
exception, 
+the changes to the MySQL database instance `b-mysql` in the business function 
will be rolled back normally,
+and the `io.seata.core.context.RootContext` of the microservice instance 
`a-service` is not bound to the Seata XID of the business function `bMethod` of 
the microservice instance `b-service`,
+so the changes to the MySQL database instance `a-mysql` in the business 
function will not be rolled back.
 
-     public static void main(String[] args) {
-         SpringApplication.run(ExampleApplication.class, args);
-     }
+In order to achieve that when the business function `bMethod` of the 
microservice instance `b-service` throws an exception, 
+the changes to the MySQL database instances `a-mysql` and `b-mysql` in the 
business function are rolled back normally,
+discuss the common processing solutions in different scenarios.
 
+1. The microservice instances `a-service` and `b-service` are both Spring Boot 
2 microservices based on Jakarta EE 8.
+Users can use `org.springframework.web.client.RestTemplate` in the business 
function `bMethod` of the microservice instance `b-service` to pass the XID to 
the microservice instance `a-service` through the service call.
+The possible transformation logic is as follows.
+
+```java
+import io.seata.core.context.RootContext;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class DemoService {
+    @Transactional
+    public void bMethod() {
+        RestTemplate restTemplate = new 
RestTemplateBuilder().additionalInterceptors((request, body, execution) -> {
+                    String xid = RootContext.getXID();
+                    if (null != xid) {
+                        request.getHeaders().add(RootContext.KEY_XID, xid);
+                    }
+                    return execution.execute(request, body);
+                })
+                .build();
+        restTemplate.getForEntity("http://a-service/hello";, String.class);
+        // ... Perform an UPDATE operation on the database instance `b-mysql`
+    }
 }
 ```
 
-The corresponding `application.yml` under the classpath needs to contain the 
following configuration.
-In this case, the equivalent configuration of Seata's `registry.conf` defined 
in `application.yaml` of Spring Boot OSS may not be valid.
-This depends on the Seata Client.
-When a downstream project uses the Maven module 
`org.apache.shardingsphere:shardingsphere-transaction-base-seata-at`,
-users are always encouraged to configure Seata Client using `registry.conf`.
+At this time, custom 
`org.springframework.web.servlet.config.annotation.WebMvcConfigurer` 
implementations need to be added to the microservice instances `a-service` and 
`b-service`.
 
-```yaml
-seata:
-   enable-auto-data-source-proxy: false
+```java
+import io.seata.integration.http.TransactionPropagationInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new TransactionPropagationInterceptor());
+    }
+}
 ```
+
+At this time, when the business function `bMethod` of the microservice 
instance `b-service` throws an exception, 
+the changes to the MySQL database instances `a-mysql` and `b-mysql` in the 
business function are rolled back normally.
+
+2. The microservice instances `a-service` and `b-service` are both Spring Boot 
3 microservices based on Jakarta EE 9/10.
+Users can use `org.springframework.web.client.RestClient` in the business 
function `bMethod` of the microservice instance `b-service` to pass the XID to 
the microservice instance `a-service` through a service call.
+The possible transformation logic is as follows.
+
+```java
+import io.seata.core.context.RootContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestClient;
+
+@Service
+public class DemoService {
+    @Transactional
+    public void bMethod() {
+        RestClient restClient = 
RestClient.builder().requestInterceptor((request, body, execution) -> {
+                    String xid = RootContext.getXID();
+                    if (null != xid) {
+                        request.getHeaders().add(RootContext.KEY_XID, xid);
+                    }
+                    return execution.execute(request, body);
+                })
+                .build();
+        
restClient.get().uri("http://a-service/hello";).retrieve().body(String.class);
+        // ... Perform an UPDATE operation on the database instance `b-mysql`
+    }
+}
+```
+
+At this time, custom 
`org.springframework.web.servlet.config.annotation.WebMvcConfigurer` 
implementations need to be added to the microservice instances `a-service` and 
`b-service`.
+
+```java
+import io.seata.integration.http.JakartaTransactionPropagationInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
+
+   @Override
+   public void addInterceptors(InterceptorRegistry registry) {
+      registry.addInterceptor(new JakartaTransactionPropagationInterceptor());
+   }
+}
+```
+
+At this time, when the business function `bMethod` of the microservice 
instance `b-service` throws an exception, 
+the changes to the MySQL database instances `a-mysql` and `b-mysql` in the 
business function are rolled back normally.
+
+3. The microservice instances `a-service` and `b-service` are both Spring Boot 
microservices, 
+but the API gateway middleware used blocks all HTTP requests containing the 
HTTP Header of `TX_XID`.
+The user needs to consider changing the HTTP Header used to pass XID to the 
microservice instance `a-service` through service calls, 
+or use the RPC framework to pass XID to the microservice instance `a-service` 
through service calls.
+Refer to https://github.com/apache/incubator-seata/tree/v2.0.0/integration .
+
+4. The microservice instances `a-service` and `b-service` are both 
microservices such as Quarkus, Micronaut Framework and Helidon. 
+In this case, Spring WebMVC HandlerInterceptor cannot be used.
+You can refer to the following Spring Boot 3 custom WebMvcConfigurer 
implementation to implement Filter.
+
+```java
+import io.seata.common.util.StringUtils;
+import io.seata.core.context.RootContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.lang.NonNull;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CustomWebMvcConfigurer implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new HandlerInterceptor() {
+            @Override
+            public boolean preHandle(@NonNull HttpServletRequest request, 
@NonNull HttpServletResponse response, @NonNull Object handler) {
+                String rpcXid = request.getHeader(RootContext.KEY_XID);
+                String xid = RootContext.getXID();
+                if (StringUtils.isBlank(xid) && 
StringUtils.isNotBlank(rpcXid)) {
+                    RootContext.bind(rpcXid);
+                }
+                return true;
+            }
+
+            @Override
+            public void afterCompletion(@NonNull HttpServletRequest request, 
@NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
+                if (RootContext.inGlobalTransaction()) {
+                    String rpcXid = request.getHeader(RootContext.KEY_XID);
+                    String xid = RootContext.getXID();
+                    if (StringUtils.isNotBlank(xid)) {
+                        String unbindXid = RootContext.unbind();
+                        if (!StringUtils.equalsIgnoreCase(rpcXid, unbindXid)) {
+                            if (StringUtils.isNotBlank(unbindXid)) {
+                                RootContext.bind(unbindXid);
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+}
+```
+
+5. Both microservice instances `a-service` and `b-service` are Spring Boot 
microservices, but the components used are Spring WebFlux instead of Spring 
WebMVC.
+ShardingSphere JDBC cannot handle R2DBC DataSource under the reactive 
programming API, only JDBC DataSource.
+Avoid creating ShardingSphere JDBC DataSource in Spring Boot microservices 
using WebFlux components.
+
+6. The Seata Client used by the microservice instances `a-service` and 
`b-service` is `org.apache.seata:seata-all`, not `io.seata:seata-all`.
+Change all calls to the `io.seata` package to the `org.apache.seata` package.
diff --git 
a/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.cn.md
 
b/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.cn.md
index 914bc949a8b..8693eca8cd0 100644
--- 
a/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.cn.md
+++ 
b/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.cn.md
@@ -121,7 +121,7 @@ services:
 ## 可观察性
 
 针对 GraalVM Native Image 形态的 ShardingSphere Proxy,其提供的可观察性的能力与
-https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-proxy/observability/
 并不一致。
+[可观察性](/cn/user-manual/shardingsphere-proxy/observability) 并不一致。
 
 你可以使用 https://www.graalvm.org/jdk22/tools/ 提供的一系列命令行工具或可视化工具观察 GraalVM Native 
Image 的内部行为,
 并根据其要求使用 VSCode 完成调试工作。如果你正在使用 IntelliJ IDEA 并且希望调试生成的 GraalVM Native 
Image,你可以关注
diff --git 
a/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.en.md
 
b/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.en.md
index 1368ece9cad..873145a916b 100644
--- 
a/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.en.md
+++ 
b/docs/document/content/user-manual/shardingsphere-proxy/startup/graalvm-native-image.en.md
@@ -129,8 +129,7 @@ services:
 ## Observability
 
 ShardingSphere for GraalVM Native Image form Proxy, which provides 
observability capabilities
-with 
https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-proxy/observability/
-not consistent.
+with [Observability](/en/user-manual/shardingsphere-proxy/observability) not 
consistent.
 
 You can observe GraalVM Native Image using a series of command line tools or 
visualization tools available
 at https://www.graalvm.org/jdk22/tools/, and use VSCode to debug it according 
to its requirements.
diff --git 
a/kernel/transaction/type/base/seata-at/src/main/java/org/apache/shardingsphere/transaction/base/seata/at/SeataATShardingSphereTransactionManager.java
 
b/kernel/transaction/type/base/seata-at/src/main/java/org/apache/shardingsphere/transaction/base/seata/at/SeataATShardingSphereTransactionManager.java
index 0e231560835..a15aab97313 100644
--- 
a/kernel/transaction/type/base/seata-at/src/main/java/org/apache/shardingsphere/transaction/base/seata/at/SeataATShardingSphereTransactionManager.java
+++ 
b/kernel/transaction/type/base/seata-at/src/main/java/org/apache/shardingsphere/transaction/base/seata/at/SeataATShardingSphereTransactionManager.java
@@ -87,7 +87,7 @@ public final class SeataATShardingSphereTransactionManager 
implements ShardingSp
     @Override
     public boolean isInTransaction() {
         checkSeataATEnabled();
-        return null != RootContext.getXID();
+        return null != SeataTransactionHolder.get();
     }
     
     @Override


Reply via email to