手把手带你一起搭建Seata,结合SpringCloud alibaba实战(二)
- 前言
- 具体实现
- 大致流程
- 配置微服务
- 订单服务
- 库存服务
- 测试
- 订单服务异常
- 库存服务异常
- 总结
接下来的一段时间论文解说要暂时放一放,咱们一起来了解下微服务方面的知识,欢迎大家来微服务专栏逛一逛,后续会慢慢更新使用教程以及源码等博客。感谢大家的支持!
前言
前面的博客手把手带你一起搭建Seata,结合SpringCloud alibaba实战(一)里咱们已经聊过了怎么搭建Seata,解析来我们看看SpringCloud Alibaba怎么结合Seata。
具体实现
大致流程
首先,我们需要将微服务与Seata连接一起。为了测试微服务之间的事务,我们需要两个微服务,即订单服务和库存服务。测试案例就是我们需要添加订单信息,然后需要减少库存,添加订单信息是在订单服务中完成的,减少库存则是在库存服务中完成的。
我们希望,两服务中一旦一方出现问题,就需要一起回滚。
需要注意的是,由于篇幅原因,接下来的代码中并不会列出数据库相关操作的代码,博主这里直接用的Mybaits-plus,大家可以试着写写,如果需要完整代码可以联系博主。
配置微服务
既然我们要连接Seata,那我们肯定首先需要配置Seata的ip啥的,对吧。
但是之前手把手带你一起搭建Seata,结合SpringCloud alibaba实战(一)提到了将Seata注册到Nacos中了,所以我们的配置就变得简单了,我们需要在订单和库存服务的application.yml中都添加以下配置。
seata:
registry:
type: nacos
nacos:
# Seata注册的应用名
application: seata-server
group: SEATA_GROUP
# Nacos的ip和端口号
server-addr: 192.168.241.139:8849
config:
type: nacos
nacos:
# Nacos的ip和端口号
server-addr: 192.168.241.139:8849
group: SEATA_GROUP
订单服务
Seata用起来也很简单,也就是直接加上@GlobalTransactional注解就可以了。这个接口是订单的接口,用于添加订单信息。
@GlobalTransactional
@PostMapping("addOrder")
public Result addOrder(Long id) {
orderinfoService.save(new Orderinfo().setName(String.format("订单商品id:%s", id)));
// 这里以OpenFegin的方式调用库存的接口
Result reducestock = stockService.reducestock(id);
int i = 1 / 0;
return Result.succ("下单成功");
}
用于调用库存服务接口的OpenFegin
package com.xiaow.fegin;
import com.xiaow.common.vo.Result;
import com.xiaow.fegin.impl.StockServiceImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "stock",fallback = StockServiceImpl.class)
public interface StockService {
@PostMapping("/stockinfo/reducestock")
Result reducestock(@RequestParam("id") Long id);
}
用来处理调用异常的StockServiceImpl
package com.xiaow.fegin.impl;
import com.xiaow.common.vo.Result;
import com.xiaow.fegin.StockService;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransactionContext;
import org.springframework.stereotype.Component;
/**
* @ClassName StockService
* @Author xiaow
* @DATE 2024/4/10 17:20
**/
// 降级的类
@Component
public class StockServiceImpl implements StockService {
@Override
public Result reducestock(Long id) {
return Result.fail("当前下单过于火爆,请稍后再试");
}
}
库存服务
库存的接口,用于减少商品的库存
package com.xiaow.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaow.common.vo.Result;
import com.xiaow.entity.Stockinfo;
import com.xiaow.service.StockinfoService;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigInteger;
import java.util.List;
/**
* <p>
* 前端控制器
* </p>
*
* @author xiaow
* @since 2024-04-24
*/
@RestController
@RequestMapping("/stockinfo")
public class StockinfoController {
@Autowired
StockinfoService stockinfoService;
@GetMapping("list")
public Result list() {
List<Stockinfo> list = stockinfoService.list();
return Result.succ(list);
}
@PostMapping("reducestock")
public Result reducestock(Long id) throws TransactionException {
LambdaQueryWrapper<Stockinfo> stockinfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
stockinfoLambdaQueryWrapper.eq(Stockinfo::getId, id);
Stockinfo one = stockinfoService.getOne(stockinfoLambdaQueryWrapper);
if (one.getSize() > 0) {
stockinfoService.update(new Stockinfo().setId(id).setSize(one.getSize() - 1), stockinfoLambdaQueryWrapper);
return Result.succ("减库存成功");
}
return Result.succ("库存不足");
}
}
测试
订单服务异常
接下来,上面可以看到,我们已经在订单接口中添加以下代码
int i = 1 / 0;
用来模拟订单服务异常。
咱们来看一下
初始状态订单是空的,商品数量是8。
我们运行一下
可以发现因为订单服务异常,订单信息并没有增加,库存数量也没变化,这证明确实事务生效了。
库存服务异常
那咱们看看远程调用的库存服务出现异常了,还可以吗
那就把订单接口的除0语句注释掉,然后在库存接口中添加除0语句来模拟服务异常。
订单接口
@GlobalTransactional
@PostMapping("addOrder")
public Result addOrder(Long id) {
orderinfoService.save(new Orderinfo().setName(String.format("订单商品id:%s", id)));
Result reducestock = stockService.reducestock(id);
// int i = 1 / 0;
return Result.succ("下单成功");
}
库存接口
@PostMapping("reducestock")
public Result reducestock(Long id) throws TransactionException {
LambdaQueryWrapper<Stockinfo> stockinfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
stockinfoLambdaQueryWrapper.eq(Stockinfo::getId, id);
Stockinfo one = stockinfoService.getOne(stockinfoLambdaQueryWrapper);
if (one.getSize() > 0) {
stockinfoService.update(new Stockinfo().setId(id).setSize(one.getSize() - 1), stockinfoLambdaQueryWrapper);
int i = 1 / 0;
return Result.succ("减库存成功");
}
return Result.succ("库存不足");
}
现在我们看下结果
不对呀,明明出现异常了,为什么没有回滚呢,这是咋回事
其实,这是因为咱们配置的Fegin的实现类帮咱们把库存接口的抛出的异常给处理了,因此Seata捕获不到异常,就以为啥事没有,所以就不会回滚。
那总不能说出异常了不管他吧,完全是可以处理的,就是在FeginServiceImpl中异常处理方法中添加以下内容即可
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
这时候就有小伙伴说了,难道写每一个异常处理方法的时候都要加这一句话吗,显然是不用的
别忘了咱有一个神奇的方法,叫做AOP,反正所有异常处理方法都要做这一件事,那我们干脆写个切面得了,一旦执行这些异常方法就直接回滚。
具体实现如下
package com.xiaow.aop.Aspect;
import com.xiaow.exception.SeataTransactionException;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransactionContext;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @ClassName FeginSeataAspect
* @Author xiaow
* @DATE 2024/4/25 20:04
* 对fegin拦截的错误进行回滚
**/
@Aspect
@Component
public class FeginSeataAspect {
@Pointcut("execution(* com.xiaow.fegin.*.*.*(..))")
public void seataFallback() {
}
@After("seataFallback()")
public void doAfter() {
try {
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
System.out.println("回滚了");
throw new SeataTransactionException(-1);
} catch (TransactionException e) {
e.printStackTrace();
}
}
}
这样咱们再测试一下
ok,成功回滚
总结
这里只是做了一个简单的小案例,大家可以在此基础上完成具体的需求。最主要的点还是在于被调用方法出现异常的特殊处理。
今天就到这了