分布式事务--Seata-AT--SpringCloud(starter版) 青旅半醒 2023-01-06 05:48 169阅读 0赞 原文网址:[分布式事务--Seata--AT(starter)(feign)\_IT利刃出鞘的博客-CSDN博客][--Seata--AT_starter_feign_IT_-CSDN] # 其他网址 # **官网** > [Seata部署指南][Seata] **其他** > [分布式事务--Seata AT(非starter)(feign)\_feiying0canglang的博客-CSDN博客][--Seata AT_starter_feign_feiying0canglang_-CSDN] > > [springboot+seata+mybatisplus+springcloudalibaba整合demo,动动手给个star呗][springboot_seata_mybatisplus_springcloudalibaba_demo_star] > [SpringCloud项目升级seata遇到的坑,并使用Docker安装新版seata服务端 | 码农家园][SpringCloud_seata_Docker_seata_ _] # 综述 # **说明** > 本文测试Seata AT模式(使用新版本测试)。所有代码:[https://gitee.com/shapeless/demo\_SpringCloud/tree/seata-at-2][https_gitee.com_shapeless_demo_SpringCloud_tree_seata-at-2] > > 因为在之前测试Seata AT模式时,有一些问题,所以本处尝试新版本(官方说已经解决此问题)。之前的Seata AT模式的网址:[分布式事务--Seata AT(非starter)(feign)\_IT利刃出鞘的博客-CSDN博客][--Seata AT_starter_feign_feiying0canglang_-CSDN] **业务场景** > 创建订单时,order微服务先预生成订单(订单状态为创建中),再调用storage的feign来减库存,再调用account的feign来减账户余额,最后将订单状态改为已完成。 **技术框架** > **所用技术栈:** > > * spring-cloud-starter-alibaba-seata:2.2.3.RELEASE > * seata-spring-boot-starter:1.4.1 > * springboot:2.3.7.RELEASE > * springcloud:Hoxton.SR9 > * mysql > * mybatis-plus-boot-starter:3.4.1 > * eureka > * gateway > > **所用插件** > > * lombok **技术细节** > **配置方式** > > * seata-server: file.conf, registry.conf > * 业务微服务服务:application.yml > > **重大改变** > > 引入Seata之后,如果不使用@GlobalTransactional而调用feign不会再报“xid must not be blank”错误 # seata-server安装/配置 # **下载seata服务** > [https://github.com/seata/seata/releases][https_github.com_seata_seata_releases] **配置** > 本处使用mysql、eureka,未使用配置中心。 > > 修改的文件:seata-server-1.4.0\\seata\\conf\\ file.cof、registry.conf file.conf //配置事务的存储方式。改动点已经加粗。 > \#\# transaction log store, only used in seata-server > store \{ > \#\# store mode: file、db、redis > \# mode = "file" > **mode = "db"** \#将事务信息存到数据库中。配置为"db"时,只有“db”节点下的配置有效,不用管“file” > > \#\# file store property > file \{ > \#\# store location dir > dir = "sessionStore" > \# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions > maxBranchSessionSize = 16384 > \# globe session size , if exceeded throws exceptions > maxGlobalSessionSize = 512 > \# file buffer size , if exceeded allocate new buffer > fileWriteBufferCacheSize = 16384 > \# when recover batch read size > sessionReloadReadSize = 100 > \# async, sync > flushDiskMode = async > \} > > \#\# database store property > db \{ > \#\# the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. > datasource = "druid" > \#\# mysql/oracle/postgresql/h2/oceanbase etc. > \#\# dbType = "mysql" > \#\# driverClassName = "com.mysql.jdbc.Driver" > \#\# url = "jdbc:mysql://127.0.0.1:3306/seata" > \#\# user = "mysql" > \#\# password = "mysql" > **driverClassName = "com.mysql.cj.jdbc.Driver"** \#使用mysql8.x驱动 > **url: "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8"** \#指定服务器地址及数据库等 > **user = "xxx" > password = "xxx"** > minConn = 5 > maxConn = 100 > globalTable = "global\_table" > branchTable = "branch\_table" > lockTable = "lock\_table" > queryLimit = 100 > maxWait = 5000 > \} > > \#\# redis store property > redis \{ > host = "127.0.0.1" > port = "6379" > password = "" > database = "0" > minConn = 1 > maxConn = 10 > maxTotal = 100 > queryLimit = 100 > \} > > \} registry.conf //配置注册中心。改动点已经加粗。 > registry \{ > \# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa > \#type = "file" > **type = "eureka"** > > loadBalance = "RandomLoadBalance" > loadBalanceVirtualNodes = 10 > > nacos \{ > application = "seata-server" > serverAddr = "127.0.0.1:8848" > group = "SEATA\_GROUP" > namespace = "" > cluster = "default" > username = "" > password = "" > \} > eureka \{ > \# serviceUrl = "http://localhost:8761/eureka" > **serviceUrl = "http://localhost:7001/eureka"** > application = "default" > weight = "1" > \} > redis \{ > serverAddr = "localhost:6379" > db = 0 > password = "" > cluster = "default" > timeout = 0 > \} > zk \{ > cluster = "default" > serverAddr = "127.0.0.1:2181" > sessionTimeout = 6000 > connectTimeout = 2000 > username = "" > password = "" > \} > consul \{ > cluster = "default" > serverAddr = "127.0.0.1:8500" > \} > etcd3 \{ > cluster = "default" > serverAddr = "http://localhost:2379" > \} > sofa \{ > serverAddr = "127.0.0.1:9603" > application = "default" > region = "DEFAULT\_ZONE" > datacenter = "DefaultDataCenter" > cluster = "default" > group = "SEATA\_GROUP" > addressWaitTime = "3000" > \} > file \{ > name = "file.conf" > \} > \} > > config \{ > \# file、nacos 、apollo、zk、consul、etcd3 > type = "file" > > nacos \{ > serverAddr = "127.0.0.1:8848" > namespace = "" > group = "SEATA\_GROUP" > username = "" > password = "" > \} > consul \{ > serverAddr = "127.0.0.1:8500" > \} > apollo \{ > appId = "seata-server" > apolloMeta = "http://192.168.1.204:8801" > namespace = "application" > apolloAccesskeySecret = "" > \} > zk \{ > serverAddr = "127.0.0.1:2181" > sessionTimeout = 6000 > connectTimeout = 2000 > username = "" > password = "" > \} > etcd3 \{ > serverAddr = "http://localhost:2379" > \} > file \{ > name = "file.conf" > \} > \} # 建库建表 # **创建数据库** > 创建名为seata的数据库。 **seata服务事务表** > 这几张表用于seata-server存储事务。在上边**file.conf**指定的数据库里创建如下表: > > //有人说这些语句在seata-server-xxx/conf/db\_store.sql里。但我没找到,可能新的seata-server已经没有了。 > > -- -------------------------------- 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 = InnoDB > DEFAULT CHARSET = utf8; > > -- 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 = InnoDB > DEFAULT CHARSET = utf8; > > -- 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 = InnoDB > DEFAULT CHARSET = utf8; **业务表** > CREATE TABLE `t_order` ( > `id` bigint(11) NOT NULL AUTO_INCREMENT, > `user_id` bigint(11) DEFAULT NULL COMMENT '用户id', > `product_id` bigint(11) DEFAULT NULL COMMENT '产品id', > `count` int(11) DEFAULT NULL COMMENT '数量', > `money` decimal(11,0) DEFAULT NULL COMMENT '金额', > `create_time` datetime(0) NULL DEFAULT NULL, > `update_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0), > PRIMARY KEY (`id`) > ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; > > ALTER TABLE `t_order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' AFTER `money` ; > > 踩坑:其他教程表名是"order",我用mybatis-plus时,会报错。因为,它插入数据时语句是:INSERT INTO order ...,表名没有用\`\`包裹,而order是mysql关键字,所以报错。 > > CREATE TABLE `t_account` ( > `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id', > `user_id` bigint(11) DEFAULT NULL COMMENT '用户id', > `total` decimal(10,0) DEFAULT NULL COMMENT '总额度', > `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额', > `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度', > `create_time` datetime(0) NULL DEFAULT NULL, > `update_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0), > PRIMARY KEY (`id`) > ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; > > # INSERT INTO `seat-account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '100'); > INSERT INTO `demo`.`t_account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000'); > > CREATE TABLE `t_storage` ( > `id` bigint(11) NOT NULL AUTO_INCREMENT, > `product_id` bigint(11) DEFAULT NULL COMMENT '产品id', > `total` int(11) DEFAULT NULL COMMENT '总库存', > `used` int(11) DEFAULT NULL COMMENT '已用库存', > `residue` int(11) DEFAULT NULL COMMENT '剩余库存', > `create_time` datetime(0) NULL DEFAULT NULL, > `update_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0), > PRIMARY KEY (`id`) > ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; > > # INSERT INTO `seat-storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100'); > INSERT INTO `demo`.`t_storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100'); **业务事务表** > 每个被调用的服务以及服务的调用方都需要用到 "undo\_log"表用于事务的回滚。新版本只需创建一个这个表即可(如果是单个数据库,所有微服务共用这一个表;如果是多个数据库,需要每个数据库创建一个这个表) > > -- 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 = InnoDB > AUTO_INCREMENT = 1 > DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table'; # 业务微服务配置Seata # **order/storage/account** > 这三个微服务都用这种配置。只有一处是不同的:seata.application-id。 > application.yml > seata: > enabled: true > application-id: order\_seata > tx-service-group: my\_tx\_group > enable-auto-data-source-proxy: true > service: > vgroup-mapping: > my\_tx\_group: default \# key与上面的tx-service-group的值对应 > registry: > type: eureka > eureka: > application: default > service-url: http://localhost:7001/eureka > weight: 1 > \# config: > \# type: nacos > \# nacos: > \# namespace: > \# serverAddr: 127.0.0.1:8848 > \# group: SEATA\_GROUP > \# username: "nacos" > \# password: "nacos" # 业务主要代码 # ## order ## **controller** > package com.example.order.controller; > > import com.example.order.entity.Order; > import com.example.order.service.OrderService; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.web.bind.annotation.PostMapping; > import org.springframework.web.bind.annotation.RequestMapping; > import org.springframework.web.bind.annotation.RestController; > > @RestController > @RequestMapping("/order") > public class OrderController { > @Autowired > OrderService orderService; > > //使用Seata > @PostMapping("create") > public String create(Order order) { > orderService.create(order); > return "success"; > } > > //不使用Seata的 > @PostMapping("createNoSeata") > public String createNoSeata(Order order) { > orderService.createNoSeata(order); > return "success"; > } > > // 在另一个微服务里再调用feign > @PostMapping("create2") > public String create2(Order order) { > orderService.create2(order); > return "success"; > } > } **service** > Service > > package com.example.order.service; > > import com.baomidou.mybatisplus.extension.service.IService; > import com.example.order.entity.Order; > > public interface OrderService extends IService<Order> { > void create(Order order); > > void createNoSeata(Order order); > > void create2(Order order); > } > > ServiceImpl > > package com.example.order.service.impl; > > import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; > import com.example.base.feign.AccountFeignClient; > import com.example.base.feign.StorageFeignClient; > import com.example.order.entity.Order; > import com.example.order.mapper.OrderMapper; > import com.example.order.service.OrderService; > import io.seata.spring.annotation.GlobalTransactional; > import lombok.extern.slf4j.Slf4j; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.stereotype.Service; > import org.springframework.transaction.annotation.Transactional; > > @Slf4j > @Service > public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { > @Autowired > StorageFeignClient storageFeignClient; > > @Autowired > AccountFeignClient accountFeignClient; > > // AT模式 > @Override > @GlobalTransactional(name = "my_tx_group",rollbackFor = Exception.class) > public void create(Order order) { > log.info("--创建订单开始 (创建并冻结)"); > order.setStatus(0); > save(order); > log.info("--创建订单结束(创建并冻结成功"); > > log.info("--扣减库存开始"); > storageFeignClient.decreaseStorage(order.getProductId(), order.getCount()); > log.info("--扣减库存结束"); > > // int i = 1/0; > > log.info("--扣减账户余额开始"); > accountFeignClient.decreaseMoney(order.getUserId(), order.getMoney()); > log.info("--扣减账户余额结束"); > > this.getBaseMapper().updateStatus(order.getId(), 1); > > // int i = 1/0; > } > > @Override > @Transactional > public void createNoSeata(Order order) { > log.info("--创建订单开始 (创建并冻结)"); > order.setStatus(0); > save(order); > log.info("--创建订单结束(创建并冻结成功"); > > log.info("--扣减库存开始"); > storageFeignClient.decreaseStorage(order.getProductId(), order.getCount()); > log.info("--扣减库存结束"); > > // int i = 1/0; > > log.info("--扣减账户余额开始"); > accountFeignClient.decreaseMoney(order.getUserId(), order.getMoney()); > log.info("--扣减账户余额结束"); > > this.getBaseMapper().updateStatus(order.getId(), 1); > > // int i = 1/0; > } > > @Override > @GlobalTransactional(name = "my_tx_group",rollbackFor = Exception.class) > public void create2(Order order) { > log.info("--创建订单开始 (创建并冻结)"); > order.setStatus(0); > save(order); > log.info("--创建订单结束(创建并冻结成功"); > > log.info("--扣减库存和余额开始"); > storageFeignClient.decreaseStorageAndMoney(order.getProductId(), order.getCount(), > order.getUserId(), order.getMoney()); > log.info("--扣减库存和余额结束"); > > int i = 1/0; > > this.getBaseMapper().updateStatus(order.getId(), 1); > > // int i = 1/0; > } > } **mapper** > package com.example.order.mapper; > > import com.baomidou.mybatisplus.core.mapper.BaseMapper; > import com.example.order.entity.Order; > import org.apache.ibatis.annotations.Param; > import org.apache.ibatis.annotations.Update; > import org.springframework.stereotype.Repository; > > @Repository > public interface OrderMapper extends BaseMapper<Order> { > @Update("UPDATE `t_order` SET status = #{status} WHERE id = #{id}") > int updateStatus(@Param("id") Long id, @Param("status") Integer status); > } ## storage ## **controller** > package com.example.storage.feign; > > import com.example.storage.service.StorageService; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.web.bind.annotation.PostMapping; > import org.springframework.web.bind.annotation.RequestParam; > import org.springframework.web.bind.annotation.RestController; > > import java.math.BigDecimal; > > @RestController > public class FeignController { > @Autowired > StorageService storageService; > > @PostMapping("/feign/storage/decreaseStorage") > public void decreaseStorage(@RequestParam("productId")Long productId, @RequestParam("count")Integer count) { > storageService.decreaseStorage(productId, count); > } > > @PostMapping("/feign/storage/decreaseStorageAndMoney") > public void decreaseStorage(@RequestParam("productId")Long productId, @RequestParam("count")Integer count, > @RequestParam("userId")Long userId, @RequestParam("money") BigDecimal money) { > storageService.decreaseStorageAndMoney(productId, count, userId, money); > } > } **service** > Service > > package com.example.storage.service; > > import com.baomidou.mybatisplus.extension.service.IService; > import com.example.storage.entity.Storage; > > import java.math.BigDecimal; > > public interface StorageService extends IService<Storage> { > void decreaseStorage(Long productId, Integer count); > > void decreaseStorageAndMoney(Long productId, Integer count, Long userId, BigDecimal money); > } > > ServiceImpl > > package com.example.storage.service.impl; > > import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; > import com.example.storage.entity.Storage; > import com.example.storage.mapper.StorageMapper; > import com.example.storage.service.StorageService; > import lombok.extern.slf4j.Slf4j; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.stereotype.Service; > import com.example.base.feign.AccountFeignClient; > > import java.math.BigDecimal; > > @Service > @Slf4j > public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements StorageService { > @Autowired > AccountFeignClient accountFeignClient; > > @Override > public void decreaseStorage(Long productId, Integer count) { > // int i = 1 / 0; > getBaseMapper().decreaseStorage(productId, count); > } > > @Override > public void decreaseStorageAndMoney(Long productId, Integer count, Long userId, BigDecimal money) { > getBaseMapper().decreaseStorage(productId, count); > accountFeignClient.decreaseMoney(userId, money); > } > } **mapper** > package com.example.storage.mapper; > > import com.baomidou.mybatisplus.core.mapper.BaseMapper; > import com.example.storage.entity.Storage; > import org.apache.ibatis.annotations.Param; > import org.apache.ibatis.annotations.Update; > import org.springframework.stereotype.Repository; > > @Repository > public interface StorageMapper extends BaseMapper<Storage> { > @Update("UPDATE `t_storage` SET used= used + #{count}, residue = residue - #{count} WHERE product_id = #{productId}") > int decreaseStorage(@Param("productId") Long productId, @Param("count") Integer count); > } ## account ## **controller** > package com.example.account.feign; > > import com.example.account.service.AccountService; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.web.bind.annotation.PostMapping; > import org.springframework.web.bind.annotation.RequestParam; > import org.springframework.web.bind.annotation.RestController; > > import java.math.BigDecimal; > > @RestController > public class FeignController { > @Autowired > AccountService accountService; > > @PostMapping("/feign/account/decreaseMoney") > public void decreaseMoney(@RequestParam("userId")Long userId, @RequestParam("money")BigDecimal money) { > accountService.decreaseMoney(userId, money); > } > } **service** > Service > > package com.example.account.service; > > import com.baomidou.mybatisplus.extension.service.IService; > import com.example.account.entity.Account; > > import java.math.BigDecimal; > > public interface AccountService extends IService<Account> { > void decreaseMoney(Long userId, BigDecimal money); > } > > ServiceImpl > > package com.example.account.service.impl; > > import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; > import com.example.account.entity.Account; > import com.example.account.mapper.AccountMapper; > import com.example.account.service.AccountService; > import lombok.extern.slf4j.Slf4j; > import org.springframework.stereotype.Service; > import org.springframework.transaction.annotation.Transactional; > > import java.math.BigDecimal; > > @Service > @Slf4j > public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService { > @Override > public void decreaseMoney(Long userId, BigDecimal money) { > getBaseMapper().decreaseMoney(userId, money); > } > } **mapper** > package com.example.account.mapper; > > import com.baomidou.mybatisplus.core.mapper.BaseMapper; > import com.example.account.entity.Account; > import org.apache.ibatis.annotations.Param; > import org.apache.ibatis.annotations.Update; > import org.springframework.stereotype.Repository; > import org.springframework.web.bind.annotation.RequestParam; > > import java.math.BigDecimal; > > @Repository > public interface AccountMapper extends BaseMapper<Account> { > @Update("UPDATE `t_account` SET residue = residue - #{money},used = used + #{money} where user_id = #{userId}") > int decreaseMoney(@Param("userId")Long userId, @Param("money") BigDecimal money); > } # 业务次要代码 # ## **base** ## **通用应用注解** > package com.example.base.config; > > import org.mybatis.spring.annotation.MapperScan; > import org.springframework.boot.autoconfigure.SpringBootApplication; > import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; > import org.springframework.cloud.client.discovery.EnableDiscoveryClient; > import org.springframework.cloud.openfeign.EnableFeignClients; > import org.springframework.core.annotation.AliasFor; > > import java.lang.annotation.*; > > @Inherited > @Target(ElementType.TYPE) > @Retention(RetentionPolicy.RUNTIME) > @MapperScan("com.example.**.mapper") > @SpringBootApplication > @EnableDiscoveryClient > @EnableFeignClients("com.example.base.feign") > public @interface BaseApplication { > @AliasFor(annotation = SpringBootApplication.class) > String[] scanBasePackages() default "com.example.**"; > > // @AliasFor(annotation = SpringBootApplication.class) > // Class<?>[] exclude() default {DataSourceAutoConfiguration.class}; > } **mybatis-plus 自动填充插件** > package com.example.base.config; > > import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; > import org.apache.ibatis.reflection.MetaObject; > import org.mybatis.spring.annotation.MapperScan; > import org.slf4j.Logger; > import org.slf4j.LoggerFactory; > import org.springframework.context.annotation.Bean; > import org.springframework.context.annotation.Configuration; > > import java.time.LocalDateTime; > > @Configuration > //@MapperScan("com.example.**.mapper") > public class MyBatisPlusConfig { > private static final Logger logger = LoggerFactory.getLogger(MyBatisPlusConfig.class); > > //自动填充插件 > @Bean > public MetaObjectHandler metaObjectHandler() { > return new MybatisPlusAutoFillConfig(); > } > > public class MybatisPlusAutoFillConfig implements MetaObjectHandler { > // 新增时填充 > @Override > public void insertFill(MetaObject metaObject) { > logger.info("插入:自动填充createTime和updateTime:"+ LocalDateTime.now()); > setFieldValByName("createTime", LocalDateTime.now(), metaObject); > setFieldValByName("updateTime", LocalDateTime.now(), metaObject); > } > > // 修改时填充 > @Override > public void updateFill(MetaObject metaObject) { > logger.info("更新:自动填充updateTime:" + LocalDateTime.now()); > setFieldValByName("updateTime", LocalDateTime.now(), metaObject); > } > } > } **feign定义** > package com.example.base.feign; > > import org.springframework.cloud.openfeign.FeignClient; > import org.springframework.web.bind.annotation.PostMapping; > import org.springframework.web.bind.annotation.RequestParam; > > import java.math.BigDecimal; > > @FeignClient("account") > public interface AccountFeignClient { > @PostMapping("/feign/account/decreaseMoney") > void decreaseMoney(@RequestParam("userId")Long userId, @RequestParam("money") BigDecimal money); > } > > package com.example.base.feign; > > import org.springframework.cloud.openfeign.FeignClient; > import org.springframework.web.bind.annotation.PostMapping; > import org.springframework.web.bind.annotation.RequestParam; > > import java.math.BigDecimal; > > @FeignClient("storage") > public interface StorageFeignClient { > @PostMapping("/feign/storage/decreaseStorage") > void decreaseStorage(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); > > @PostMapping("/feign/storage/decreaseStorageAndMoney") > void decreaseStorageAndMoney(@RequestParam("productId") Long productId, @RequestParam("count") Integer count, > @RequestParam("userId") Long userId, @RequestParam("money")BigDecimal money); > } **pom.xml** > <?xml version="1.0" encoding="UTF-8"?> > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> > <modelVersion>4.0.0</modelVersion> > <parent> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-parent</artifactId> > <!-- <version>2.1.12.RELEASE</version>--> > <version>2.3.7.RELEASE</version> > </parent> > <packaging>pom</packaging> > <groupId>com.example</groupId> > <artifactId>base</artifactId> > <version>0.0.1-SNAPSHOT</version> > <name>base</name> > <description>Demo project for Spring Boot</description> > > <properties> > <java.version>1.8</java.version> > </properties> > > <dependencies> > <dependency> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-web</artifactId> > </dependency> > > <dependency> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-test</artifactId> > <scope>test</scope> > </dependency> > > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> > </dependency> > > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-starter-openfeign</artifactId> > </dependency> > > <dependency> > <groupId>org.projectlombok</groupId> > <artifactId>lombok</artifactId> > <version>1.18.12</version> > <scope>provided</scope> > </dependency> > > <dependency> > <groupId>mysql</groupId> > <artifactId>mysql-connector-java</artifactId> > <!-- <version>8.0.21</version> 版本Spring-Boot-Parent中已带 --> > </dependency> > > <dependency> > <groupId>com.baomidou</groupId> > <artifactId>mybatis-plus-boot-starter</artifactId> > <version>3.4.1</version> > </dependency> > > <dependency> > <groupId>com.alibaba.cloud</groupId> > <artifactId>spring-cloud-starter-alibaba-seata</artifactId> > <version>2.2.3.RELEASE</version> > <exclusions> > <exclusion> > <artifactId>seata-spring-boot-starter</artifactId> > <groupId>io.seata</groupId> > </exclusion> > </exclusions> > </dependency> > > <dependency> > <groupId>io.seata</groupId> > <artifactId>seata-spring-boot-starter</artifactId> > <version>1.4.1</version> > </dependency> > </dependencies> > > <dependencyManagement> > <dependencies> > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-dependencies</artifactId> > <!-- <version>Greenwich.SR3</version>--> > <version>Hoxton.SR9</version> > <type>pom</type> > <scope>import</scope> > </dependency> > </dependencies> > </dependencyManagement> > > <build> > <plugins> > <plugin> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-maven-plugin</artifactId> > </plugin> > <plugin> > <groupId>org.codehaus.mojo</groupId> > <artifactId>build-helper-maven-plugin</artifactId> > <version>1.8</version> > <executions> > <execution> > <id>add-source</id> > <phase>generate-sources</phase> > <goals> > <goal>add-source</goal> > </goals> > <configuration> > <sources> > <source>src/main/java</source> > <source>../base</source> > <!-- <source>../base/src/main/java</source>--> > </sources> > </configuration> > </execution> > </executions> > </plugin> > </plugins> > <!-- <resources>--> > <!-- <resource>--> > <!-- <directory>src/main/resources</directory>--> > <!-- <includes>--> > <!-- <include>**/*</include>--> > <!-- </includes>--> > <!-- <filtering>false</filtering>--> > <!-- </resource>--> > <!-- <resource>--> > <!-- <directory>../base/src/main/resources</directory>--> > <!-- <includes>--> > <!-- <include>**/*</include>--> > <!-- </includes>--> > <!-- <filtering>false</filtering>--> > <!-- </resource>--> > <!-- </resources>--> > </build> > > </project> ## eureka/gateway ## **eureka** > 启动类 > > package com.example.eurekaserver; > > import org.springframework.boot.SpringApplication; > import org.springframework.boot.autoconfigure.SpringBootApplication; > import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; > > @EnableEurekaServer > @SpringBootApplication > public class EurekaServerApplication { > > public static void main(String[] args) { > SpringApplication.run(EurekaServerApplication.class, args); > } > > } > > application.yml > > server: > port: 7001 > > spring: > application: > name: eureka-server > > eureka: > instance: > hostname: localhost1 > client: > register-with-eureka: false > fetch-registry: false > serviceUrl: > defaultZone: http://localhost:7001/eureka/ > > pom.xml > > <?xml version="1.0" encoding="UTF-8"?> > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> > <modelVersion>4.0.0</modelVersion> > <parent> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-parent</artifactId> > <!-- <version>2.0.6.RELEASE</version>--> > <version>2.1.12.RELEASE</version> > <relativePath/> <!-- lookup parent from repository --> > </parent> > <groupId>com.example</groupId> > <artifactId>eureka-server</artifactId> > <version>0.0.1-SNAPSHOT</version> > <name>eureka-server</name> > <description>Demo project for Spring Boot</description> > > <properties> > <java.version>1.8</java.version> > </properties> > > <dependencies> > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> > </dependency> > </dependencies> > > <dependencyManagement> > <dependencies> > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-dependencies</artifactId> > <!-- <version>Finchley.SR2</version>--> > <version>Greenwich.SR6</version> > <type>pom</type> > <scope>import</scope> > </dependency> > </dependencies> > </dependencyManagement> > </project> **gateway** > 启动类 > > package com.example.demo; > > import org.springframework.boot.SpringApplication; > import org.springframework.boot.autoconfigure.SpringBootApplication; > import org.springframework.cloud.client.discovery.EnableDiscoveryClient; > > @SpringBootApplication > @EnableDiscoveryClient > public class GatewayApplication { > > public static void main(String[] args) { > SpringApplication.run(GatewayApplication.class, args); > } > > } > > application.yml > > server: > port: 6001 > > spring: > application: > name: gateway > cloud: > gateway: > discovery: > locator: > enabled: true > lower-case-service-id: true > httpclient: > ssl: > use-insecure-trust-manager: true > > eureka: > client: > service-url: > defaultZone: http://localhost:7001/eureka/ > > # 配置Gateway日志等级,输出转发细节信息 > logging: > level: > org.springframework.cloud.gateway: debug > > pom.xml > > <?xml version="1.0" encoding="UTF-8"?> > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> > <modelVersion>4.0.0</modelVersion> > <parent> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-parent</artifactId> > <version>2.1.12.RELEASE</version> > <relativePath/> <!-- lookup parent from repository --> > </parent> > <groupId>com.example</groupId> > <artifactId>demo</artifactId> > <version>0.0.1-SNAPSHOT</version> > <name>gateway</name> > <description>Demo project for Spring Boot</description> > > <properties> > <java.version>1.8</java.version> > </properties> > > <dependencies> > <dependency> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter</artifactId> > </dependency> > > <dependency> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-starter-test</artifactId> > <scope>test</scope> > </dependency> > > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-starter-gateway</artifactId> > </dependency> > > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> > </dependency> > </dependencies> > > <dependencyManagement> > <dependencies> > <dependency> > <groupId>org.springframework.cloud</groupId> > <artifactId>spring-cloud-dependencies</artifactId> > <version>Greenwich.SR6</version> > <type>pom</type> > <scope>import</scope> > </dependency> > </dependencies> > </dependencyManagement> > > <build> > <plugins> > <plugin> > <groupId>org.springframework.boot</groupId> > <artifactId>spring-boot-maven-plugin</artifactId> > </plugin> > </plugins> > </build> > > </project> ## order/storage/account ## > 说明:order/storage/account的次要代码都是一样的。只是改了唯一性的东西:spring.applicaion.name、端口号、pom.xml的artifactId以及name。 **启动类** > package com.example.order; > > import com.example.base.config.BaseApplication; > import org.springframework.boot.SpringApplication; > > @BaseApplication > public class OrderApplication { > > public static void main(String[] args) { > SpringApplication.run(OrderApplication.class, args); > } > > } **application.yml** > server: > port: 9011 > > spring: > application: > name: order > datasource: > driver-class-name: com.mysql.cj.jdbc.Driver > url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 > username: root > password: 222333 > > eureka: > client: > service-Url: > defaultZone: http://localhost:7001/eureka > # defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka > > feign: > hystrix: > enabled: true > > mybatis-plus: > configuration: > log-impl: org.apache.ibatis.logging.stdout.StdOutImpl > > seata: > enabled: true > application-id: order_seata > tx-service-group: my_tx_group > enable-auto-data-source-proxy: true > service: > vgroup-mapping: > my_tx_group: default # key与上面的tx-service-group的值对应 > registry: > type: eureka > eureka: > application: default > service-url: http://localhost:7001/eureka > weight: 1 > # config: > # type: nacos > # nacos: > # namespace: > # serverAddr: 127.0.0.1:8848 > # group: SEATA_GROUP > # username: "nacos" > # password: "nacos" **pom.xml** > <?xml version="1.0" encoding="UTF-8"?> > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> > <modelVersion>4.0.0</modelVersion> > <parent> > <groupId>com.example</groupId> > <artifactId>base</artifactId> > <version>0.0.1-SNAPSHOT</version> > <relativePath>../base/pom.xml</relativePath> > </parent> > <artifactId>order</artifactId> > <version>0.0.1-SNAPSHOT</version> > <name>order</name> > <description>Demo project for Spring Boot</description> > > <properties> > <java.version>1.8</java.version> > </properties> > > <dependencies> > </dependencies> > > </project> # 测试 # > 启动注册中心,启动seata服务(运行seata-server-1.4.0\\seata\\bin\\seata-server.bat),启动gateway,启动各个业务微服务。 > > 注意:启动业务微服务之后,要等输出类似下边这种打印信息才算完全启动完毕 > > 2021-02-18 17:58:31.948 INFO 15040 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager : will connect to 192.168.20.17:8091 > 2021-02-18 17:58:31.949 INFO 15040 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:TMROLE,address:192.168.20.17:8091,msg:< RegisterTMRequest{applicationId='order_seata', transactionServiceGroup='my_tx_group'} > > 2021-02-18 17:58:31.954 INFO 15040 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient : register TM success. client version:1.4.1, server version:1.4.0,channel:[id: 0x6a95bd21, L:/192.168.20.17:50145 - R:/192.168.20.17:8091] > 2021-02-18 17:58:31.954 INFO 15040 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory : register success, cost 3 ms, version:1.4.0,role:TMROLE,channel:[id: 0x6a95bd21, L:/192.168.20.17:50145 - R:/192.168.20.17:8091] > **使用seata** > > 访问:[http://localhost:6001/order/order/create?userId=1&productId=1&count=10&money=100][http_localhost_6001_order_order_create_userId_1_productId_1_count_10_money_100] > **不使用seata** > > 访问:[http://localhost:6001/order/order/createNoSeata?userId=1&productId=1&count=10&money=100][http_localhost_6001_order_order_createNoSeata_userId_1_productId_1_count_10_money_100] > **feign里边再调feign** > > [http://localhost:6001/order/order/create2?userId=1&productId=1&count=10&money=100][http_localhost_6001_order_order_create2_userId_1_productId_1_count_10_money_100] [--Seata--AT_starter_feign_IT_-CSDN]: https://knife.blog.csdn.net/article/details/112600939 [Seata]: http://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html [--Seata AT_starter_feign_feiying0canglang_-CSDN]: https://blog.csdn.net/feiying0canglang/article/details/111867531 [springboot_seata_mybatisplus_springcloudalibaba_demo_star]: https://gitee.com/itCjb/spring-cloud-alibaba-seata-demo [SpringCloud_seata_Docker_seata_ _]: https://www.codenong.com/cs109182278/ [https_gitee.com_shapeless_demo_SpringCloud_tree_seata-at-2]: https://gitee.com/shapeless/demo_SpringCloud/tree/seata-at-2 [https_github.com_seata_seata_releases]: https://github.com/seata/seata/releases [http_localhost_6001_order_order_create_userId_1_productId_1_count_10_money_100]: http://localhost:6001/order/order/create?userId=1&productId=1&count=10&money=100 [http_localhost_6001_order_order_createNoSeata_userId_1_productId_1_count_10_money_100]: http://localhost:6001/order/order/createNoSeata?userId=1&productId=1&count=10&money=100 [http_localhost_6001_order_order_create2_userId_1_productId_1_count_10_money_100]: http://localhost:6001/order/order/create2?userId=1&productId=1&count=10&money=100
相关 【分布式事务】分布式事务Seata 文章目录 前言 什么是分布式事务? (1)数据库分库分表就产生了分布式事务; (2)项目拆分服务化也产生了分布式事务; 一、W 客官°小女子只卖身不卖艺/ 2023年10月09日 13:49/ 0 赞/ 53 阅读
相关 分布式事务--Seata-AT--SpringCloud(starter版) 原文网址:[分布式事务--Seata--AT(starter)(feign)\_IT利刃出鞘的博客-CSDN博客][--Seata--AT_starter_feign_IT_- 青旅半醒/ 2023年01月06日 05:48/ 0 赞/ 170 阅读
相关 分布式-分布式事务 分布式事务 文章目录 分布式事务 一,本地消息表 二,2PC 两阶段提交 三,3PC 三段式提交 四,T 待我称王封你为后i/ 2022年11月27日 15:40/ 0 赞/ 317 阅读
相关 分布式事务01-分布式事务概述 文章目录 1.分布式事务产生的背景 1.1 分布式事务在不同场景下如何产生 1.2 案例 2.事务理论知识 2.1 AC 旧城等待,/ 2022年04月25日 09:54/ 0 赞/ 374 阅读
相关 分布式事务 一、前言 在单个数据库实例时候,我们可以使用一个数据源的事务([本地事务][Link 1] )来保证事务内的多个操作要么全部执行生效,要么全部不生效。在多数据库实例节点时 冷不防/ 2022年04月14日 02:09/ 0 赞/ 394 阅读
相关 分布式事务 什么是分布式事务 分布式事务涉及到操作多个数据库的事务,分布式事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的`不同节点上`。一个分布式事务可 Dear 丶/ 2022年03月10日 15:27/ 0 赞/ 267 阅读
相关 分布式事务 1、分布式事务产生的背景 在微服务环境下,因为会根据不同的业务会拆分成不同的服务,比如会员服务、订单服务、商品服务等,让专业的人做专业的事情,每个服务都有自己独立的数据库,并 - 日理万妓/ 2021年10月25日 14:09/ 0 赞/ 470 阅读
相关 分布式事务 阅读: 深入理解分布式事务,高并发下分布式事务的解决方案 [https://blog.csdn.net/qq\_32534441/article/details/890 红太狼/ 2021年10月13日 01:13/ 0 赞/ 503 阅读
相关 分布式事务 在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的 CRUD 等语义,我们需... 灰太狼/ 2020年04月24日 17:42/ 0 赞/ 978 阅读
还没有评论,来说两句吧...