Springcloud结合mybatis-plus与nacos实现分布式事务seata

简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

项目示例

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
库存服务:对给定的商品扣除仓储数量。
订单服务:根据采购需求创建订单。
帐户服务:从用户帐户中扣除余额。
下单流程

seata配置启动

#初始化seata 的nacos配置
cd conf
sh nacos-config.sh 127.0.0.1

#启动seata-server
cd bin
seata-server.bat -p 8091 -h 127.0.0.1 -m db

启动成功
配置可以参考官方的配置。

账户模块

mapper
/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author elite
 * @since 2022-09-11
 */
@Mapper
public interface AccountMapper extends BaseMapper<Account> {

    /**
     * 更新账户余额
     * @param user_id
     * @param amt
     * @return
     */
    @Update("UPDATE seata_account.account SET  acc_money= acc_money - #{amt} WHERE user_id = #{user_id} and (acc_money - #{amt}) >= 0")
    boolean deductAcct(Integer user_id, BigDecimal amt);
}
启动账户

启动成功

库存模块

mapper
/**
 * <p>
 * 库存表 Mapper 接口
 * </p>
 *
 * @author elite
 * @since 2022-09-11
 */
@Mapper
public interface StockMapper extends BaseMapper<Stock> {
    @Update("UPDATE seata_stock.stock set stock_num = stock_num - #{product_num} WHERE product_id = #{product_id} and  (stock_num - #{product_num}) >= 0")
    boolean deuctStock(Integer product_id, Integer product_num);
}
启动账户模块

账户

订单模块
依赖
 <!--seata-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-seata</artifactId>
        <version>2.2.0.RELEASE</version>
    </dependency>
    <dependency>
yml配置
server:
  port: 8087
spring:
  application:
    name: springcloud-seata-order
  ##nacos服务地址注册
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.5.130:8848
        enabled: true
    alibaba:
      seata:
        tx-service-group: springcloud-seata-order
  #配置数据库
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.5.130:3306/seata_order
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    druid:
      # 初始化大小,最小,最大
      initialSize: 5
      minIdle: 5
      maxActive: 20
      # 配置获取连接等待超时的时间(毫秒)
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置有一个连接在连接池中的最小生存时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打开PSCache,指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql将无法统计,'wall'用于防火墙
      filters: stat, wall, log4j
      # 通过connectProperties属性来打开mergeSql功能,慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

#mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml

controller类
/**
 * <p>
 * 订单表 前端控制器
 * </p>
 *
 * @author elite
 * @since 2022-09-11
 */
@RestController
@RequestMapping("/springcloud/order")
public class OrderController {
    @Autowired
    IOrderService orderService;

    /**
     * 传入用户id 商品id下单
     * @param product_id
     * @param user_id
     * @return
     */
    @GetMapping("/createOrder/{product_id}/{user_id}/{product_num}")
    public R createOrder(@PathVariable("")Integer product_id,
                         @PathVariable("user_id") Integer user_id,
                         @PathVariable("product_num")Integer product_num){
         R r = orderService.createOrder(product_id,user_id,product_num);
        return r;
    }

}
服务类

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author elite
 * @since 2022-09-06
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Autowired
    OrderMapper orderMapper;
    //库存服务
    @Autowired
    StockService stockService;
    //账户服务
    @Autowired
    AcctService acctService;
    /**
     * 下单服务
     * @param product_id
     * @param user_id
     * @return
     */
    @GlobalTransactional
    @Override
    public R createOrder(Integer product_id, Integer user_id,Integer product_num) {
        //库存服务:对给定的商品扣除仓储数量。
        Product product = stockService.getProductById(product_id);
        if (product == null){
            return R.fail(400,"商品信息为空,商品ID不能为空");
        }
        boolean deductSuccess = stockService.deductStock(product_id, product_num);
        if (!deductSuccess){
            return R.fail(400,"扣减库存失败!");
        }
        //验证用户
        User user = acctService.getUserByUseId(user_id);
        if (user == null){
            return R.fail(400,"传入的用户ID不存在");
        }
        //订单服务:根据采购需求创建订单。
        BigDecimal amt = (product.getProductPrice()).multiply(new BigDecimal(product_num  ));
        //订单信息
        Order order = new Order();
        order.setOrderNo(10);
        order.setProductId(product_id);
        order.setUserId(user.getUserId());
        order.setOrderNum(product_num);
        order.setOrderAmt(amt); //单价*数量
        order.setOrderStatus("下单");
        order.setPayStatus("支付成功");
        order.setCreateBy("牛奶糖");
        order.setUpdateBy("牛奶糖");
        orderMapper.insert(order);
        //模拟异常
        //int i = 1/0;
        //帐户服务:从用户帐户中扣除余额。
        boolean deductAcctFlag = acctService.deductAcct(user_id, amt);
        if (!deductAcctFlag){
            return R.fail(400,"扣减账户失败!");
        }
        return R.ok(200,"下单成功!",order);
    }
}

测试库存模块

获取商品

获取商品

扣减库存

扣减库存
执行的sql
==> Preparing: UPDATE seata_stock.stock set stock_num = stock_num - ? WHERE product_id = ? and (stock_num - ?) >= 0
更新数量大于库存数量失败

测试账户模块

获取用户

获取用户

扣减账户

扣减账户

测试订单

不加事务模拟下单

下单成功
订单
库存
账户

不加事务模拟异常

异常模拟库存减少
账户未扣

加事务模拟异常

@GlobalTransactional 进行模拟异常
事务
下单订单
账户
数据库脚本和完整代码比较多,这里就不全部贴出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小刘同学要加油呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值