jeecgboot seata集成實戰(zhàn)

1. 環(huán)境描述

JeecgBoot 3.0

seata版本 : 1.3.0

2.數(shù)據(jù)庫搭建

先創(chuàng)建3個數(shù)據(jù)庫币砂,加上jeecg-boot自有的數(shù)據(jù)庫,一共4個數(shù)據(jù)庫

image.png

首先在四個數(shù)據(jù)庫中引入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中沛贪,創(chuàng)建表并插入數(shù)據(jù)

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庫中,創(chuàng)建表

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中逗爹,創(chuàng)建表并插入數(shù)據(jù)

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. 坐標(biāo)引入

<!-- 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 事務(wù)組編號 用于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
        # 設(shè)置 賬號數(shù)據(jù)源配置
        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
          # 設(shè)置 訂單數(shù)據(jù)源配置
        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
          # 設(shè)置商品 數(shù)據(jù)源配置
        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
          
      # 設(shè)置默認(rèn)數(shù)據(jù)源或者數(shù)據(jù)源組 默認(rèn)值即為master
      primary: master   # 默認(rèn)指定一個數(shù)據(jù)源
      # 開啟對 seata的支持
      seata: true

5. Seata啟動

采用jeecg-boot單體模式測試绑蔫,使用默認(rèn)的文件進(jìn)行seata配置,不需要做額外的配置县习,直接啟動seata-server.bat即可耻煤。

6. 代碼編寫

項目結(jié)構(gòu)

image.png

其中三個實體類對應(yīng)如下

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對應(yīng)代碼如下

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>
 
    <!--根據(jù)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對應(yīng)的代碼如下

package org.jeecg.modules.seata.service;

import org.jeecg.modules.seata.entity.Product;
 
public interface ProductService {
 
    /**
     * 減庫存
     *
     * @param productId 商品 ID
     * @param amount    扣減數(shù)量
     * @throws Exception 扣減失敗時拋出異常
     */
    Product reduceStock(Integer productId, Integer amount) throws Exception;
 
}
package org.jeecg.modules.seata.service;
 
public interface OrderService {
 
    /**
     * 下訂單
     *
     * @param userId 用戶id
     * @param productId 產(chǎn)品id
     * @return 訂單id
     * @throws Exception 創(chuàng)建訂單失敗,拋出異常
     */
    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("當(dāng)前 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全局事務(wù)注解
    public Integer createOrder(Integer userId, Integer productId) throws Exception {
        Integer amount = 1; // 購買數(shù)量暫時設(shè)置為 1
 
        log.info("當(dāng)前 XID: {}", RootContext.getXID());
 
        // 減庫存 - 遠(yuǎn)程服務(wù)
        Product product = productService.reduceStock(productId, amount);
 
        // 減余額 - 遠(yuǎn)程服務(wù)
        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("當(dāng)前 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對應(yīng)的代碼如下

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. 測試結(jié)果

在瀏覽器請求

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

image.png

正常提交准颓,數(shù)據(jù)庫數(shù)據(jù)都是正常的哈蝇。

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

image.png

更新異常,數(shù)據(jù)回滾攘已。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炮赦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子样勃,更是在濱河造成了極大的恐慌吠勘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峡眶,死亡現(xiàn)場離奇詭異剧防,居然都是意外死亡,警方通過查閱死者的電腦和手機辫樱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門峭拘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狮暑,你說我怎么就攤上這事鸡挠。” “怎么了搬男?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵拣展,是天一觀的道長。 經(jīng)常有香客問我缔逛,道長备埃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任褐奴,我火速辦了婚禮按脚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歉糜。我一直安慰自己乘寒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布匪补。 她就那樣靜靜地躺著伞辛,像睡著了一般烂翰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚤氏,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天甘耿,我揣著相機與錄音,去河邊找鬼竿滨。 笑死佳恬,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的于游。 我是一名探鬼主播毁葱,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贰剥!你這毒婦竟也來了倾剿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚌成,失蹤者是張志新(化名)和其女友劉穎前痘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體担忧,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡芹缔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓶盛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片最欠。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蓬网,靈堂內(nèi)的尸體忽然破棺而出窒所,到底是詐尸還是另有隱情,我是刑警寧澤帆锋,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站禽额,受9級特大地震影響锯厢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脯倒,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一实辑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藻丢,春花似錦剪撬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馍佑。三九已至,卻和暖如春梨水,著一層夾襖步出監(jiān)牢的瞬間拭荤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工疫诽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舅世,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓奇徒,卻偏偏與公主長得像雏亚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摩钙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容