ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Springcloud基础知识(20)- Spring Cloud Alibaba Seata (六) | Nacos+Seata+Openfeign 分布式事务实例(订单服务、集成测试)

2022-08-01 22:36:32  阅读:194  来源: 互联网

标签:20 seata Openfeign 192.168 id import com order Seata



本文在 “Springcloud基础知识(19)- Spring Cloud Alibaba Seata (五) | Nacos+Seata+Openfeign 分布式事务实例(账户服务)” 里 SpringcloudDemo05 项目基础上,创建 SeataOrder 子模块,协同 SeataStorage 和 SeataAccount 子模块,进行集成测试。


1. 创建数据库

    在 MariaDB (MySQL) 中,创建一个名为 seata_order 的数据库实例,并在该数据库内执行以下 SQL。

 1         # tbl_orders(订单表)
 2         DROP TABLE IF EXISTS `tbl_orders`;
 3         CREATE TABLE `tbl_orders` (
 4             `id` bigint NOT NULL AUTO_INCREMENT,
 5             `user_id` bigint DEFAULT NULL COMMENT 'user id',
 6             `product_id` bigint DEFAULT NULL COMMENT 'product id',
 7             `count` int DEFAULT NULL COMMENT 'product count',
 8             `money` decimal(10,2) DEFAULT NULL COMMENT 'amount',
 9             `status` int DEFAULT NULL COMMENT 'order status:0:pending;1:finish',
10             PRIMARY KEY (`id`)
11         ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
12 
13         # undo_log(回滚日志表)
14         DROP TABLE IF EXISTS `undo_log`;
15         CREATE TABLE `undo_log` (
16             `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
17             `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
18             `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
19             `rollback_info` longblob NOT NULL COMMENT 'rollback info',
20             `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
21             `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
22             `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
23             UNIQUE KEY `ux_undo_log` (`branch_id`,`xid`)
24         ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;


2. 创建 Maven 模块

    选择左上的项目列表中的 SpringcloudDemo05,点击鼠标右键,选择 New -> Module 进入 New Module 页面:

        Maven -> Project SDK: 1.8 -> Check "Create from archtype" -> select "org.apache.maven.archtypes:maven-archtype-quickstart" -> Next

            Name: SeataOrder
            GroupId: com.example
            ArtifactId: SeataOrder

        -> Finish


3. 修改 pom.xml,内容如下

  1     <?xml version="1.0" encoding="UTF-8"?>
  2     <project xmlns="http://maven.apache.org/POM/4.0.0"
  3             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6         <parent>
  7             <artifactId>SpringcloudDemo05</artifactId>
  8             <groupId>com.example</groupId>
  9             <version>1.0-SNAPSHOT</version>
 10         </parent>
 11         <modelVersion>4.0.0</modelVersion>
 12 
 13         <artifactId>SeataOrder</artifactId>
 14 
 15         <name>SeataOrder</name>
 16         <!-- FIXME change it to the project's website -->
 17         <url>http://www.example.com</url>
 18 
 19         <properties>
 20             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 21             <maven.compiler.source>1.8</maven.compiler.source>
 22             <maven.compiler.target>1.8</maven.compiler.target>
 23             <maven.install.skip>true</maven.install.skip>
 24         </properties>
 25 
 26         <dependencies>
 27             <dependency>
 28                 <groupId>junit</groupId>
 29                 <artifactId>junit</artifactId>
 30                 <scope>test</scope>
 31             </dependency>
 32 
 33             <dependency>
 34                 <groupId>org.springframework.boot</groupId>
 35                 <artifactId>spring-boot-starter-web</artifactId>
 36             </dependency>        
 37             <dependency>
 38                 <groupId>org.springframework.boot</groupId>
 39                 <artifactId>spring-boot-starter-test</artifactId>
 40                 <scope>test</scope>
 41             </dependency>
 42 
 43             <!-- JDBC -->
 44             <dependency>
 45                 <groupId>org.springframework.boot</groupId>
 46                 <artifactId>spring-boot-starter-data-jdbc</artifactId>
 47             </dependency>
 48             <!-- Druid -->
 49             <dependency>
 50                 <groupId>com.alibaba</groupId>
 51                 <artifactId>druid</artifactId>
 52                 <version>1.2.8</version>
 53             </dependency>
 54 
 55             <!-- MariaDB -->
 56             <dependency>
 57                 <groupId>org.mariadb.jdbc</groupId>
 58                 <artifactId>mariadb-java-client</artifactId>
 59                 <version>${mariadb.version}</version>
 60             </dependency>
 61             <!-- MyBatis -->
 62             <dependency>
 63                 <groupId>org.mybatis.spring.boot</groupId>
 64                 <artifactId>mybatis-spring-boot-starter</artifactId>
 65                 <version>${mybatis.version}</version>
 66             </dependency>                
 67             <dependency>
 68                 <groupId>org.projectlombok</groupId>
 69                 <artifactId>lombok</artifactId>
 70                 <version>${lombok.version}</version>
 71             </dependency>
 72 
 73             <!-- nacos -->
 74             <dependency>
 75                 <groupId>com.alibaba.cloud</groupId>
 76                 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 77             </dependency>
 78             <dependency>
 79                 <groupId>com.alibaba.cloud</groupId>
 80                 <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 81             </dependency>
 82 
 83             <!-- seata -->
 84             <dependency>
 85                 <groupId>com.alibaba.cloud</groupId>
 86                 <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
 87                 <exclusions>
 88                     <exclusion>
 89                         <groupId>io.seata</groupId>
 90                         <artifactId>seata-spring-boot-starter</artifactId>
 91                     </exclusion>
 92                 </exclusions>
 93             </dependency>
 94             <dependency>
 95                 <groupId>io.seata</groupId>
 96                 <artifactId>seata-spring-boot-starter</artifactId>
 97                 <version>1.4.2</version>
 98             </dependency>
 99 
100             <!-- OpenFeign -->
101             <dependency>
102                 <groupId>org.springframework.cloud</groupId>
103                 <artifactId>spring-cloud-starter-openfeign</artifactId>
104             </dependency>
105             <dependency>
106                 <groupId>org.springframework.cloud</groupId>
107                 <artifactId>spring-cloud-loadbalancer</artifactId>
108             </dependency>
109 
110         </dependencies>
111 
112     </project>

 


4. 配置文件

    1) 访问 Nacos 页面修改 seataClient.properties

        浏览器访问 http://localhost:8848/nacos/, 输入登录名和密码(默认 nacos/nacos),点击提交按钮,跳转到 Nacos Server 控制台页面。

        在 Nacos Server 控制台的 “配置管理” 下的 “配置列表” 中,创建或修改如下配置。

 1             Data ID: seataClient.properties
 2             Group:   SEATA_GROUP
 3             配置格式: Properties
 4             配置内容:
 5 
 6                 service.vgroupMapping.default_tx_group=default
 7                 service.vgroupMapping.service-storage-group=default
 8                 service.vgroupMapping.service-account-group=default
 9                 service.vgroupMapping.service-order-group=default
10                 service.default.grouplist=127.0.0.1:8092

       
        注:可以把这两条内容直接加入到 seataServer.properties,无需新创建 seataClient.properties。这里分开放置 server 和 client 的配置,可以避免混淆两者的配置。

    2) 创建 src/main/resources/application.yml 文件

 1         server:
 2             port: 7001  # 端口号
 3 
 4         spring:
 5             application:
 6                 name: seata-order-7001  # 服务名
 7             datasource: # 数据源配置
 8                 driver-class-name: org.mariadb.jdbc.Driver
 9                 name: seata_order
10                 url: jdbc:mysql://127.0.0.1:3306/seata_order?rewriteBatchedStatements=true
11                 username: nacos
12                 password: nacos
13             cloud:
14                 nacos:
15                     discovery:
16                         server-addr: 127.0.0.1:8848
17                         namespace:  # 留空表示使用 public
18                         group: SEATA_GROUP
19                         username: nacos
20                         password: nacos
21                     config:
22                         server-addr: ${spring.cloud.nacos.discovery.server-addr}
23                         context-path: /nacos
24                         namespace:   # 留空表示使用 public
25                         username: ${spring.cloud.nacos.discovery.username}
26                         password: ${spring.cloud.nacos.discovery.password}
27 
28         mybatis:
29             mapper-locations: classpath:mapper/*.xml
30 
31         seata:
32             #enabled: true
33             application-id: ${spring.application.name}
34             tx-service-group: service-order-group
35             registry:
36                 type: nacos
37                 nacos:
38                     server-addr: ${spring.cloud.nacos.discovery.server-addr}
39                     #application: seata-server
40                     group: ${spring.cloud.nacos.discovery.group}
41                     namespace:   # 留空表示使用 public
42                     username: ${spring.cloud.nacos.discovery.username}
43                     password: ${spring.cloud.nacos.discovery.password}
44             config:
45                 type: nacos
46                 nacos:
47                     server-addr:  ${spring.cloud.nacos.discovery.server-addr}
48                     group: ${spring.cloud.nacos.discovery.group}
49                     namespace:
50                     username: ${spring.cloud.nacos.discovery.username}
51                     password: ${spring.cloud.nacos.discovery.password}
52                     dataId: seataClient.properties

 

5. 数据库配置

    1) 配置 Druid

        创建 src/main/java/com/example/config/DruidDataSourceConfig.java 文件

 1         package com.example.config;
 2 
 3         import javax.sql.DataSource;
 4         import java.sql.SQLException;
 5 
 6         import com.alibaba.druid.pool.DruidDataSource;
 7         import org.springframework.boot.context.properties.ConfigurationProperties;
 8         import org.springframework.context.annotation.Bean;
 9         import org.springframework.context.annotation.Configuration;
10         import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
11 
12         @Configuration
13         public class DruidDataSourceConfig implements WebMvcConfigurer {
14 
15             @ConfigurationProperties("spring.datasource")
16             @Bean
17             public DataSource dataSource() throws SQLException {
18                 DruidDataSource druidDataSource = new DruidDataSource();
19                 return druidDataSource;
20             }
21         }


    2) 实体类

        (1) 创建 src/main/java/com/example/entity/Order.java 文件

 1             package com.example.entity;
 2 
 3             import lombok.Data;
 4             import lombok.NoArgsConstructor;
 5             import lombok.experimental.Accessors;
 6             import java.io.Serializable;
 7 
 8             @NoArgsConstructor // 无参构造函数
 9             @Data // 提供类的 get、set、equals、hashCode、canEqual、toString 方法
10             @Accessors(chain = true)
11             public class Order implements Serializable {
12                 private Long id;
13                 private Long userId;
14                 private Long productId;
15                 private Integer count;
16                 private Double money;
17                 private Integer status;
18             }


        (2) 创建 src/main/java/com/example/entity/CommonResult.java 文件

 1             package com.example.entity;
 2 
 3             import lombok.Data;
 4             import lombok.experimental.Accessors;
 5             import java.io.Serializable;
 6 
 7             @Data // 提供类的 get、set、equals、hashCode、canEqual、toString 方法
 8             @Accessors(chain = true)
 9             public class CommonResult implements Serializable {
10                 private Integer code;
11                 private String description;
12 
13                 public CommonResult(Integer code, String description) {
14                     this.code = code;
15                     this.description = description;
16                 }
17 
18             }


    3) Mybatis Mapper

        (1) 创建 src/main/java/com/example/mapper/OrderMapper.java 文件

 1             package com.example.mapper;
 2 
 3             import com.example.entity.Order;
 4             import org.apache.ibatis.annotations.Mapper;
 5             import org.apache.ibatis.annotations.Param;
 6 
 7             @Mapper
 8             public interface OrderMapper {
 9 
10                 int insert(Order order);
11                 int insertSelective(Order order);
12 
13                 Order selectByPrimaryKey(Long id);
14 
15                 int updateByPrimaryKey(Order order);
16                 int updateByPrimaryKeySelective(Order order);
17                 void update(@Param("userId") Long userId, @Param("status") Integer status);
18 
19                 int deleteByPrimaryKey(Long id);
20             }


        (2) 创建 src/main/resources/mapper/OrderMapper.xml 文件

  1         <?xml version="1.0" encoding="UTF-8"?>
  2         <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3                                 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4         <mapper namespace="com.example.mapper.OrderMapper">
  5             <resultMap id="BaseResultMap" type="com.example.entity.Order">
  6                 <id column="id" jdbcType="BIGINT" property="id"/>
  7                 <result column="user_id" jdbcType="BIGINT" property="userId"/>
  8                 <result column="product_id" jdbcType="BIGINT" property="productId"/>
  9                 <result column="count" jdbcType="INTEGER" property="count"/>
 10                 <result column="money" jdbcType="DECIMAL" property="money"/>
 11                 <result column="status" jdbcType="INTEGER" property="status"/>
 12             </resultMap>
 13             <sql id="Base_Column_List">
 14                 id, user_id, product_id, count, money, status
 15             </sql>
 16             <insert id="insert" parameterType="com.example.entity.Order">
 17                 INSERT INTO tbl_orders (id, user_id, product_id, count, money, status)
 18                 VALUES (#{id,jdbcType=BIGINT}, #{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT},
 19                 #{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER})
 20             </insert>
 21             <update id="update">
 22                 UPDATE tbl_orders
 23                 SET status = 1
 24                 WHERE user_id = #{userId}
 25                 AND status = #{status};
 26             </update>
 27 
 28             <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
 29                 SELECT
 30                 <include refid="Base_Column_List"/>
 31                 FROM tbl_orders
 32                 WHERE id = #{id,jdbcType=BIGINT}
 33             </select>
 34             <insert id="insertSelective" parameterType="com.example.entity.Order">
 35                 INSERT INTO tbl_orders
 36                 <trim prefix="(" suffix=")" suffixOverrides=",">
 37                     <if test="id != null">
 38                         id,
 39                     </if>
 40                     <if test="userId != null">
 41                         user_id,
 42                     </if>
 43                     <if test="productId != null">
 44                         product_id,
 45                     </if>
 46                     <if test="count != null">
 47                         count,
 48                     </if>
 49                     <if test="money != null">
 50                         money,
 51                     </if>
 52                     <if test="status != null">
 53                         status,
 54                     </if>
 55                 </trim>
 56                 <trim prefix="values (" suffix=")" suffixOverrides=",">
 57                     <if test="id != null">
 58                         #{id,jdbcType=BIGINT},
 59                     </if>
 60                     <if test="userId != null">
 61                         #{userId,jdbcType=BIGINT},
 62                     </if>
 63                     <if test="productId != null">
 64                         #{productId,jdbcType=BIGINT},
 65                     </if>
 66                     <if test="count != null">
 67                         #{count,jdbcType=INTEGER},
 68                     </if>
 69                     <if test="money != null">
 70                         #{money,jdbcType=DECIMAL},
 71                     </if>
 72                     <if test="status != null">
 73                         #{status,jdbcType=INTEGER},
 74                     </if>
 75                 </trim>
 76             </insert>
 77             <update id="updateByPrimaryKeySelective" parameterType="com.example.entity.Order">
 78                 UPDATE tbl_orders
 79                 <set>
 80                     <if test="userId != null">
 81                         user_id = #{userId,jdbcType=BIGINT},
 82                     </if>
 83                     <if test="productId != null">
 84                         product_id = #{productId,jdbcType=BIGINT},
 85                     </if>
 86                     <if test="count != null">
 87                         count = #{count,jdbcType=INTEGER},
 88                     </if>
 89                     <if test="money != null">
 90                         money = #{money,jdbcType=DECIMAL},
 91                     </if>
 92                     <if test="status != null">
 93                         status = #{status,jdbcType=INTEGER},
 94                     </if>
 95                 </set>
 96                 WHERE id = #{id,jdbcType=BIGINT}
 97             </update>
 98             <update id="updateByPrimaryKey" parameterType="com.example.entity.Order">
 99                 UPDATE tbl_orders
100                 SET user_id    = #{userId,jdbcType=BIGINT},
101                 product_id = #{productId,jdbcType=BIGINT},
102                 count      = #{count,jdbcType=INTEGER},
103                 money      = #{money,jdbcType=DECIMAL},
104                 status     = #{status,jdbcType=INTEGER}
105                 WHERE id = #{id,jdbcType=BIGINT}
106             </update>
107             <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
108                 DELETE FROM tbl_orders
109                 WHERE id = #{id,jdbcType=BIGINT}
110             </delete>
111 
112         </mapper>


6. 业务操作

    1) 创建 src/main/java/com/example/service/OrderService.java 文件

1         package com.example.service;
2 
3         import com.example.entity.Order;
4 
5         public interface OrderService {
6 
7             int insert(Order order);
8 
9         }


    2) 创建 src/main/java/com/example/service/StorageFeignService.java 文件

 1         package com.example.service;
 2 
 3         import org.springframework.cloud.openfeign.FeignClient;
 4         import org.springframework.web.bind.annotation.PostMapping;
 5         import org.springframework.web.bind.annotation.RequestParam;
 6 
 7         @FeignClient(value = "seata-storage-5001")
 8         public interface StorageFeignService {
 9 
10             @PostMapping(value = "/storage/decrease")
11             int decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
12 
13         }


    3) 创建 src/main/java/com/example/service/AccountFeignService.java 文件

 1         package com.example.service;
 2 
 3         import org.springframework.cloud.openfeign.FeignClient;
 4         import org.springframework.web.bind.annotation.PostMapping;
 5         import org.springframework.web.bind.annotation.RequestParam;
 6 
 7         @FeignClient(value = "seata-account-6001")
 8         public interface AccountFeignService {
 9 
10             @PostMapping(value = "/account/decrease")
11             int decrease(@RequestParam("userId") Long userId, @RequestParam("money") Double money);
12 
13         }    


    4) 创建 src/main/java/com/example/service/OrderServiceImpl.java 文件

 1         package com.example.service;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4         import org.springframework.stereotype.Service;
 5         import io.seata.spring.annotation.GlobalTransactional;
 6 
 7         import com.example.entity.Order;
 8         import com.example.mapper.OrderMapper;
 9 
10         @Service
11         public class OrderServiceImpl implements OrderService {
12             @Autowired
13             private OrderMapper orderMapper;
14             @Autowired
15             private StorageFeignService storageFeignService;
16             @Autowired
17             private AccountFeignService accountFeignService;
18 
19             @Override
20             @GlobalTransactional(rollbackFor = Exception.class)
21             public int insert(Order order) {
22 
23                 order.setUserId(new Long(1));
24                 order.setStatus(0);
25 
26                 int ret = orderMapper.insert(order);
27                 System.out.println("OrderServiceImpl -> orderMapper.insert(): ret = " + ret);
28 
29                 ret = storageFeignService.decrease(order.getProductId(), order.getCount());
30                 System.out.println("OrderServiceImpl -> storageService.decrease(): ret = " + ret);
31 
32                 ret = accountFeignService.decrease(order.getUserId(), order.getMoney());
33                 System.out.println("OrderServiceImpl -> accountFeignService.decrease(): ret = " + ret);
34 
35                 orderMapper.update(order.getUserId(), 0);
36 
37                 return ret;
38 
39             }
40 
41         }


        在分布式微服务架构中,可以使用 Seata 提供的 @GlobalTransactional 注解实现分布式事务的开启、管理和控制。

        当调用 @GlobalTransaction 注解的方法时,TM 会先向 TC 注册全局事务,TC 生成一个全局唯一的 XID,返回给 TM。

        @GlobalTransactional 注解既可以在类上使用,也可以在类方法上使用,该注解的使用位置决定了全局事务的范围,具体关系如下:

            (1) 在类中某个方法使用时,全局事务的范围就是该方法以及它所涉及的所有服务。
            (2) 在类上使用时,全局事务的范围就是这个类中的所有方法以及这些方法涉及的服务。

    5) 创建 src/main/java/com/example/controller/OrderController.java 文件

 1         package com.example.controller;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4         import org.springframework.web.bind.annotation.GetMapping;
 5         import org.springframework.web.bind.annotation.PathVariable;
 6         import org.springframework.web.bind.annotation.RestController;
 7 
 8         import com.example.entity.Order;
 9         import com.example.entity.CommonResult;
10         import com.example.service.OrderService;
11 
12         @RestController
13         public class OrderController {
14             @Autowired
15             private OrderService orderService;
16 
17             @GetMapping("/order/create/{pid}/{count}/{money}")
18             public CommonResult create(@PathVariable("pid") Integer pid,
19                                     @PathVariable("count") Integer count,
20                                     @PathVariable("money") Double money) {
21 
22                 Order order = new Order();
23                 order.setProductId(Integer.valueOf(pid).longValue());
24                 order.setCount(count);
25                 order.setMoney(money);
26 
27                 System.out.println("OrderController -> create(): order = " + order);
28                 return orderService.insert(order);
29             }
30 
31         }


    6) 修改 src/main/java/com/example/App.java 文件

 1         package com.example;
 2 
 3         import org.springframework.boot.SpringApplication;
 4         import org.springframework.boot.autoconfigure.SpringBootApplication;
 5         import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 6         import org.springframework.cloud.openfeign.EnableFeignClients;
 7 
 8         @EnableDiscoveryClient
 9         @EnableFeignClients
10         @SpringBootApplication(scanBasePackages = "com.example")
11         public class App {
12             public static void main(String[] args) {
13                 SpringApplication.run(App.class, args);
14             }
15         }


7. 打包运行

    菜单 Run -> Edit Configurations (或工具条上选择) —> 进入 Run/Debug Configurations 页面 -> Click "+" add new configuration -> Select "Maven":

        Working directory: SeataOrder 所在路径
        Command line: clean package

    -> Apply / OK

    Click Run "SeataOrder [clean, package]" ,jar 包生成在目录 target/ 里

        SeataOrder-1.0-SNAPSHOT.jar
        SeataOrder-1.0-SNAPSHOT.jar.original

    打开 cmd 命令行窗口,进入 SeataOrder 模块目录,运行如下命令:

        ...\SpringcloudDemo05\SeataOrder>java -jar target\SeataOrder-1.0-SNAPSHOT.jar

    显示如下:

 1         ...
 2 
 3         INFO 15716 --- [           main] com.example.App                          : Started App in 3.62 seconds (JVM running for 4.009)
 4         INFO 15716 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.0.2:8092
 5         INFO 15716 --- [eoutChecker_2_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.0.2:8092
 6         INFO 15716 --- [eoutChecker_2_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:192.168.0.2:8092,msg:< RegisterRMRequest{resourceIds='null', applicationId='seata-order-7001', transactionServiceGroup='service-order-group'} >
 7         INFO 15716 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:TMROLE,address:192.168.0.2:8092,msg:< RegisterTMRequest{applicationId='seata-order-7001', transactionServiceGroup='service-order-group'} >
 8         INFO 15716 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient    : register TM success. client version:1.4.2, server version:1.4.2,channel:[id: 0x2dd221d2, L:/192.168.0.2:51581 - R:/192.168.0.2:8092]
 9         INFO 15716 --- [eoutChecker_2_1] i.s.c.rpc.netty.RmNettyRemotingClient    : register RM success. client version:1.4.2, server version:1.4.2,channel:[id: 0xc12cf699, L:/192.168.0.2:51582 - R:/192.168.0.2:8092]
10         INFO 15716 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 38 ms, version:1.4.2,role:TMROLE,channel:[id: 0x2dd221d2, L:/192.168.0.2:51581 - R:/192.168.0.2:8092]
11         INFO 15716 --- [eoutChecker_2_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 38 ms, version:1.4.2,role:RMROLE,channel:[id: 0xc12cf699, L:/192.168.0.2:51582 - R:/192.168.0.2:8092]


        注:从 log 可以看出 SeataOrder 成功连接到了 Seata Server (192.168.0.2:8092),192.168.0.2 是本地主机的内网地址。


8. 集成测试

    打开 cmd 命令行窗口,进入 SeataStorage 模块目录,运行以下命令:

        java -jar target\SeataStorage-1.0-SNAPSHOT.jar

    打开 cmd 命令行窗口,进入 SeataAccount 模块目录,运行以下命令:

        java -jar target\SeataAccount-1.0-SNAPSHOT.jar

    打开 cmd 命令行窗口,进入 SeataOrder 模块目录,运行以下命令:

        java -jar target\SeataOrder-1.0-SNAPSHOT.jar

    等待以上程序都连接到 Seata Server 后,开始测试。


    1) 正常访问

        浏览器访问 http://localhost:7001/order/create/1/1/1,页面返回结果如下:

            {"code":200,"description":"Create order successfully"}

        查看 seata_storage 数据库 tbl_storages 表,显示结果如下:

            id  product_id  total   used    residue
            1       1             100     1         99

        查看 seata_account 数据库 tbl_accounts 表,显示结果如下:

            id  user_id      total     used    residue
            1       1          1000.0    1.00     999.00

        查看 seata_order 数据库 tbl_orders 表,显示结果如下:

            id  user_id  product_id   count  money   status
            1      1             1              1        1.00        1

    2) 异常访问

        浏览器访问 http://localhost:7001/order/create/1/1/1000,该操作的意思是减掉 1 个库存和减掉 1000.00 余额,页面显示结果如下:

            Whitelabel Error Page
            This application has no explicit mapping for /error, so you are seeing this as a fallback.

            Mon Aug 01 18:01:47 CST 2022
            There was an unexpected error (type=Internal Server Error, status=500).
             
        页面显示的 500 异常,定位到 SeataOrder (seata-order-7001) 程序 OrderServiceImpl.insert() 方法,代码如下:

 1             @Override
 2             @GlobalTransactional(rollbackFor = Exception.class)
 3             public int insert(Order order) {
 4 
 5                 order.setUserId(new Long(1));
 6                 order.setStatus(0);
 7 
 8                 int ret = orderMapper.insert(order);
 9                 System.out.println("OrderServiceImpl -> orderMapper.insert(): ret = " + ret);
10 
11                 ret = storageFeignService.decrease(order.getProductId(), order.getCount());
12                 System.out.println("OrderServiceImpl -> storageService.decrease(): ret = " + ret);
13 
14                 ret = accountFeignService.decrease(order.getUserId(), order.getMoney());
15                 System.out.println("OrderServiceImpl -> accountFeignService.decrease(): ret = " + ret);
16 
17                 orderMapper.update(order.getUserId(), 0);
18 
19                 return ret;
20 
21             }


        异常发生在 accountFeignService.decrease() 方法,这个方法是通过 Openfeign 运程调用到 SeataAccount (seata-account-6001) 程序的 AccountServiceImpl.decrease() 方法。异常原因是 seata_account 数据库 tbl_accounts 表的 residue 值 999.00 小于 1000.00,就是余额不足。
        
        异常抛出前,orderMapper.insert() 和 storageFeignService.decrease() 已经完成了数据库插入和更新操作。事务的特点:所有操作必须全部完成,中间有操作出错,所有已完成的操作,需要恢复原样(或称事务回滚,Rollback)。

        @GlobalTransactional 注解的作用,就是在这种情形下触发 SeataOrder 和 SeataStorage 的事务回滚 (Rollback)。

    3) 异常访问的控制台信息

       SeataStorage (seata-storage-5001) 控制台输出如下(回滚日志):

1             ...
2 
3             2022-08-01 18:01:47.767  INFO 16120 --- [h_RMROLE_1_2_16] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=192.168.0.2:8092:2035917448298704911,branchId=2035917448298704915,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata_storage,applicationData=null
4             2022-08-01 18:01:47.768  INFO 16120 --- [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.0.2:8092:2035917448298704911 2035917448298704915 jdbc:mysql://127.0.0.1:3306/seata_storage
5             2022-08-01 18:01:47.804  INFO 16120 --- [h_RMROLE_1_2_16] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.0.2:8092:2035917448298704911 branch 2035917448298704915, undo_log deleted with GlobalFinished
6             2022-08-01 18:01:47.804  INFO 16120 --- [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked    


        SeataAccount (seata-account-6001) 控制台输出如下:

1             ...
2 
3             2022-08-01 17:58:20.810  INFO 49720 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=192.168.0.2:8092:2035917448298704897,branchId=2035917448298704904,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata_account,applicationData=null
4             2022-08-01 17:58:20.812  INFO 49720 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.0.2:8092:2035917448298704897 2035917448298704904 jdbc:mysql://127.0.0.1:3306/seata_account null
5             2022-08-01 17:58:20.814  INFO 49720 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
6             AccountServiceImpl -> decrease(): Insufficient Balance


        SeataOrder (seata-order-7001) 控制台输出如下(回滚日志):

1             ...
2 
3             18:01:47.813  INFO 15600 --- [h_RMROLE_1_3_16] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=192.168.0.2:8092:2035917448298704911,branchId=2035917448298704913,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata_order,applicationData=null
4             2022-08-01 18:01:47.813  INFO 15600 --- [h_RMROLE_1_3_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.0.2:8092:2035917448298704911 2035917448298704913 jdbc:mysql://127.0.0.1:3306/seata_order
5             2022-08-01 18:01:47.837  INFO 15600 --- [h_RMROLE_1_3_16] i.s.r.d.undo.AbstractUndoLogManager      : xid 192.168.0.2:8092:2035917448298704911 branch 2035917448298704913, undo_log deleted with GlobalFinished
6             2022-08-01 18:01:47.838  INFO 15600 --- [h_RMROLE_1_3_16] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
7             2022-08-01 18:01:47.848  INFO 15600 --- [nio-7001-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 192.168.0.2:8092:2035917448298704911
8             2022-08-01 18:01:47.848  INFO 15600 --- [nio-7001-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.0.2:8092:2035917448298704911] rollback status: Rollbacked


         Seata Server 控制台输出如下:

 1             ...
 2 
 3             18:01:47.697  INFO --- [verHandlerThread_1_34_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.0.2:8092:2035917448298704911, branchId = 2035917448298704913, resourceId = jdbc:mysql://127.0.0.1:3306/seata_order ,lockKeys = tbl_orders:2
 4             18:01:47.714  INFO --- [     batchLoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.0.2:8092:2035917448298704911,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata_storage,lockKey=tbl_storages:1
 5             ,clientIp:192.168.0.2,vgroup:service-storage-group
 6             18:01:47.720  INFO --- [verHandlerThread_1_35_500] i.seata.server.coordinator.AbstractCore  : Register branch successfully, xid = 192.168.0.2:8092:2035917448298704911, branchId = 2035917448298704915, resourceId = jdbc:mysql://127.0.0.1:3306/seata_storage ,lockKeys = tbl_storages:1
 7             18:01:47.759  INFO --- [     batchLoggerPrint_1_1] i.s.c.r.p.server.BatchLogHandler         : SeataMergeMessage xid=192.168.0.2:8092:2035917448298704911,extraData=null
 8             ,clientIp:192.168.0.2,vgroup:service-order-group
 9             18:01:47.811  INFO --- [verHandlerThread_1_36_500] io.seata.server.coordinator.DefaultCore  : Rollback branch transaction successfully, xid = 192.168.0.2:8092:2035917448298704911 branchId = 2035917448298704915
10             18:01:47.842  INFO --- [verHandlerThread_1_36_500] io.seata.server.coordinator.DefaultCore  : Rollback branch transaction successfully, xid = 192.168.0.2:8092:2035917448298704911 branchId = 2035917448298704913
11             18:01:47.846  INFO --- [verHandlerThread_1_36_500] io.seata.server.coordinator.DefaultCore  : Rollback global transaction successfully, xid = 192.168.0.2:8092:2035917448298704911.


    4) 异常访问的数据库结果显示 (回滚成功)

        查看 seata_storage 数据库 tbl_storages 表,显示结果如下:

            id  product_id  total   used    residue
            1       1            100      1         99

        查看 seata_account 数据库 tbl_accounts 表,显示结果如下:

            id  user_id      total     used    residue
            1       1          1000.0    1.00     999.00

        查看 seata_order 数据库 tbl_orders 表,显示结果如下:

            id  user_id  product_id   count    money   status
            1      1             1              1          1.00       1

 

标签:20,seata,Openfeign,192.168,id,import,com,order,Seata
来源: https://www.cnblogs.com/tkuang/p/16542083.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有