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 更新异常,数据回滚。
|