1 引出分布式事务问题
1.1 seata-service-account编写查询用户、远程调用下订单接口
@RestController
@RequestMapping("/accountTbl")
public class AccountTblController {
@Autowired
AccountTblMapper accountTblMapper;
@Autowired
OrderFeign orderFeign;
@GetMapping("/insertOrder")
public Object insertOrder() {
// 查询用户
AccountTbl accountTbl = accountTblMapper.selectById("11111111");
// 下单
Object order = orderFeign.insertOrder(accountTbl.getUserId(), "iphone11", 1, 1);
// 修改余额
accountTbl.setMoney(accountTbl.getMoney() - 1);
accountTblMapper.updateById(accountTbl);
return order;
}
}
@FeignClient(name = "seata-service-order")
public interface OrderFeign {
@GetMapping("orderTbl/insertOrder")
Object insertOrder(@RequestParam String userId,@RequestParam String commodityCode,@RequestParam int count,@RequestParam int money);
}
1.2 seata-service-order编写下订单,远程调用减库存接口
@RestController
@RequestMapping("/orderTbl")
public class OrderTblController {
@Autowired
OrderTblMapper orderTblMapper;
@Autowired
StorageFeign storageFeign;
@GetMapping("insertOrder")
public Object insertOrder(String userId, String commodityCode, int count, int money) {
// 下定单
OrderTbl orderTbl = new OrderTbl();
orderTbl.setUserId(userId);
orderTbl.setCommodityCode(commodityCode);
orderTbl.setCount(count);
orderTbl.setMoney(1);
// 下订单扣库存
orderTblMapper.insert(orderTbl);
Object storage = storageFeign.updateStorage(commodityCode, count);
return orderTbl;
}
}
@FeignClient(name = "seata-service-storage")
public interface StorageFeign {
@GetMapping("/storageTbl/updateStorage")
Object updateStorage(@RequestParam String commodityCode,@RequestParam int count);
}
1.3 seata-service-storage编写减库存接口
@RestController
@RequestMapping("/storageTbl")
public class StorageTblController {
@Autowired
StorageTblMapper storageTblMapper;
@GetMapping("updateStorage")
Object updateStorage(String commodityCode, int count){
// 查询库存
StorageTbl storageTbl = storageTblMapper.selectOne(new LambdaQueryWrapper<StorageTbl>().eq(StorageTbl::getCommodityCode, commodityCode));
// 减去库存更新
storageTbl.setCount(storageTbl.getCount()-count);
int i = storageTblMapper.updateById(storageTbl);
return storageTbl;
}
}
1.4 引出分布式事务
当我们关闭seata-service-storage服务,访问/accountTbl/insertOrder接口,会发现下单成功了,但是未扣库存,所以需要引入分布式事务,对一些跨服务的改库操作进行全局管理
2 集成Seata解决分布式事务问题
2.1 各个数据据添加undo_log表
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
2.2 父pom添加seata依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
2.3 各模块添加yml配置
seata:
enabled: true
application-id: seata-service-account # 添加为服务名
tx-service-group: my_test_tx_group
config:
type: nacos
nacos:
namespace:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
2.4 调用链开端AccountTblController接口添加@GlobalTransactional注解
2.5 手动实现xid传递
手动实现xid传递,实际cloud alibaba通过已实现了自动传递,但是新版openfeign移除了很多组件,需要自己实现,添加配置类,并重新去掉自动配置
/**
* @author wuKeFan
* @date 2020/11/27
*/
@Component
@ConditionalOnClass({
RequestInterceptor.class, GlobalTransactional.class})
public class SeataRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String currentXid = RootContext.getXID();
if (StrUtil.isNotBlank(currentXid) && !template.url().startsWith(Auth.CHECK_TOKEN_URI) && !template.url().startsWith(Auth.CHECK_RBAC_URI)) {
template.header(RootContext.KEY_XID, currentXid);
}
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
}
}
@SpringBootApplication(exclude = {
SeataFeignClientAutoConfiguration.class})
2.6 启动各个项目,访问接口,全局事务提交成功
2.7 模拟异常
当账户因为异常回滚后,并没有订单及库存数据不一致,全局事务回滚