1 Lead to distributed transaction issues
1.1 seata-service-account is used to write interfaces for querying users and remotely calling orders.
@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 is written to place an order and remotely call the inventory reduction interface
@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 writes inventory reduction interface
@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 Introducing distributed transactions
When we close the seata-service-storage service and access the /accountTbl/insertOrder interface, we will find that the order was successfully placed, but the inventory was not deducted, so we need to introduce distributed transactions to globally manage some cross-service database modification operations.
2 Integrate Seata to solve distributed transaction problems
2.1 Add undo_log table to each data data
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 Add seata dependency to parent pom
<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 Add yml configuration to each module
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 Add the @GlobalTransactional annotation to the AccountTblController interface at the beginning of the call chain
2.5 Manually implement xid transfer
Manually implement xid transfer. In fact, cloud alibaba has implemented automatic transfer, but the new version of openfeign has removed many components. You need to implement it yourself, add configuration classes, and remove automatic configuration again.
/**
* @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 Start each project, access the interface, and submit the global transaction successfully
2.7 Simulating exceptions
When the account is rolled back due to an exception, there are no order and inventory data inconsistencies, and the global transaction is rolled back.