概念 分布式事务处理过程的-ID+三组件模型
Transaction ID XID 全局唯一的事务ID
3组件概念
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
2.XID在微服务调用链路的上下文中传播
3.RM向TC注册分支事务,将其耐入XID对应的全局事务的管辖
4.TM向TC发起针对XID的全局提交或回滚决议
5.TC调度XID下管辖的全部分支事务完成提交或回滚请求
Seta Server下载配置 下载 http://seata.io/zh-cn/blog/download.htmlj
选择版本点击source
下载即可
我下载的是1.0.0版本
安装 下载完成只后直接解压即可,得到目录
配置 进入到conf文件夹,先备份原始file.conf
文件为file - 副本.conf
service 接着修改file.conf
中的service模块将vgroup_mapping.my_test_tx_group = "default"
中的default修改为kylin_tx_group
1 2 3 4 5 6 7 8 9 service { #transaction service group mapping #vgroup_mapping.my_test_tx_group = "default" vgroup_mapping.my_test_tx_group = "kylin_tx_group" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #disable seata disableGlobalTransaction = false }
store 接着修改store模块mode = "file"
file改为db
,同时配置好自己的mysql用户名和密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 service { #transaction service group mapping #vgroup_mapping.my_test_tx_group = "default" vgroup_mapping.my_test_tx_group = "kylin_tx_group" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #disable seata disableGlobalTransaction = false } ## transaction log store, only used in seata-server store { ## store mode: file、db mode = "db" ## file store property file { ## store location dir dir = "sessionStore" } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "kylin" } }
数据库 新建seata
数据库
运行建表sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 drop table if exists `global_table`;create table `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`) ); drop table if exists `branch_table`;create table `branch_table` ( `branch_id` bigint not null , `xid` varchar (128 ) not null , `transaction_id` bigint , `resource_group_id` varchar (32 ), `resource_id` varchar (256 ) , `lock_key` varchar (128 ) , `branch_type` varchar (8 ) , `status` tinyint, `client_id` varchar (64 ), `application_data` varchar (2000 ), `gmt_create` datetime, `gmt_modified` datetime, primary key (`branch_id`), key `idx_xid` (`xid`) ); drop table if exists `lock_table`;create table `lock_table` ( `row_key` varchar (128 ) not null , `xid` varchar (96 ), `transaction_id` long , `branch_id` long, `resource_id` varchar (256 ) , `table_name` varchar (32 ) , `pk` varchar (36 ) , `gmt_create` datetime , `gmt_modified` datetime, primary key(`row_key`) );
registry.conf 将conf目录下的registry.conf备份registry - 副本.conf
后修改,目的是:指明注册中心为nacos,及修改nacos连接信息
测试 先启动Nacos,在从bin目录下点击seata-server.bat
运行Seta Server
运行成功!
数据库准备 seata_order: 存储订单的数据库
seata_storage:存储库存的数据库
seata_account: 存储账户信息的数据库
1 2 3 4 5 CREATE DATABASE seata_order;CREATE DATABASE seata_storage; CREATE DATABASE seata_account;
seata_order
库下建t_order
表
1 2 3 4 5 6 7 8 9 10 CREATE TABLE t_order( `id` BIGINT (11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY, `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 '金额' , `status` INT (1 ) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结' ) ENGINE= INNODB AUTO_INCREMENT= 7 DEFAULT CHARSET= utf8; SELECT * FROM t_order;
seata_storage
库下建t_storage
表
1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE TABLE t_storage( `id` BIGINT (11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY, `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 '剩余库存' ) ENGINE= INNODB AUTO_INCREMENT= 2 DEFAULT CHARSET= utf8; INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES ('1' ,'1' ,'100' ,'0' ,'100' ); SELECT * FROM t_storage;
seata_account
库下建t_account
表
1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE t_account( `id` BIGINT (11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY 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 '剩余可用额度' ) ENGINE= INNODB AUTO_INCREMENT= 2 DEFAULT CHARSET= utf8; INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES ('1' ,'1' ,'1000' ,'0' ,'1000' );SELECT * FROM t_account;
按照上述3库分别建对应的回滚日志表db_undo_log.sql
1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE TABLE `undo_log` ( `id` bigint (20 ) NOT NULL AUTO_INCREMENT, `branch_id` bigint (20 ) NOT NULL , `xid` varchar (100 ) NOT NULL , `context` varchar (128 ) NOT NULL , `rollback_info` longblob NOT NULL , `log_status` int (11 ) NOT NULL , `log_created` datetime NOT NULL , `log_modified` datetime NOT NULL , `ext` varchar (100 ) DEFAULT NULL , PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET= utf8;
环境搭建 下订单->减库存->扣余额->改(订单)状态
新建订单Order-Module 创建模块seata-order-service2001
pom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > <exclusions > <exclusion > <artifactId > seata-all</artifactId > <groupId > io.seata</groupId > </exclusion > </exclusions > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-all</artifactId > <version > 1.0.0</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.37</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.10</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies >
yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: tx-service-group: kylin_tx_group nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order username: root password: kylin feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
file.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { vgroup_mapping.kylin_tx_group = "default" #修改自定义事务组名称 default.grouplist = "127.0.0.1:8091" enableDegrade = false disable = false max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" disableGlobalTransaction = false } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } ## transaction log store store { ## store mode: file、db mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "kylin" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { ## the lock store mode: local、remote mode = "remote" local { ## store locks in user's database } remote { ## store locks in the seata's server } } recovery { #schedule committing retry period in milliseconds committing-retry-period = 1000 #schedule asyn committing retry period in milliseconds asyn-committing-retry-period = 1000 #schedule rollbacking retry period in milliseconds rollbacking-retry-period = 1000 #schedule timeout retry period in milliseconds timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } ## metrics settings metrics { enabled = false registry-type = "compact" # multi exporters use comma divided exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }
registry.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } 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 = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
domain CommonResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult <T > { private Integer code; private String message; private T data; public CommonResult (Integer code, String message) { this (code,message,null ); } }
Order
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data @AllArgsConstructor @NoArgsConstructor public class Order { private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; }
Dao OrderDao
1 2 3 4 5 6 7 8 @Mapper public interface OrderDao { void create (Order order) ; void update (@Param("userId") Long userId, @Param("status") Integer status) ; }
resources文件夹下新建mapper文件夹后添加OrderMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.kylin.dao.OrderDao" > <resultMap id ="BaseResultMap" type ="com.kylin.domain.Order" > <id column ="id" property ="id" jdbcType ="BIGINT" /> <result column ="user_id" property ="userId" jdbcType ="BIGINT" /> <result column ="product_id" property ="productId" jdbcType ="BIGINT" /> <result column ="count" property ="count" jdbcType ="INTEGER" /> <result column ="money" property ="money" jdbcType ="DECIMAL" /> <result column ="status" property ="status" jdbcType ="INTEGER" /> </resultMap > <insert id ="create" > insert into t_order (id,user_id,product_id,count,money,status) values (null,#{userId},#{productId},#{count},#{money},0); </insert > <update id ="update" > update t_order set status = 1 where user_id=#{userId} and status = #{status}; </update > </mapper >
Service OrderService
1 2 3 public interface OrderService { void create (Order order) ; }
OrderServiceImpl
实现接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private StorageService storageService; @Resource private AccountService accountService; @Override @GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class) public void create (Order order) { log.info("----->开始新建订单" ); orderDao.create(order); log.info("----->订单微服务开始调用库存,做扣减Count" ); storageService.decrease(order.getProductId(), order.getCount()); log.info("----->订单微服务开始调用库存,做扣减end" ); log.info("----->订单微服务开始调用账户,做扣减Money" ); accountService.decrease(order.getUserId(), order.getMoney()); log.info("----->订单微服务开始调用账户,做扣减end" ); log.info("----->修改订单状态开始" ); orderDao.update(order.getUserId(), 0 ); log.info("----->修改订单状态结束" ); log.info("----->下订单结束了,O(∩_∩)O哈哈~" ); } }
StorageService
1 2 3 4 5 @FeignClient(value = "seata-storage-service") public interface StorageService { @PostMapping(value = "/storage/decrease") CommonResult decrease (@RequestParam("productId") Long productId, @RequestParam("count") Integer count) ; }
AccountService
1 2 3 4 5 @FeignClient(value = "seata-account-service") public interface AccountService { @PostMapping(value = "/account/decrease") CommonResult decrease (@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) ; }
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class OrderController { @Resource private OrderService orderService; @GetMapping("/order/create") public CommonResult create (Order order) { orderService.create(order); return new CommonResult(200 , "订单创建成功" ); } }
Config MyBatisConfig
1 2 3 4 @Configuration @MapperScan({"com.kylin.dao"}) public class MyBatisConfig {}
DataSourceProxyConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import com.alibaba.druid.pool.DruidDataSource;import io.seata.rm.datasource.DataSourceProxy;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.transaction.SpringManagedTransactionFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;@Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource () { return new DruidDataSource(); } @Bean public DataSourceProxy dataSourceProxy (DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean (DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
主启动类
1 2 3 4 5 6 7 8 9 @EnableDiscoveryClient @EnableFeignClients @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class SeataOrderMainApp2001 { public static void main (String[] args) { SpringApplication.run(SeataOrderMainApp2001.class, args); } }
新建库存Storage-Module 创建模块seata-order-service2002
pom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-seata</artifactId > <exclusions > <exclusion > <artifactId > seata-all</artifactId > <groupId > io.seata</groupId > </exclusion > </exclusions > </dependency > <dependency > <groupId > io.seata</groupId > <artifactId > seata-all</artifactId > <version > 1.0.0</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <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.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.37</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.10</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies >
yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 server: port: 2002 spring: application: name: seata-storage-service cloud: alibaba: seata: tx-service-group: kylin_tx_group nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_storage username: root password: kylin logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
file.conf 保持一致
registry.conf 保持一致
domain CommonResult一致
Storage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Data public class Storage { private Long id; private Long productId; private Integer total; private Integer used; private Integer residue; }
Dao StorageDao
1 2 3 4 5 6 @Mapper public interface StorageDao { void decrease (@Param("productId") Long productId, @Param("count") Integer count) ; }
resources文件夹下新建mapper文件夹后添加StorageMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.kylin.dao.StorageDao" > <resultMap id="BaseResultMap" type="com.kylin.domain.Storage" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="product_id" property="productId" jdbcType="BIGINT" /> <result column="total" property="total" jdbcType="INTEGER" /> <result column="used" property="used" jdbcType="INTEGER" /> <result column="residue" property="residue" jdbcType="INTEGER" /> </resultMap> <update id="decrease" > UPDATE t_storage SET used = used + #{count},residue = residue - #{count} WHERE product_id = #{productId} </update> </mapper>
Service StorageServicej接口
1 2 3 4 5 6 public interface StorageService { void decrease (Long productId, Integer count) ; }
实现类StorageServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import com.kylin.dao.StorageDao;import com.kylin.service.StorageService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service public class StorageServiceImpl implements StorageService { private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class); @Resource private StorageDao storageDao; @Override public void decrease (Long productId, Integer count) { LOGGER.info("------->storage-service中扣减库存开始" ); storageDao.decrease(productId,count); LOGGER.info("------->storage-service中扣减库存结束" ); } }
Controller StorageController
Config 保持一致
主启动类
1 2 3 4 5 6 7 8 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class SeataStorageServiceApplication2002 { public static void main (String[] args) { SpringApplication.run(SeataStorageServiceApplication2002.class, args); } }
新建账户Account-Module 创建seata-order-service2003
pom 保持一致
yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server: port: 2003 spring: application: name: seata-account-service cloud: alibaba: seata: tx-service-group: kylin_tx_group nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_account username: root password: kylin feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
file.conf 保持一致
registry.conf 保持一致
domain CommonResult一致
Account
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; /** * 用户id */ private Long userId; /** * 总额度 */ private BigDecimal total; /** * 已用额度 */ private BigDecimal used; /** * 剩余额度 */ private BigDecimal residue; }
Dao AccountDao
1 2 3 4 5 6 7 8 @Mapper public interface AccountDao { void decrease (@Param("userId") Long userId, @Param("money") BigDecimal money) ; }
resources文件夹下新建mapper文件夹后添加AccountMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.kylin.dao.AccountDao" > <resultMap id ="BaseResultMap" type ="com.kylin.domain.Account" > <id column ="id" property ="id" jdbcType ="BIGINT" /> <result column ="user_id" property ="userId" jdbcType ="BIGINT" /> <result column ="total" property ="total" jdbcType ="DECIMAL" /> <result column ="used" property ="used" jdbcType ="DECIMAL" /> <result column ="residue" property ="residue" jdbcType ="DECIMAL" /> </resultMap > <update id ="decrease" > UPDATE t_account SET residue = residue - #{money},used = used + #{money} WHERE user_id = #{userId}; </update > </mapper >
Service AccountService
1 2 3 4 5 6 7 8 public interface AccountService { void decrease (@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) ; }
AccountServiceImpl实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service public class AccountServiceImpl implements AccountService { private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class); @Resource AccountDao accountDao; @Override public void decrease (Long userId, BigDecimal money) { LOGGER.info("------->account-service中扣减账户余额开始" ); try { TimeUnit.SECONDS.sleep(20 ); } catch (InterruptedException e) { e.printStackTrace(); } accountDao.decrease(userId, money); LOGGER.info("------->account-service中扣减账户余额结束" ); } }
Controller AccountController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController public class AccountController { @Resource AccountService accountService; @RequestMapping("/account/decrease") public CommonResult decrease (@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) { accountService.decrease(userId,money); return new CommonResult(200 ,"扣减账户余额成功!" ); } }
Config 保持一致
主启动类 SeataAccountMainApp2003
1 2 3 4 5 6 7 8 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class SeataAccountMainApp2003 { public static void main (String[] args) { SpringApplication.run(SeataAccountMainApp2003.class, args); } }
测试 访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
AccountServiceImpl添加超时
重新启动访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
当库存和账户余额扣减后,订单状态并没有设置为已经完成,没有从零改为1,而且由于feign的重试机制,账户余额还有可能被多次扣减。
使用@GlobalTransactional
再次测试http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
下单后数据库数据并没有任何改变。记录都添加不进来