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. 坐标引入



    io.seata
    seata-spring-boot-starter
    1.3.0

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. 代码编写

项目结构

jeecgboot集成seata实战_第1张图片

其中三个实体类对应如下

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);
}



    
        
        
        
        
        
        
    
    
    id, name, price, stock, add_time, update_time
  
    
    
    delete from product
    where id = #{id,jdbcType=INTEGER}
  
    
    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 into product
        
            
                id,
            
            
                name,
            
            
                price,
            
            
                stock,
            
            
                add_time,
            
            
                update_time,
            
        
        
            
                #{id,jdbcType=INTEGER},
            
            
                #{name,jdbcType=VARCHAR},
            
            
                #{price,jdbcType=DECIMAL},
            
            
                #{stock,jdbcType=INTEGER},
            
            
                #{addTime,jdbcType=TIMESTAMP},
            
            
                #{updateTime,jdbcType=TIMESTAMP},
            
        
    
    
        update product
        
            
                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 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 product SET stock = stock - #{amount, jdbcType=INTEGER}
    WHERE id = #{productId, jdbcType=INTEGER} AND stock >= #{amount, jdbcType=INTEGER}
  
 



  
    
    
    
    
    
    
  
  
    id, user_id, product_id, pay_amount, add_time, update_time
  
  
  
    delete from orders
    where id = #{id,jdbcType=INTEGER}
  
  
    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 into orders
    
      
        id,
      
      
        user_id,
      
      
        product_id,
      
      
        pay_amount,
      
      
        add_time,
      
      
        update_time,
      
    
    
      
        #{id,jdbcType=INTEGER},
      
      
        #{userId,jdbcType=INTEGER},
      
      
        #{productId,jdbcType=INTEGER},
      
      
        #{payAmount,jdbcType=DECIMAL},
      
      
        #{addTime,jdbcType=TIMESTAMP},
      
      
        #{updateTime,jdbcType=TIMESTAMP},
      
    
  
 
  
    update orders
    
      
        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 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}
  



 
    
        
        
        
        
    
 
    
    id, user_id, balance, update_time
  
 
    
    
 
    
 
    
    delete from account
    where id = #{id,jdbcType=INTEGER}
  
 
    
    insert into account (id, user_id, balance,
      update_time)
    values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{balance,jdbcType=DOUBLE},
      #{updateTime,jdbcType=TIMESTAMP})
  
 
    
        insert into account
        
            
                id,
            
            
                user_id,
            
            
                balance,
            
            
                update_time,
            
        
        
            
                #{id,jdbcType=INTEGER},
            
            
                #{userId,jdbcType=INTEGER},
            
            
                #{balance,jdbcType=DOUBLE},
            
            
                #{updateTime,jdbcType=TIMESTAMP},
            
        
    
 
    
        update account
        
            
                user_id = #{userId,jdbcType=INTEGER},
            
            
                balance = #{balance,jdbcType=DOUBLE},
            
            
                update_time = #{updateTime,jdbcType=TIMESTAMP},
            
        
        where id = #{id,jdbcType=INTEGER}
    
 
   
    update account
    set user_id = #{userId,jdbcType=INTEGER},
      balance = #{balance,jdbcType=DOUBLE},
      update_time = #{updateTime,jdbcType=TIMESTAMP}
    where id = #{id,jdbcType=INTEGER}
  
 
    
    
    update account
        SET balance = balance - #{money}
    WHERE user_id = #{userId, jdbcType=INTEGER}
        AND balance >= ${money}
  

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-b...
在这里插入图片描述

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

http://localhost:8080/jeecg-b...
jeecgboot集成seata实战_第2张图片

更新异常,数据回滚。

你可能感兴趣的:(jeecgboot集成seata实战)