准备父子模块
分别是订单服务和库存服务,要实现一个功能,增加一个订单成功后删除对应产品的库存,基于以下两个接口实现
订单接口:
@RestController
@RequestMapping("orders")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersController {
private final OrdersService service;
@PostMapping("add")
public Object add() {
return service.add();
}
}
// 这是实际业务方法
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
implements OrdersService {
private final RestTemplate restTemplate;
@Override
public Object add() {
Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
restTemplate.getForObject("http://localhost:9001/stock/reduct?productId=" + orders.getProductId(), String.class);
return orders.getId();
}
}
可以看到就是增加订单时会调用库存微服务的/stock/reduct接口
库存接口:
@RestController
@RequestMapping("stock")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class StockController {
private final StockService service;
@GetMapping("reduct")
public Object reduct(Long productId) {
return service.reduct(productId);
}
}
// 这是实际业务方法
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock>
implements StockService {
@Override
public Boolean reduct(Long productId) {
return this.baseMapper.reduct(productId);
}
}
请不要在意此两个接口是否符合规范,目前的效果是调用订单接口对应的库存会减1,这时我测试一下本地事务效果。
修改一下订单接口:
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
implements OrdersService {
private final RestTemplate restTemplate;
@Override
@Transactional
public Object add() {
Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
restTemplate.getForObject("http://localhost:9001/stock/reduct?productId=" + orders.getProductId(), String.class);
int i = 1 / 0;
return orders.getId();
}
}
加上本地事务并且让程序出错,手动清空本地订单表,设置产品对应库存是100,如果本地事务成功的话,订单表会增加一条数据,并且库存会减1变成99
结果是:订单表没有生成数据,但是对应产品的库存减1了,说明本地事务只对单一服务生效
将上面两个服务进行改造
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>oliver</groupId>
<artifactId>database</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>
spring:
application:
name: order-seata-service
cloud:
nacos:
server-addr: localhost:8848
# 如果开启了权限,账号密码必须要显式写清
username: nacos
password: nacos
spring:
application:
name: stock-seata-service
cloud:
nacos:
server-addr: localhost:8848
# 如果开启了权限,账号密码必须要显式写清
username: nacos
password: nacos
@FeignClient(value = "stock-seata-service", path = "/stock")
public interface StockService {
@GetMapping("reduct")
Object reduct(@RequestParam("productId") Long productId);
}
Order服务的新建订单方法改为:
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
implements OrdersService {
private final StockService stockService;
@Override
public Object add() {
Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
this.baseMapper.insert(orders);
stockService.reduct(orders.getProductId());
//int i = 1 / 0;
return orders.getId();
}
}
记得启动类开启OpenFeign
@EnableFeignClients
弄完之后我调用订单服务的add接口报错:不支持接口类型:【POST】,原因是Feign使用对象作为参数默认会将GET请求转成POST请求,导致调用库存时请求类型不同,在Feign接口填参数时加上@RequestParam,被调用方的参数前也要加上@RequestParam
在要使用到Seata的每个数据库中都增加一个表
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;
此表用于记录undo信息,用于回滚
seata:
enabled: true
enable-auto-data-source-proxy: true
# 配置事务组
tx-service-group: my_test_tx_group
registry:
# 用于告诉Client如何访问Seata Server
type: nacos
nacos:
server-addr: localhost:8848
# seata服务端的服务名
application: seata-server
username: nacos
password: nacos
# seata服务端的分组名称
group: SEATA_GROUP
namespace: seata-server
# 配置Seata服务端的配置中心,用于读取关于Seata Client的一些配置
config:
# 用于告诉Client如何访问Seata Server
type: nacos
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
# seata服务端的分组名称
group: SEATA_GROUP
namespace: seata-server
service:
vgroup-mapping:
my_test_tx_group: default
disable-global-transaction: false
client:
rm:
report-success-enable: true
这里也要配置事务分组,就是服务端配置中的config.txt里的第一条配置
在增加订单接口逻辑上增加@GlobalTransactional并且测试
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
implements OrdersService {
private final StockService stockService;
@Override
@GlobalTransactional
public Object add() {
Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
this.baseMapper.insert(orders);
stockService.reduct(orders.getProductId());
return orders.getId();
}
}
发现分布式事务已经生效