jeecgboot seata集成实战

1. 环境描述

JeecgBoot 3.0

seata版本 : 1.3.0

2.数据库搭建

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

image.png

首先在四个数据库中引入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. 代码编写

项目结构

image.png

其中三个实体类对应如下

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

image.png

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

http://localhost:8080/jeecg-boot/order?userId=2&productId=1

image.png

更新异常,数据回滚。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容