dubbo与seata集成

1.seata是什么?

        Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

2.seata的注解

@GlobalTransactional:全局事务注解,添加了以后可实现分布式事务的回滚和提交,用法与spring的@Transactional注解类似,注解参数的作用也基本一致

3.seata的事务模式

        seata有四种事务模式,分别为AT模式、TCC模式、Saga模式、XA模式,此处只说明AT模式及TCC模式。

3.1.Seata AT 模式

3.1.1.概述​

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

本文中,我们将重点介绍 Seata AT 模式的使用,如果您对于 AT 模式原理感兴趣,还请阅读对应于本篇文章的开发者指南。

3.1.2.整体机制​

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

3.1.3.基本使用​

我们首先抽象一个使用场景,在用户购买行为的时候需要减少库存并减少账户余额,当库存表 stock_tbl 和 account_tbl 在同一个数据库时,我们可以使用关系数据库自身提供的能力非常容易实现事务。但如果这两个表分属于不同的数据源,我们就要使用 Seata 提供的分布式事务能力了。

观察下方的示例代码,

@GlobalTransactional
public void purchase(String userId, String commodityCode, int count, int money) {jdbcTemplateA.update("update stock_tbl set count = count - ? where commodity_code = ?", new Object[] {count, commodityCode});jdbcTemplateB.update("update account_tbl set money = money - ? where user_id = ?", new Object[] {money, userId});
}

如果您曾使用过 Spring 框架 @Transactional 注解的话,也可以根据命名类比理解 @GlobalTransactional 的功能。是的,这里只是引入了一个注解就轻松实现了分布式事务能力,使用 AT 模式可以最小程度减少业务改造成本。

同时,需要注意的是,jdbcTemplateA 和 jdbcTemplateB 使用了不同的数据源进行构造,而这两个不同的数据源都需要使用 Seata 提供的 AT 数据源代理类 DataSourceProxy 进行包装。有关数据源代理帮助我们做了什么,请阅读附录中的事务隔离。

3.2.Seata TCC 模式

3.2.1概述​

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

Overview of a global transaction

本文中,我们将重点介绍 Seata TCC 模式的使用,如果您对于 TCC 模式原理感兴趣,想要了解 Seata TCC 对于幂等、空回滚、悬挂问题的解决,还请阅读对应于本篇文章的开发者指南。

3.2.2.优势​

TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。

3.2.3.缺点​

TCC 是一种侵入式的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。

3.2.4.适用场景​

TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。

3.2.5.整体机制​

在两阶段提交协议中,资源管理器(RM, Resource Manager)需要提供“准备”、“提交”和“回滚” 3 个操作;而事务管理器(TM, Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器“准备”是否成功,如果所有资源均“准备”成功则在第二阶段执行所有资源的“提交”操作,否则在第二阶段执行所有资源的“回滚”操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。

资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。

TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。

3.2.6基本使用​

区别于在 AT 模式直接使用数据源代理来屏蔽分布式事务细节,业务方需要自行定义 TCC 资源的“准备”、“提交”和“回滚” 。比如在下方的例子中,

public interface TccActionOne {@TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}

Seata 会把一个 TCC 接口当成一个 Resource,也叫 TCC Resource。在业务接口中核心的注解是 @TwoPhaseBusinessAction,表示当前方法使用 TCC 模式管理事务提交,并标明了 Try,Confirm,Cancel 三个阶段。name属性,给当前事务注册了一个全局唯一的的 TCC bean name。同时 TCC 模式的三个执行阶段分别是:

  • Try 阶段,预定操作资源(Prepare) 这一阶段所以执行的方法便是被 @TwoPhaseBusinessAction 所修饰的方法。如示例代码中的 prepare 方法。
  • Confirm 阶段,执行主要业务逻辑(Commit) 这一阶段使用 commitMethod 属性所指向的方法,来执行Confirm 的工作。
  • Cancel 阶段,事务回滚(Rollback) 这一阶段使用 rollbackMethod 属性所指向的方法,来执行 Cancel 的工作。

其次,可以在 TCC 模式下使用 BusinessActionContext 在事务上下文中传递查询参数。如下属性:

  • xid 全局事务id
  • branchId 分支事务id
  • actionName 分支资源id,(resource id)
  • actionContext 业务传递的参数,可以通过 @BusinessActionContextParameter 来标注需要传递的参数。

在定义好 TCC 接口之后,我们可以像 AT 模式一样,通过 @GlobalTransactional 开启一个分布式事务。

@GlobalTransactional
public String doTransactionCommit(){tccActionOne.prepare(null,"one");tccActionTwo.prepare(null,"two");
}

注意,如果 TCC 参与者是本地 bean(非远程RPC服务),本地 TCC bean 还需要在接口定义中添加 @LocalTCC 注解,比如,

@LocalTCC
public interface TccActionTwo {@TwoPhaseBusinessAction(name = "TccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}

4.seata服务启动

        seata的服务可去官网下载。我使用seata和nacos集成在一起。

1. 解压seata-server-$version.zip,修改conf/registry.conf文件

registry {type = "nacos"nacos {application = "seata-server"serverAddr = "127.0.0.1:8848"group = "SEATA_GROUP"namespace = ""cluster = "default"username = "nacos"password = "nacos"}
}config {type = "nacos"nacos {serverAddr = "127.0.0.1:8848"namespace = ""group = "SEATA_GROUP"username = "nacos"password = "nacos"}
}

2.由于使用nacos作为注册中心,所以conf目录下的file.conf无需理会。然后就可以直接启动bin/seata-server.bat,可以在nacos里看到一个名为seata-server的服务了。

3.由于seata使用mysql作为db高可用数据库,故需要在mysql创建一个dubbo-seata库,并导入数据库脚本。

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(`xid`                       VARCHAR(128) NOT NULL,`transaction_id`            BIGINT,`status`                    TINYINT      NOT NULL,`application_id`            VARCHAR(32),`transaction_service_group` VARCHAR(32),`transaction_name`          VARCHAR(128),`timeout`                   INT,`begin_time`                BIGINT,`application_data`          VARCHAR(2000),`gmt_create`                DATETIME,`gmt_modified`              DATETIME,PRIMARY KEY (`xid`),KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id`         BIGINT       NOT NULL,`xid`               VARCHAR(128) NOT NULL,`transaction_id`    BIGINT,`resource_group_id` VARCHAR(32),`resource_id`       VARCHAR(256),`branch_type`       VARCHAR(8),`status`            TINYINT,`client_id`         VARCHAR(64),`application_data`  VARCHAR(2000),`gmt_create`        DATETIME(6),`gmt_modified`      DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key`        VARCHAR(128) NOT NULL,`xid`            VARCHAR(96),`transaction_id` BIGINT,`branch_id`      BIGINT       NOT NULL,`resource_id`    VARCHAR(256),`table_name`     VARCHAR(32),`pk`             VARCHAR(36),`gmt_create`     DATETIME,`gmt_modified`   DATETIME,PRIMARY KEY (`row_key`),KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',`xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',`context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',`log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',`log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',`log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

4.config.txt文件复制到seata目录

config.txt

service.vgroupMapping.ruoyi-system-group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/ry-seata?useUnicode=true
store.db.user=root
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

5.nacos-config.sh复制到seata的conf目录,window脚本请参考seata的config目录下的README-zh.md

nacos-config.sh 

#!/usr/bin/env bash
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at、
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.while getopts ":h:p:g:t:u:w:" opt
docase $opt inh)host=$OPTARG;;p)port=$OPTARG;;g)group=$OPTARG;;t)tenant=$OPTARG;;u)username=$OPTARG;;w)password=$OPTARG;;?)echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "exit 1;;esac
doneurlencode() {for ((i=0; i < ${#1}; i++))dochar="${1:$i:1}"case $char in[a-zA-Z0-9.~_-]) printf $char ;;*) printf '%%%02X' "'$char" ;;esacdone
}if [[ -z ${host} ]]; thenhost=localhost
fi
if [[ -z ${port} ]]; thenport=8848
fi
if [[ -z ${group} ]]; thengroup="SEATA_GROUP"
fi
if [[ -z ${tenant} ]]; thentenant=""
fi
if [[ -z ${username} ]]; thenusername=""
fi
if [[ -z ${password} ]]; thenpassword=""
finacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"echo "set nacosAddr=$nacosAddr"
echo "set group=$group"failCount=0
tempLog=$(mktemp -u)
function addConfig() {curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$(urlencode $1)&group=$group&content=$(urlencode $2)&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/nullif [[ -z $(cat "${tempLog}") ]]; thenecho " Please check the cluster status. "exit 1fiif [[ $(cat "${tempLog}") =~ "true" ]]; thenecho "Set $1=$2 successfully "elseecho "Set $1=$2 failure "(( failCount++ ))fi
}count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do(( count++ ))key=${line%%=*}value=${line#*=}addConfig "${key}" "${value}"
doneecho "========================================================================="
echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
echo "========================================================================="if [[ ${failCount} -eq 0 ]]; thenecho " Init nacos config finished, please start seata-server. "
elseecho " init nacos config fail. "
fi

6.执行命令,后面填写nacos的IP地址,我的是本机所以是127.0.0.1 

sh nacos-config.sh 127.0.0.1

5.代码示例

        代码示例仅展示AT模式。工程分为4个,分别为:接口工程(dubbo-demo-interface)、用户管理工程(dubbo-demo-user-provider)、组织机构管理工程(dubbo-demo-dept-provider)、消费者模块(dubbo-demo-consumer).

        用户管理及组织机构管理为服务端,消费者模块远程调用组织机构新增和用户管理新增,使用seata的全局事务,保证数据的一致性。

        dubbo的相关代码此处不再详细说明,有兴趣的可参考我的另一篇博客《dubbo的springboot集成》。

5.1.接口工程代码示例

1)实体类(Uesr.java):

package com.jc.shop.dubbo.demo.domain;/*** 用户表*/
public class User implements java.io.Serializable{/*** 主键ID*/private long id;/*** 用户名称*/private String name;/*** 所属部门*/private long deptId;/*** 岗位*/private String post;private Dept dept;public Dept getDept() {return dept;}public void setDept(Dept dept) {this.dept = dept;}public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public long getDeptId() {return deptId;}public void setDeptId(long deptId) {this.deptId = deptId;}public String getPost() {return post;}public void setPost(String post) {this.post = post;}
}

2)组织机构类(Dept.java)

package com.jc.shop.dubbo.demo.domain;import java.io.Serializable;/*** 部门*/
public class Dept implements Serializable {private long id;private String name;private long parentId = 0;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public long getParentId() {return parentId;}public void setParentId(long parentId) {this.parentId = parentId;}
}

3)用户接口类:

package com.jc.shop.dubbo.demo.service;import com.jc.shop.dubbo.demo.domain.User;/*** 业务接口*/
public interface IUserService {public int insert(User user);
}

4)组织机构接口类:

package com.jc.shop.dubbo.demo.service;import com.jc.shop.dubbo.demo.domain.Dept;public interface IDeptService {public int insert(Dept dept);
}

5.2.用户管理工程示例代码

1)seata框架的maven依赖:

<!-- Seata对Spring Boot的支持 --><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><!-- 同样请根据最新的Seata版本号替换此处 --><version>1.4.2</version></dependency><!-- 如果你的项目使用了Dubbo,则需要添加Seata对Dubbo的支持 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><!-- 对应阿里云Spring Cloud Alibaba的Seata Dubbo starter版本 --></dependency>

  2)执行数据库脚本

         执行用户管理和组织机构的数据库脚本,因为是demo,所以将两个工程的表放在一个库中,表分库存也是一样的,因为两个工程的数据源是分开的:

        脚本中必须包含表:“undo_log”,如果分库,每个库中,都应该有该表,该表用于记录数据库操作的日志,用于事务回滚和提交。

/*Navicat Premium Data TransferSource Server         : 我的笔记本Source Server Type    : MariaDBSource Server Version : 110202Source Host           : 192.168.31.23:3306Source Schema         : dubbo-demoTarget Server Type    : MariaDBTarget Server Version : 110202File Encoding         : 65001Date: 09/01/2024 16:46:40
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for t_dept
-- ----------------------------
DROP TABLE IF EXISTS `t_dept`;
CREATE TABLE `t_dept`  (`t_id` bigint(20) NOT NULL AUTO_INCREMENT,`t_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,`t_parent_id` bigint(20) DEFAULT 0,PRIMARY KEY (`t_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (`t_id` bigint(20) NOT NULL AUTO_INCREMENT,`t_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,`t_dept_id` bigint(20) DEFAULT NULL,`t_post` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,PRIMARY KEY (`t_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',`xid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'global transaction id',`context` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` longblob NOT NULL COMMENT 'rollback info',`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` datetime(6) NOT NULL COMMENT 'create datetime',`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

3)用户管理工程的yml配置(application.yml):

# seata配置
seata:enabled: true# Seata 应用编号,默认为 ${spring.application.name}application-id: ${spring.application.name}# Seata 事务组编号,用于 TC 集群名。tx-service-group: test-group# 开启自动代理,关闭自动代理,适用于TCC模式enable-auto-data-source-proxy: true# 服务配置项service:# 虚拟组和分组的映射vgroup-mapping:test-group: default  #此处的test-group需和tx-service-group的值一致# 分组和 Seata 服务的映射grouplist:default: 127.0.0.1:8091   #seata服务的地址config:type: nacosnacos:group: SEATA_GROUPserver-addr: 127.0.0.1:8848namespace:registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848namespace:

4)UserServiceImpl.java实现上面定义的IUserService接口类

package com.jc.shop.dubbo.demo.service.impl;import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.mapper.UserMapper;
import com.jc.shop.dubbo.demo.service.IUserService;
import io.seata.core.context.RootContext;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
@DubboService(version = "1.0.0")
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper mapper;@Overridepublic int insert(User user) {//打印事务ID,在测试时,只要用户新增和组织机构新增的事务ID相同,就可以任务两个原子操作在同一个事务中。System.out.println("用户新增的事务ID为:"+ RootContext.getXID());return mapper.insert(user);}
}

5.3.组织机构工程示例代码

1)maven的依赖同5.2,此处不再赘述。

2)数据库与用户管理使用同一个库,此处不再赘述

3)用户管理工程的yml配置(application.yml):

# seata配置
seata:enabled: true# Seata 应用编号,默认为 ${spring.application.name}application-id: ${spring.application.name}# Seata 事务组编号,用于 TC 集群名tx-service-group: test-group# 开启自动代理enable-auto-data-source-proxy: true# 服务配置项service:# 虚拟组和分组的映射vgroup-mapping:test-group: default# 分组和 Seata 服务的映射grouplist:default: 127.0.0.1:8091config:type: nacosnacos:group: SEATA_GROUPserver-addr: 127.0.0.1:8848namespace:registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848namespace:

4)DeptServiceImpl实现IDeptService接口

package com.jc.shop.dubbo.demo.service.impl;import com.jc.shop.dubbo.demo.domain.Dept;
import com.jc.shop.dubbo.demo.mapper.DeptMapper;
import com.jc.shop.dubbo.demo.service.IDeptService;
import io.seata.core.context.RootContext;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
@DubboService(version = "1.0.0")
public class DeptServiceImpl implements IDeptService {@Autowiredprivate DeptMapper mapper;@Overridepublic int insert(Dept dept) {int i = mapper.insert(dept);System.out.println("部门新增,影响行数:"+i);System.out.println("组织机构的事务ID为:"+RootContext.getXID());return i;}
}

5.4.消费端的代码示例

1)maven依赖与5.2一致,此处不再赘述

2)yml文件配置(application.yml)

# seata配置
seata:enabled: true# Seata 应用编号,默认为 ${spring.application.name}application-id: ${spring.application.name}# Seata 事务组编号,用于 TC 集群名tx-service-group: test-group# 开启自动代理enable-auto-data-source-proxy: true# 服务配置项service:# 虚拟组和分组的映射vgroup-mapping:test-group: default# 分组和 Seata 服务的映射grouplist:default: 127.0.0.1:8091config:type: nacosnacos:group: SEATA_GROUPserver-addr: 127.0.0.1:8848namespace:registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848namespace:

2)远程调用+分布式事务的代码示例

package com.jc.shop.dubbo.demo.service.impl;import com.jc.core.exception.ServiceException;
import com.jc.shop.dubbo.demo.domain.Dept;
import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.service.IConsumerService;
import com.jc.shop.dubbo.demo.service.IDeptService;
import com.jc.shop.dubbo.demo.service.IUserService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;@Service
public class ConsumerServiceImpl implements IConsumerService {@DubboReference(version = "1.0.0")private IDeptService deptService;@DubboReference(version = "1.0.0")private IUserService userService;@Override@GlobalTransactional(rollbackFor = {ServiceException.class})public int insertUser(User user) {System.out.println("消费端:"+ RootContext.getXID());Dept dept = user.getDept();int i = deptService.insert(dept);user.setDeptId(dept.getId());//此处在组织机构新增成功后,故意抛出异常,用于测试dept数据是否可以正常回滚。if(i>0){throw new ServiceException();}int j = userService.insert(user);System.out.println("用户新增,影响行数:"+j);return j;}
}

3)定义controller层接口,使用postman等工具进行测试

package com.jc.shop.dubbo.demo.controller;import com.jc.core.domain.AjaxResult;
import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.service.IConsumerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/demo")
public class DemoController {@Autowiredprivate IConsumerService service;@PostMapping("/insert")public AjaxResult insert(@RequestBody User user){int i = service.insertUser(user);if(i>0) {return AjaxResult.success("success");}else{return AjaxResult.error();}}
}

使用postman进行接口调用

经过测试,发现dept表数据回滚成功,数据为空,若去掉抛异常的代码,则组织机构(t_dept)和用户表(t_user)均可以保存成功。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware虚拟机安装Ubuntu

准备:Ubuntu的镜像文件,VMware,手. 1.新建虚拟机&#xff0c;选择自定义&#xff0c;下一步。 2.硬件兼容性&#xff0c;选择Workstation 16.x&#xff0c;下一步。 3.选择安装程序光盘映像文件&#xff0c;路径为映像文件所在文件夹&#xff0c;下一步。 4. 创建用户和设置密…

hive 函数使用详解

一、前言 在任何一种编程语言中,函数可以说是必不可少的,像mysql、oracle中,提供了很多内置函数,或者通过自定义函数的方式进行定制化使用,而hive作为一门数据分析软件,随着版本的不断更新迭代,也陆续出现了很多函数,以满足日常数据查询与分析的各种场景。 二、hive 函…

GEE中导入研究区shape

找到Assets&#xff0c;新建NEW 等待上传完成 从Assets列表中找到导入文件&#xff0c;复制ID

106短信群发如何发送会员通知

106短信群发可以通过以下步骤发送会员通知&#xff1a; 1.确定通知内容&#xff1a;首先需要确定要发送的通知内容&#xff0c;包括通知的主题、内容、时间、地点等信息。 2.设计通知模板&#xff1a;根据通知内容&#xff0c;设计通知模板。模板应包括短信的格式、语言风格、…

AI教我学编程之C#入门程序详解与拓展

与AI肩并肩 前言一、一个简单的C#程序补充说明对话AI 二、标识符三、关键字四、Main五、空白1. 缩进2. 代码块的间距3. 操作符的空格4. 换行5. 一致性 六、语句七、从程序输出文本主题&#xff1a;从程序中输出文本1. Write 和 WriteLine 方法2. 格式字符串3. 多重标记和值4. 格…

Banner设计专家详细解释和使用技巧!

一、banner的作用 宝安在运营工作中具有吸引注意力、提高转化率、传达信息、提升品牌形象、引导用户行为、营造活动氛围等多种功能。因此&#xff0c;在运营工作中&#xff0c;需要根据具体需要和目标精心设计和合理使用宝安&#xff0c;才能达到更好的运营效果。 二、banner…

【读书笔记】网空态势感知理论与模型(七)

通过网络级对象依赖关系揭示0Day攻击路径 1. 研究动机 0Day攻击是攻击者和防御者之间信息不对称的结果。赛门铁克研究人员认为&#xff0c;典型的0Day攻击平均隐藏312天才会被觉察。 受到SKRM模型启发&#xff0c;Patrol系统以全局视野来调查在某一个路径上的0Day攻击行为。从…

k8s-存储 11

一、configmapu存储 首先&#xff0c;确保集群正常&#xff0c;节点都处于就绪状态 Configmap用于保存配置数据&#xff0c;以键值对形式存储。configMap资源提供了向 Pod 注入配置数据的方法&#xff0c;旨在让镜像和配置文件解耦&#xff0c;以便实现镜像的可移植性和可复用…

js实现倒计时

今天和大家分享一个小功能&#xff0c;在我们进行需要获取手机验证码或其他方式的验证码进行登录时会有一个功能&#xff0c;就是当你点击发送验证码时会有一个倒计时&#xff0c;倒计时没有结束时按钮是禁用的状态&#xff0c;防止重复发送短信&#xff0c;下面我们来看一下这…

【数据结构】——期末复习题题库(7)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

C2-4.3.1 多个决策树——随机森林

C2-4.3.1 多个决策树——随机森林 参考链接 1、为什么要使用多个决策树——随机森林&#xff1f; 决策树的缺点&#xff1a; A small change in the data can cause a large change in the structure of the decision tree causing instability 即&#xff1a;对数据集 中…

持续领跑云安全赛道!安全狗多项安全能力获认可

近日&#xff0c;以“数字安全 未来可期”为主题的“2024安全市场年度大会”在北京成功举行。 作为国内云原生安全领导厂商&#xff0c;安全狗也受邀出席此次活动。 厦门服云信息科技有限公司&#xff08;品牌名&#xff1a;安全狗&#xff09;创办于2013年&#xff0c;是国内领…

Redis主从复制哨兵及集群

目录 一.主从复制 主从复制的工作原理如下&#xff1a; 主从复制的作用&#xff1a; 搭建Redis 主从复制 每台服务器配置&#xff1a; ​编辑进行编译安装&#xff1a; 定义systemd服务管理脚本&#xff1a; 开启服务&#xff0c;报错看下内容&#xff1a; 修改 Redis…

Python 日志模块 logging 的最佳实践,内容干练简洁

文章目录 1. 引言2. 定义日志类3. 引用日志4. 参考 1. 引言 每次写 python 代码&#xff0c;想找一个日志模块 logging 的最佳实践&#xff0c;都要找一大圈&#xff0c;确不一定可以找到合适的最佳实践。 痛定思痛&#xff0c;我决定下笔记录目前觉得合适的 python 日志的用…

代码混淆:保护您的应用程序

目录 引言 什么是代码混淆&#xff1f; 代码混淆常见手段 代码混淆的作用 代码混淆可能带来的问题 常用的混淆工具 如何使用ipaguard进行混淆 总结 引言 在当今数字化时代&#xff0c;移动应用程序已经成为人们生活和工作中不可或缺的一部分。但是&#xff0c;随着应用…

PPT插件-大珩助手-清除同类

清除同类-颜色 对于选定的形状&#xff0c;删除相同颜色 清除同类-文本 删除当前幻灯片中的文本对象 清除同类-非文本 删除当前幻灯片中的非文本对象 清空当页 删除当前幻灯片中的所有对象 选中的形状动画 删除选中的对象上的形状动画 当前页形状动画 对于当前页中的…

MIB 变更周期

MIB 始终以 80 ms 的周期在 BCH 上传输并在 80 ms 内重复&#xff0c;并且它包括从小区获取 SIB1 所需的参数&#xff1b;如果 SSB 的周期大于 80 ms&#xff0c;则 MIB 的发送周期与 SSB 的周期相同。 在UE初始搜索时&#xff0c;SSB在半帧内的周期是20ms&#xff1b;所以对于…

Linux第23步_安装windows下的stm32cubeprogram软件

stm32cubeprogram软件是通过USB3.0接口烧写系统软件。 1、双击“05、开发工具\02、ST官方开发工具\en.stm32cubeprog_v2-5-0\SetupSTM32CubeProgrammer-2.5.0.exe”。 2、点击“Next” 3、点击“Next” 4、勾选“I agree”&#xff0c;点击“Next” 5、点击“Next” 6、点…

阴盘奇门八字排盘马星位置计算方法php代码

如下位置&#xff0c;马星的四个位置。 计算方法&#xff1a; 1。先根据出生年月日&#xff0c;计算得八字四柱。比如 2024年01月09日&#xff0c;四柱为 其中时柱地支为“申” 2。然后根据以下对应的数组&#xff0c;来找到id号&#xff0c;即马星位置。 根据下表来找到&am…