• jeecgboot集成seata实战


    1. 环境描述

    JeecgBoot 3.0

    seata版本 : 1.3.0

    2.数据库搭建

    先创建3个数据库,加上jeecg-boot自有的数据库,一共4个数据库

    在这里插入图片描述

    首先在四个数据库中引入undo_log表

    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,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8;
    

    在jeecg-account中,创建表并插入数据

    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `balance` int(11) DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
     
    INSERT INTO `account` (`id`, `user_id`, `balance`, `update_time`) VALUES ('1', '1', '200', '2021-01-15 00:02:17');
    

    在jeecg-order库中,创建表

    CREATE TABLE `orders` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `product_id` int(11) DEFAULT NULL,
      `pay_amount` int(11) DEFAULT NULL,
      `add_time` datetime DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb4;
    

    在jeecg-product中,创建表并插入数据

    CREATE TABLE `product` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `price` int(11) DEFAULT NULL,
      `stock` int(11) DEFAULT NULL,
      `add_time` datetime DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
     
    INSERT INTO `product` (`id`, `name`, `price`, `stock`, `add_time`, `update_time`) VALUES ('1', '电池', '10', '67', '2021-01-15 00:00:32', '2021-01-15 00:00:35');
    

    3. 坐标引入

    <!-- seata-spring-boot-starter -->
    <dependency>
    	<groupId>io.seata</groupId>
    	<artifactId>seata-spring-boot-starter</artifactId>
    	<version>1.3.0</version>
    </dependency>
    

    4. yml配置文件

    seata:
      config:
        type: file
      application-id: springboot-seata
      #  enable-auto-data-source-proxy: false
      registry:
        type: file
      service:
        grouplist:
          default: 127.0.0.1:8091
        vgroup-mapping:
          springboot-seata-group: default
      # seata 事务组编号 用于TC集群名
      tx-service-group: springboot-seata-group
    spring:
      datasource:
        dynamic:
          datasource:
            master:
              url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
              username: root
              password: root
              driver-class-name: com.mysql.cj.jdbc.Driver
            # 设置 账号数据源配置
            account-ds:
              driver-class-name: com.mysql.cj.jdbc.Driver
              password: root
              url: jdbc:mysql://127.0.0.1:3306/jeecg-account?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
              username: root
              # 设置 订单数据源配置
            order-ds:
              driver-class-name: com.mysql.cj.jdbc.Driver
              password: root
              url: jdbc:mysql://127.0.0.1:3306/jeecg-order?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
              username: root
              # 设置商品 数据源配置
            product-ds:
              driver-class-name: com.mysql.cj.jdbc.Driver
              password: root
              url: jdbc:mysql://127.0.0.1:3306/jeecg-product?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
              username: root
              
          # 设置默认数据源或者数据源组 默认值即为master
          primary: master   # 默认指定一个数据源
          # 开启对 seata的支持
          seata: true
    

    5. Seata启动

    采用jeecg-boot单体模式测试,使用默认的文件进行seata配置,不需要做额外的配置,直接启动seata-server.bat即可。

    6. 代码编写

    项目结构

    在这里插入图片描述

    其中三个实体类对应如下

    package org.jeecg.modules.seata.entity;
     
    import lombok.Data;
     
    import java.math.BigDecimal;
    import java.util.Date;
     
    @Data
    public class Orders {
     
        private Integer id;
     
        private Integer userId;
     
        private Integer productId;
     
        private BigDecimal payAmount;
     
        private Date addTime;
     
        private Date updateTime;
     
    }
    
    package org.jeecg.modules.seata.entity;
     
    import lombok.Data;
     
    import java.math.BigDecimal;
    import java.util.Date;
     
    @Data
    public class Product {
     
        private Integer id;
     
        private String name;
     
        private BigDecimal price;
     
        private Integer stock;
     
        private Date addTime;
     
        private Date updateTime;
    }
    
    package org.jeecg.modules.seata.entity;
     
    import lombok.Data;
     
    import java.math.BigDecimal;
    import java.util.Date;
     
    @Data
    public class Account {
     
        private Integer id;
     
        private Integer userId;
     
        private BigDecimal balance;
     
        private Date updateTime;
    }
    

    Mapper对应代码如下

    package org.jeecg.modules.seata.mapper;
     
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.jeecg.modules.seata.entity.Product;
     
    @Mapper
    public interface ProductMapper {
     
        int deleteByPrimaryKey(Integer id);
     
        int insert(Product record);
     
        int insertSelective(Product record);
     
        Product selectByPrimaryKey(Integer id);
     
        int updateByPrimaryKeySelective(Product record);
     
        int updateByPrimaryKey(Product record);
     
        int reduceStock(@Param("productId") Integer productId, @Param("amount") Integer amount);
    }
    
    package org.jeecg.modules.seata.mapper;
     
    import org.apache.ibatis.annotations.Mapper;
    import org.jeecg.modules.seata.entity.Orders;
     
    @Mapper
    public interface OrdersMapper {
     
        int deleteByPrimaryKey(Integer id);
     
        int insert(Orders record);
     
        int insertSelective(Orders record);
     
        Orders selectByPrimaryKey(Integer id);
     
        int updateByPrimaryKeySelective(Orders record);
     
        int updateByPrimaryKey(Orders record);
    }
    
    package org.jeecg.modules.seata.mapper;
     
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.jeecg.modules.seata.entity.Account;
     
    import java.math.BigDecimal;
     
    @Mapper
    public interface AccountMapper {
     
        int deleteByPrimaryKey(Integer id);
     
        int insert(Account record);
     
        int insertSelective(Account record);
     
        Account selectByPrimaryKey(Integer id);
     
        Account selectAccountByUserId(Integer userId);
     
        int updateByPrimaryKeySelective(Account record);
     
        int updateByPrimaryKey(Account record);
     
        int reduceBalance(@Param("userId") Integer userId, @Param("money") BigDecimal money);
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.jeecg.modules.seata.mapper.ProductMapper">
        <resultMap id="BaseResultMap" type="org.jeecg.modules.seata.entity.Product">
            <id column="id" jdbcType="INTEGER" property="id"/>
            <result column="name" jdbcType="VARCHAR" property="name"/>
            <result column="price" jdbcType="DECIMAL" property="price"/>
            <result column="stock" jdbcType="INTEGER" property="stock"/>
            <result column="add_time" jdbcType="TIMESTAMP" property="addTime"/>
            <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
        </resultMap>
        <sql id="Base_Column_List">
        id, name, price, stock, add_time, update_time
      </sql>
        <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
            select
            <include refid="Base_Column_List"/>
            from product
            where id = #{id,jdbcType=INTEGER}
        </select>
        <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        delete from product
        where id = #{id,jdbcType=INTEGER}
      </delete>
        <insert id="insert" parameterType="org.jeecg.modules.seata.entity.Product">
        insert into product (id, name, price,
          stock, add_time, update_time
          )
        values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{price,jdbcType=DECIMAL},
          #{stock,jdbcType=INTEGER}, #{addTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}
          )
      </insert>
        <insert id="insertSelective" parameterType="org.jeecg.modules.seata.entity.Product">
            insert into product
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    id,
                </if>
                <if test="name != null">
                    name,
                </if>
                <if test="price != null">
                    price,
                </if>
                <if test="stock != null">
                    stock,
                </if>
                <if test="addTime != null">
                    add_time,
                </if>
                <if test="updateTime != null">
                    update_time,
                </if>
            </trim>
            <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    #{id,jdbcType=INTEGER},
                </if>
                <if test="name != null">
                    #{name,jdbcType=VARCHAR},
                </if>
                <if test="price != null">
                    #{price,jdbcType=DECIMAL},
                </if>
                <if test="stock != null">
                    #{stock,jdbcType=INTEGER},
                </if>
                <if test="addTime != null">
                    #{addTime,jdbcType=TIMESTAMP},
                </if>
                <if test="updateTime != null">
                    #{updateTime,jdbcType=TIMESTAMP},
                </if>
            </trim>
        </insert>
        <update id="updateByPrimaryKeySelective" parameterType="org.jeecg.modules.seata.entity.Product">
            update product
            <set>
                <if test="name != null">
                    name = #{name,jdbcType=VARCHAR},
                </if>
                <if test="price != null">
                    price = #{price,jdbcType=DECIMAL},
                </if>
                <if test="stock != null">
                    stock = #{stock,jdbcType=INTEGER},
                </if>
                <if test="addTime != null">
                    add_time = #{addTime,jdbcType=TIMESTAMP},
                </if>
                <if test="updateTime != null">
                    update_time = #{updateTime,jdbcType=TIMESTAMP},
                </if>
            </set>
            where id = #{id,jdbcType=INTEGER}
        </update>
        <update id="updateByPrimaryKey" parameterType="org.jeecg.modules.seata.entity.Product">
        update product
        set name = #{name,jdbcType=VARCHAR},
          price = #{price,jdbcType=DECIMAL},
          stock = #{stock,jdbcType=INTEGER},
          add_time = #{addTime,jdbcType=TIMESTAMP},
          update_time = #{updateTime,jdbcType=TIMESTAMP}
        where id = #{id,jdbcType=INTEGER}
      </update>
     
        <!--减库存-->
        <update id="reduceStock">
        update product SET stock = stock - #{amount, jdbcType=INTEGER}
        WHERE id = #{productId, jdbcType=INTEGER} AND stock >= #{amount, jdbcType=INTEGER}
      </update>
     
    </mapper>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.jeecg.modules.seata.mapper.OrdersMapper">
      <resultMap id="BaseResultMap" type="org.jeecg.modules.seata.entity.Orders">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="user_id" jdbcType="INTEGER" property="userId" />
        <result column="product_id" jdbcType="INTEGER" property="productId" />
        <result column="pay_amount" jdbcType="DECIMAL" property="payAmount" />
        <result column="add_time" jdbcType="TIMESTAMP" property="addTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
      </resultMap>
      <sql id="Base_Column_List">
        id, user_id, product_id, pay_amount, add_time, update_time
      </sql>
      <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from orders
        where id = #{id,jdbcType=INTEGER}
      </select>
      <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        delete from orders
        where id = #{id,jdbcType=INTEGER}
      </delete>
      <insert id="insert" parameterType="org.jeecg.modules.seata.entity.Orders">
        insert into orders (id, user_id, product_id,
          pay_amount, add_time, update_time
          )
        values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{productId,jdbcType=INTEGER},
          #{payAmount,jdbcType=DECIMAL}, #{addTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}
          )
      </insert>
     
      <insert id="insertSelective" parameterType="org.jeecg.modules.seata.entity.Orders">
        insert into orders
        <trim prefix="(" suffix=")" suffixOverrides=",">
          <if test="id != null">
            id,
          </if>
          <if test="userId != null">
            user_id,
          </if>
          <if test="productId != null">
            product_id,
          </if>
          <if test="payAmount != null">
            pay_amount,
          </if>
          <if test="addTime != null">
            add_time,
          </if>
          <if test="updateTime != null">
            update_time,
          </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
          <if test="id != null">
            #{id,jdbcType=INTEGER},
          </if>
          <if test="userId != null">
            #{userId,jdbcType=INTEGER},
          </if>
          <if test="productId != null">
            #{productId,jdbcType=INTEGER},
          </if>
          <if test="payAmount != null">
            #{payAmount,jdbcType=DECIMAL},
          </if>
          <if test="addTime != null">
            #{addTime,jdbcType=TIMESTAMP},
          </if>
          <if test="updateTime != null">
            #{updateTime,jdbcType=TIMESTAMP},
          </if>
        </trim>
      </insert>
     
      <update id="updateByPrimaryKeySelective" parameterType="org.jeecg.modules.seata.entity.Orders">
        update orders
        <set>
          <if test="userId != null">
            user_id = #{userId,jdbcType=INTEGER},
          </if>
          <if test="productId != null">
            product_id = #{productId,jdbcType=INTEGER},
          </if>
          <if test="payAmount != null">
            pay_amount = #{payAmount,jdbcType=DECIMAL},
          </if>
          <if test="addTime != null">
            add_time = #{addTime,jdbcType=TIMESTAMP},
          </if>
          <if test="updateTime != null">
            update_time = #{updateTime,jdbcType=TIMESTAMP},
          </if>
        </set>
        where id = #{id,jdbcType=INTEGER}
      </update>
      <update id="updateByPrimaryKey" parameterType="org.jeecg.modules.seata.entity.Orders">
        update orders
        set user_id = #{userId,jdbcType=INTEGER},
          product_id = #{productId,jdbcType=INTEGER},
          pay_amount = #{payAmount,jdbcType=DECIMAL},
          add_time = #{addTime,jdbcType=TIMESTAMP},
          update_time = #{updateTime,jdbcType=TIMESTAMP}
        where id = #{id,jdbcType=INTEGER}
      </update>
    </mapper>
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.jeecg.modules.seata.mapper.AccountMapper">
     
        <resultMap id="BaseResultMap" type="org.jeecg.modules.seata.entity.Account">
            <id column="id" jdbcType="INTEGER" property="id"/>
            <result column="user_id" jdbcType="INTEGER" property="userId"/>
            <result column="balance" jdbcType="DECIMAL" property="balance"/>
            <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
        </resultMap>
     
        <sql id="Base_Column_List">
        id, user_id, balance, update_time
      </sql>
     
        <!--根据userId-->
        <select id="selectAccountByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
            select
            <include refid="Base_Column_List"/>
            from account
            where user_id = #{userId, jdbcType=INTEGER}
        </select>
     
        <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
            select
            <include refid="Base_Column_List"/>
            from account
            where id = #{id,jdbcType=INTEGER}
        </select>
     
        <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        delete from account
        where id = #{id,jdbcType=INTEGER}
      </delete>
     
        <insert id="insert" parameterType="org.jeecg.modules.seata.entity.Account">
        insert into account (id, user_id, balance,
          update_time)
        values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{balance,jdbcType=DOUBLE},
          #{updateTime,jdbcType=TIMESTAMP})
      </insert>
     
        <insert id="insertSelective" parameterType="org.jeecg.modules.seata.entity.Account">
            insert into account
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    id,
                </if>
                <if test="userId != null">
                    user_id,
                </if>
                <if test="balance != null">
                    balance,
                </if>
                <if test="updateTime != null">
                    update_time,
                </if>
            </trim>
            <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    #{id,jdbcType=INTEGER},
                </if>
                <if test="userId != null">
                    #{userId,jdbcType=INTEGER},
                </if>
                <if test="balance != null">
                    #{balance,jdbcType=DOUBLE},
                </if>
                <if test="updateTime != null">
                    #{updateTime,jdbcType=TIMESTAMP},
                </if>
            </trim>
        </insert>
     
        <update id="updateByPrimaryKeySelective" parameterType="org.jeecg.modules.seata.entity.Account">
            update account
            <set>
                <if test="userId != null">
                    user_id = #{userId,jdbcType=INTEGER},
                </if>
                <if test="balance != null">
                    balance = #{balance,jdbcType=DOUBLE},
                </if>
                <if test="updateTime != null">
                    update_time = #{updateTime,jdbcType=TIMESTAMP},
                </if>
            </set>
            where id = #{id,jdbcType=INTEGER}
        </update>
     
       <update id="updateByPrimaryKey" parameterType="org.jeecg.modules.seata.entity.Account">
        update account
        set user_id = #{userId,jdbcType=INTEGER},
          balance = #{balance,jdbcType=DOUBLE},
          update_time = #{updateTime,jdbcType=TIMESTAMP}
        where id = #{id,jdbcType=INTEGER}
      </update>
     
        <!--减余额-->
        <update id="reduceBalance">
        update account
            SET balance = balance - #{money}
        WHERE user_id = #{userId, jdbcType=INTEGER}
            AND balance >= ${money}
      </update>
    </mapper>
    

    Service对应的代码如下

    package org.jeecg.modules.seata.service;
    
    import org.jeecg.modules.seata.entity.Product;
     
    public interface ProductService {
     
        /**
         * 减库存
         *
         * @param productId 商品 ID
         * @param amount    扣减数量
         * @throws Exception 扣减失败时抛出异常
         */
        Product reduceStock(Integer productId, Integer amount) throws Exception;
     
    }
    
    package org.jeecg.modules.seata.service;
     
    public interface OrderService {
     
        /**
         * 下订单
         *
         * @param userId 用户id
         * @param productId 产品id
         * @return 订单id
         * @throws Exception 创建订单失败,抛出异常
         */
        Integer createOrder(Integer userId, Integer productId) throws Exception;
     
    }
    
    package org.jeecg.modules.seata.service;
     
    import java.math.BigDecimal;
     
    public interface AccountService {
     
        /**
         * 减余额
         *
         * @param userId 用户id
         * @param money  扣减金额
         * @throws Exception 失败时抛出异常
         */
        void reduceBalance(Integer userId, BigDecimal money) throws Exception;
     
    }
    
    package org.jeecg.modules.seata.service.impl;
     
    import com.baomidou.dynamic.datasource.annotation.DS;
    import io.seata.core.context.RootContext;
    import lombok.extern.slf4j.Slf4j;
    import org.jeecg.modules.seata.entity.Product;
    import org.jeecg.modules.seata.mapper.ProductMapper;
    import org.jeecg.modules.seata.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
     
    @Slf4j
    @Service
    public class ProductServiceImpl implements ProductService {
     
        @Autowired
        private ProductMapper productMapper;
     
        @Override
        @DS(value = "product-ds")
        public Product reduceStock(Integer productId, Integer amount) throws Exception {
            log.info("当前 XID: {}", RootContext.getXID());
     
            // 检查库存
            Product product = productMapper.selectByPrimaryKey(productId);
            if (product.getStock() < amount) {
                throw new Exception("库存不足");
            }
     
            // 扣减库存
            int updateCount = productMapper.reduceStock(productId, amount);
            // 扣除成功
            if (updateCount == 0) {
                throw new Exception("库存不足");
            }
     
            // 扣除成功
            log.info("扣除 {} 库存成功", productId);
            return product;
        }
    }
    
    package org.jeecg.modules.seata.service.impl;
     
    import com.baomidou.dynamic.datasource.annotation.DS;
    import io.seata.core.context.RootContext;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.jeecg.modules.seata.entity.Orders;
    import org.jeecg.modules.seata.entity.Product;
    import org.jeecg.modules.seata.mapper.OrdersMapper;
    import org.jeecg.modules.seata.service.AccountService;
    import org.jeecg.modules.seata.service.OrderService;
    import org.jeecg.modules.seata.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
     
    import java.math.BigDecimal;
     
    @Slf4j
    @Service
    public class OrderServiceImpl implements OrderService {
     
        @Autowired
        private OrdersMapper ordersMapper;
     
        @Autowired
        private AccountService accountService;
     
        @Autowired
        private ProductService productService;
     
        @Override
        @DS(value = "order-ds")
        @GlobalTransactional //seata全局事务注解
        public Integer createOrder(Integer userId, Integer productId) throws Exception {
            Integer amount = 1; // 购买数量暂时设置为 1
     
            log.info("当前 XID: {}", RootContext.getXID());
     
            // 减库存 - 远程服务
            Product product = productService.reduceStock(productId, amount);
     
            // 减余额 - 远程服务
            accountService.reduceBalance(userId, product.getPrice());
     
            // 下订单 - 本地下订单
            Orders order = new Orders();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setPayAmount(product.getPrice().multiply(new BigDecimal(amount)));
     
            ordersMapper.insertSelective(order);
     
            log.info("下订单: {}", order.getId());
     
     
            //int a = 1/0;
            // 返回订单编号
            return order.getId();
        }
    }
    
    package org.jeecg.modules.seata.service.impl;
     
    import com.baomidou.dynamic.datasource.annotation.DS;
    import io.seata.core.context.RootContext;
    import lombok.extern.slf4j.Slf4j;
    import org.jeecg.modules.seata.entity.Account;
    import org.jeecg.modules.seata.mapper.AccountMapper;
    import org.jeecg.modules.seata.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
     
    import java.math.BigDecimal;
     
    @Slf4j
    @Service
    public class AccountServiceImpl implements AccountService {
     
        @Autowired
        private AccountMapper accountMapper;
     
        @Override
        @DS(value = "account-ds")
        public void reduceBalance(Integer userId, BigDecimal money) throws Exception {
            log.info("当前 XID: {}", RootContext.getXID());
     
            // 检查余额
            Account account = accountMapper.selectAccountByUserId(userId);
            if (account.getBalance().doubleValue() < money.doubleValue()) {
                throw new Exception("余额不足");
            }
     
            // 扣除余额
            int updateCount = accountMapper.reduceBalance(userId, money);
            // 扣除成功
            if (updateCount == 0) {
                throw new Exception("余额不足");
            }
            log.info("扣除用户 {} 余额成功", userId);
        }
    }
    

    controller对应的代码如下

    package org.jeecg.modules.seata.controller;
     
    import lombok.extern.slf4j.Slf4j;
    import org.jeecg.modules.seata.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
     
    @Slf4j //lombok
    @RestController
    public class OrderController {
     
        @Autowired
        private OrderService orderService;
     
        @RequestMapping("/order")
        public Integer createOrder(@RequestParam("userId") Integer userId,
                                   @RequestParam("productId") Integer productId) throws Exception {
     
            log.info("请求下单, 用户:{}, 商品:{}", userId, productId);
     
            return orderService.createOrder(userId, productId);
        }
    }
    

    7. 测试结果

    在浏览器请求

    http://localhost:8080/jeecg-boot/order?userId=1&productId=1
    在这里插入图片描述

    正常提交,数据库数据都是正常的。

    http://localhost:8080/jeecg-boot/order?userId=2&productId=1
    在这里插入图片描述

    更新异常,数据回滚。

  • 相关阅读:
    徒手撸设计模式组合模式
    徒手撸设计模式过滤器模式
    徒手撸设计模式享元模式
    徒手撸设计模式桥接模式
    徒手撸设计模式命令模式
    徒手撸设计模式观察者模式
    徒手撸设计模式装饰器模式
    徒手撸设计模式责任链模式
    徒手撸设计模式代理模式
    徒手撸设计模式解释器模式
  • 原文地址:https://www.cnblogs.com/jeecg158/p/16184047.html
Copyright © 2020-2023  润新知