上一节讲述了seata分布式事务设计到的业务表、业务表对应数据库需要seata的表以及商品微服务的代码。本节继续上一节未未完成剩余的两个微服务的描述。
用户账户微服务Account
pom.xml、application.yml、application-dev.yml、MybatisConfig、主函数相关代码和微服务Goods几乎一样,不在贴代码列举。
其中application.yml中server.port端口和spring.application.name应用名称根据自己的情况更改,上图是我这边配置。
新增一个AccountController类,增加一个减少账户余额的请求实现。如下:
@PostMapping("/user/reduceBalance")
public String balance(@RequestParam("userid") String userid, @RequestParam("cost") String cost) throws Exception {
boolean isSuccess = accountService.reduceBalance(userid,cost);
Map<String,String> map= new HashMap<>(8);
map.put("code",isSuccess?"ok":"fail");
map.put("msg",isSuccess?"成功":"失败");
return jsonUtil.objectToJson(map);
}
accountService的核心代码
public boolean reduceBalance(String userid, String cost) throws Exception {
logger.info("事务XID:{}", RootContext.getXID());
UserAccount userAccount = userAccountMapper.selectByPrimaryKey(Long.parseLong(userid));
if (userAccount.getAccount()<Long.parseLong(cost)){
throw new Exception("余额不足");
}
int row = userAccountMapper.reduceBalance(userAccount.getUserid(),Long.parseLong(cost));
return row>0 ? true:false;
}
UserAccountMapper.xml中减少账户余额核心的sql
<update id="reduceBalance" parameterType="java.lang.Long">
update user_account
set account = account - #{cost,jdbcType=BIGINT}
where userid = #{userid,jdbcType=BIGINT} AND account > #{cost,jdbcType=BIGINT}
</update>
订单微服务Order
pom.xml、application.yml、application-dev.yml、MybatisConfig、主函数相关代码和微服务Goods几乎一样,不在贴代码列举。
application.yml中server.port端口和spring.application.name应用名称根据自己的情况更改,上图是我这边配置。
pom.xml在原来的基础上增加openfeign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
因openfeign内部使用了ribbon实现,默认超时时间为1秒,为了避免超时的情况,更改超时时间。设置为10000毫秒。application.yml中增加配置如下
如果想了解那里设置了默认时间,请查看RibbonClientConfiguration类,如下:
增加AccountFeign接口,调用用户账户相关请求。如下:
@Service
@FeignClient("ACCOUNT")
public interface AccountFeign {
@PostMapping("/user/reduceBalance")
public String balance(@RequestParam("userid") String userid, @RequestParam("cost") String cost);
@GetMapping("/user/{id}")
public String userAccount(@PathVariable("id") Long id);
}
注意:这里的增加了FeignClient注解,值“ACCOUNT”为用户账户微服务(ACCOUN)中spring.application.name的值。
增加GoodsFeign接口,调用商品相关请求。如下:
@Service
@FeignClient("GOODS")
public interface GoodsFeign {
@GetMapping("/goods/{id}")
public String goodsInfo(@PathVariable(value = "id") String id);
@PostMapping("/goods/deductStorage")
public String deductStorage(@RequestParam(value="id",defaultValue = "0") String id,@RequestParam(value="count",defaultValue = "0") String count);
}
新增一个OrderController的类,增加创建订单请求代码。如下:
@PostMapping("/order/createOrder")
public String createOrder(@RequestParam(value="userId",defaultValue = "") String userId,
@RequestParam(value="commodityCode",defaultValue = "")String commodityCode,
@RequestParam(value="count",defaultValue = "")String count) throws Exception {
Order order=orderService.saveOrder(userId,commodityCode,count);
return jsonUtil.objectToJson(order);
}
OrderService核心代码
@Resource
OrderMapper orderMapper;
@Resource
GoodsFeign goodsFeign;
@Resource
AccountFeign accountFeign;
@GlobalTransactional(name = "createOrder",timeoutMills = 120000,rollbackFor = Exception.class)
@Override
public Order saveOrder(String userId, String commodityCode, String count) throws Exception {
logger.info("------用户下单---------");
logger.info("事务XID:{}", RootContext.getXID());
//保存订单
Order order = new Order();
order.setUserid(Long.parseLong(userId));
order.setCommoditycode(Long.parseLong(commodityCode));
order.setNum(Long.parseLong(count));
//获取商品信息以及库存信息
String jsonGoods=goodsFeign.goodsInfo(commodityCode);
Map<String,Object> map=jsonUtil.jsonToMap(jsonGoods);
Long price = Long.parseLong(map.getOrDefault("price",0L).toString());
Long money = price*Long.parseLong(count);
order.setMoney(money);
//更新库存信息
goodsFeign.deductStorage(commodityCode,count);
//扣减余额
accountFeign.balance(userId,money.toString());
//初始化订单状态
order.setStatus(1L);
int insertRow = orderMapper.insertSelective(order);
logger.info("保存订单id:{}{}", order.getOrderid(), insertRow>0?"成功":"失败");
//int updateRow = orderMapper.updateStatus(Long.parseLong(String.valueOf(insertRow)),1L);
//logger.info("更新订单id:{} {}",order.getOrderid(),updateRow>0?"成功":"失败");
return order;
}
注意,这个使用了GlobalTransactional注解,增加了全局分布式事务,设置timeoutMills超时时间为120秒(因上文设置ribbon的超时时间为10秒,只要所有openfeign不超过这个时间就行),所有的异常都回滚。
OrderMapper.xml中创建订单核心的sql
<insert id="insertSelective" parameterType="com.juwusheng.order.model.Order" >
insert into `order`
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="orderid != null" >
orderid,
</if>
<if test="commoditycode != null" >
commoditycode,
</if>
<if test="userid != null" >
userId,
</if>
<if test="num != null" >
num,
</if>
<if test="money != null" >
money,
</if>
<if test="status != null" >
status,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="orderid != null" >
#{orderid,jdbcType=BIGINT},
</if>
<if test="commoditycode != null" >
#{commoditycode,jdbcType=BIGINT},
</if>
<if test="userid != null" >
#{userid,jdbcType=BIGINT},
</if>
<if test="num != null" >
#{num,jdbcType=BIGINT},
</if>
<if test="money != null" >
#{money,jdbcType=BIGINT},
</if>
<if test="status != null" >
#{status,jdbcType=BIGINT},
</if>
</trim>
</insert>
启动程序
依次启动商品微服务Goods、用户账户微服务Account和订单微服务Order程序。查看nacos服务列表,有4个服务。
发送post请求http://localhost:7001/order/createOrder?userId=1&commodityCode=1&count=9
userd为1用户只有100块,余额不足,购买失败,订单表数据为空,事务回滚成功。查看日志如下
2022-04-10 23:39:28.024 INFO 18939 --- [nio-7002-exec-2] c.j.goods.service.impl.GoodsServiceImpl : 事务XID:192.168.0.105:8091:256565428548440064
2022-04-10 23:39:29.757 INFO 18939 --- [ch_RMROLE_1_2_8] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=192.168.0.105:8091:256565428548440064,branchId=256565446432952321,branchType=AT,resourceId=jdbc:mysql://120.25.151.44:3306/seata_order,applicationData=null
2022-04-10 23:39:29.761 INFO 18939 --- [ch_RMROLE_1_2_8] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 192.168.0.105:8091:256565428548440064 256565446432952321 jdbc:mysql://120.25.151.44:3306/seata_order
2022-04-10 23:39:30.008 INFO 18939 --- [ch_RMROLE_1_2_8] i.s.r.d.undo.AbstractUndoLogManager : xid 192.168.0.105:8091:256565428548440064 branch 256565446432952321, undo_log deleted with GlobalFinished
2022-04-10 23:39:30.046 INFO 18939 --- [ch_RMROLE_1_2_8] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
2022-04-10 23:39:30.124 INFO 18939 --- [ch_RMROLE_1_3_8] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=192.168.0.105:8091:256565428548440064,branchId=256565444398714880,branchType=AT,resourceId=jdbc:mysql://120.25.151.44:3306/seata_order,applicationData=null
2022-04-10 23:39:30.124 INFO 18939 --- [ch_RMROLE_1_3_8] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 192.168.0.105:8091:256565428548440064 256565444398714880 jdbc:mysql://120.25.151.44:3306/seata_order
2022-04-10 23:39:30.207 INFO 18939 --- [ch_RMROLE_1_3_8] i.s.r.d.undo.AbstractUndoLogManager : xid 192.168.0.105:8091:256565428548440064 branch 256565444398714880, undo_log added with GlobalFinished
2022-04-10 23:39:30.246 INFO 18939 --- [ch_RMROLE_1_3_8] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
请求参数count改为1时,发送请求
http://localhost:7001/order/createOrder?userId=1&commodityCode=1&count=1
下单成功,返回结果