From 644ef85f4b76316df5ca7f9496dc51e405c96f04 Mon Sep 17 00:00:00 2001 From: YunaiV <> Date: Wed, 1 Apr 2020 19:25:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20spring=20boot=20x=20seata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab-52/lab-52-multiple-datasource/pom.xml | 55 ++++++++++++++++++ .../MultipleDatasourceApplication.java | 13 +++++ .../seatademo/controller/OrderController.java | 29 ++++++++++ .../lab52/seatademo/dao/AccountDao.java | 32 +++++++++++ .../lab52/seatademo/dao/OrderDao.java | 21 +++++++ .../lab52/seatademo/dao/ProductDao.java | 33 +++++++++++ .../lab52/seatademo/entity/OrderDO.java | 56 +++++++++++++++++++ .../lab52/seatademo/service/OrderService.java | 19 +++++++ .../lab52/seatademo/service/PayService.java | 17 ++++++ .../seatademo/service/StorageService.java | 17 ++++++ .../service/impl/OrderServiceImpl.java | 53 ++++++++++++++++++ .../service/impl/PayServiceImpl.java | 51 +++++++++++++++++ .../service/impl/StorageServiceImpl.java | 52 +++++++++++++++++ .../src/main/resources/application.yaml | 49 ++++++++++++++++ lab-52/pom.xml | 19 +++++++ pom.xml | 1 + 16 files changed, 517 insertions(+) create mode 100644 lab-52/lab-52-multiple-datasource/pom.xml create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/MultipleDatasourceApplication.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/controller/OrderController.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/AccountDao.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/OrderDao.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/ProductDao.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/entity/OrderDO.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/OrderService.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/PayService.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/StorageService.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/PayServiceImpl.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/StorageServiceImpl.java create mode 100644 lab-52/lab-52-multiple-datasource/src/main/resources/application.yaml create mode 100644 lab-52/pom.xml diff --git a/lab-52/lab-52-multiple-datasource/pom.xml b/lab-52/lab-52-multiple-datasource/pom.xml new file mode 100644 index 000000000..412a459eb --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/pom.xml @@ -0,0 +1,55 @@ + + + + org.springframework.boot + spring-boot-starter-parent + 2.2.2.RELEASE + + + 4.0.0 + + lab-52-multiple-datasource + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + mysql + mysql-connector-java + 5.1.48 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.2 + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.0.0 + + + + + io.seata + seata-spring-boot-starter + 1.1.0 + + + + diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/MultipleDatasourceApplication.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/MultipleDatasourceApplication.java new file mode 100644 index 000000000..573deacb9 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/MultipleDatasourceApplication.java @@ -0,0 +1,13 @@ +package cn.iocoder.springboot.lab52.seatademo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MultipleDatasourceApplication { + + public static void main(String[] args) { + SpringApplication.run(MultipleDatasourceApplication.class, args); + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/controller/OrderController.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/controller/OrderController.java new file mode 100644 index 000000000..3cb75882d --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/controller/OrderController.java @@ -0,0 +1,29 @@ +package cn.iocoder.springboot.lab52.seatademo.controller; + +import cn.iocoder.springboot.lab52.seatademo.service.OrderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/order") +public class OrderController { + + private Logger logger = LoggerFactory.getLogger(OrderController.class); + + @Autowired + private OrderService orderService; + + @PostMapping("/create") + public Integer createOrder(@RequestParam("userId") Long userId, + @RequestParam("productId") Long productId, + @RequestParam("price") Integer price) throws Exception { + logger.info("收到下单请求,用户:{}, 商品:{}, 价格:{}", userId, productId, price); + return orderService.createOrder(userId, productId, price); + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/AccountDao.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/AccountDao.java new file mode 100644 index 000000000..6dfed7fc9 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/AccountDao.java @@ -0,0 +1,32 @@ +package cn.iocoder.springboot.lab52.seatademo.dao; + + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import org.springframework.stereotype.Repository; + +@Mapper +@Repository +public interface AccountDao { + + /** + * 获取账户余额 + * + * @param userId 用户 ID + * @return 账户余额 + */ + @Select("SELECT balance FROM account WHERE id = #{userId}") + Integer getBalance(@Param("userId") Long userId); + + /** + * 扣减余额 + * + * @param price 需要扣减的数目 + * @return 影响记录行数 + */ + @Update("UPDATE account SET balance = balance - #{price} WHERE id = 1 AND balance >= ${price}") + int reduceBalance(@Param("price") Integer price); + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/OrderDao.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/OrderDao.java new file mode 100644 index 000000000..f11927a27 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/OrderDao.java @@ -0,0 +1,21 @@ +package cn.iocoder.springboot.lab52.seatademo.dao; + +import cn.iocoder.springboot.lab52.seatademo.entity.OrderDO; +import org.apache.ibatis.annotations.*; +import org.springframework.stereotype.Repository; + +@Mapper +@Repository +public interface OrderDao { + + /** + * 插入订单记录 + * + * @param order 订单 + * @return 影响记录数量 + */ + @Insert("INSERT INTO orders (user_id, product_id, pay_amount) VALUES (#{userId}, #{productId}, #{payAmount})") + @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id") + int saveOrder(OrderDO order); + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/ProductDao.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/ProductDao.java new file mode 100644 index 000000000..89caadb5c --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/dao/ProductDao.java @@ -0,0 +1,33 @@ +package cn.iocoder.springboot.lab52.seatademo.dao; + + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; +import org.springframework.stereotype.Repository; + +@Mapper +@Repository +public interface ProductDao { + + /** + * 获取库存 + * + * @param productId 商品编号 + * @return 库存 + */ + @Select("SELECT stock FROM product WHERE id = #{productId}") + Integer getStock(@Param("productId") Long productId); + + /** + * 扣减库存 + * + * @param productId 商品编号 + * @param amount 扣减数量 + * @return 影响记录行数 + */ + @Update("UPDATE product SET stock = stock - #{amount} WHERE id = #{productId} AND stock >= #{amount}") + int reduceStock(@Param("productId") Long productId, @Param("amount") Integer amount); + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/entity/OrderDO.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/entity/OrderDO.java new file mode 100644 index 000000000..af6e8ee31 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/entity/OrderDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.springboot.lab52.seatademo.entity; + +/** + * 订单实体 + */ +public class OrderDO { + + /** 订单编号 **/ + private Integer id; + + /** 用户编号 **/ + private Long userId; + + /** 产品编号 **/ + private Long productId; + + /** 支付金额 **/ + private Integer payAmount; + + public Integer getId() { + return id; + } + + public OrderDO setId(Integer id) { + this.id = id; + return this; + } + + public Long getUserId() { + return userId; + } + + public OrderDO setUserId(Long userId) { + this.userId = userId; + return this; + } + + public Long getProductId() { + return productId; + } + + public OrderDO setProductId(Long productId) { + this.productId = productId; + return this; + } + + public Integer getPayAmount() { + return payAmount; + } + + public OrderDO setPayAmount(Integer payAmount) { + this.payAmount = payAmount; + return this; + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/OrderService.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/OrderService.java new file mode 100644 index 000000000..691029d85 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/OrderService.java @@ -0,0 +1,19 @@ +package cn.iocoder.springboot.lab52.seatademo.service; + +/** + * 订单 Service + */ +public interface OrderService { + + /** + * 创建订单 + * + * @param userId 用户编号 + * @param productId 产品编号 + * @param price 价格 + * @return 订单编号 + * @throws Exception 创建订单失败,抛出异常 + */ + Integer createOrder(Long userId, Long productId, Integer price) throws Exception; + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/PayService.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/PayService.java new file mode 100644 index 000000000..6581867e4 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/PayService.java @@ -0,0 +1,17 @@ +package cn.iocoder.springboot.lab52.seatademo.service; + +/** + * 支付 Service + */ +public interface PayService { + + /** + * 扣除余额 + * + * @param userId 用户编号 + * @param price 扣减金额 + * @throws Exception 失败时抛出异常 + */ + void reduceBalance(Long userId, Integer price) throws Exception; + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/StorageService.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/StorageService.java new file mode 100644 index 000000000..b9e07c571 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/StorageService.java @@ -0,0 +1,17 @@ +package cn.iocoder.springboot.lab52.seatademo.service; + +/** + * 库存 Service + */ +public interface StorageService { + + /** + * 扣减库存 + * + * @param productId 商品 ID + * @param amount 扣减数量 + * @throws Exception 扣减失败时抛出异常 + */ + void reduceStock(Long productId, Integer amount) throws Exception; + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java new file mode 100644 index 000000000..8ef74528e --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java @@ -0,0 +1,53 @@ +package cn.iocoder.springboot.lab52.seatademo.service.impl; + +import cn.iocoder.springboot.lab52.seatademo.dao.OrderDao; +import cn.iocoder.springboot.lab52.seatademo.entity.OrderDO; +import cn.iocoder.springboot.lab52.seatademo.service.OrderService; +import cn.iocoder.springboot.lab52.seatademo.service.PayService; +import cn.iocoder.springboot.lab52.seatademo.service.StorageService; +import com.baomidou.dynamic.datasource.annotation.DS; +import io.seata.core.context.RootContext; +import io.seata.spring.annotation.GlobalTransactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class OrderServiceImpl implements OrderService { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private OrderDao orderDao; + + @Autowired + private PayService payService; + + @Autowired + private StorageService storageService; + + @GlobalTransactional + @Override + @DS(value = "order-ds") + public Integer createOrder(Long userId, Long productId, Integer price) throws Exception { + Integer amount = 1; // 购买数量,暂时设置为 1。 + + logger.info("[createOrder] 当前 XID: {}", RootContext.getXID()); + + // 扣减库存 + storageService.reduceStock(productId, amount); + + // 扣减余额 + payService.reduceBalance(userId, price); + + // 保存订单 + OrderDO order = new OrderDO().setUserId(userId).setProductId(productId).setPayAmount(amount * price); + orderDao.saveOrder(order); + logger.info("[createOrder] 保存订单: {}", order.getId()); + + // 返回订单编号 + return order.getId(); + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/PayServiceImpl.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/PayServiceImpl.java new file mode 100644 index 000000000..9206487b6 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/PayServiceImpl.java @@ -0,0 +1,51 @@ +package cn.iocoder.springboot.lab52.seatademo.service.impl; + +import cn.iocoder.springboot.lab52.seatademo.dao.AccountDao; +import cn.iocoder.springboot.lab52.seatademo.service.PayService; +import com.baomidou.dynamic.datasource.annotation.DS; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class PayServiceImpl implements PayService { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private AccountDao accountDao; + + @Override + @DS(value = "pay-ds") + @Transactional(propagation = Propagation.REQUIRES_NEW) // 开启新事物 + public void reduceBalance(Long userId, Integer price) throws Exception { + logger.info("[reduceBalance] 当前 XID: {}", RootContext.getXID()); + + // 检查余额 + checkBalance(userId, price); + + logger.info("[reduceBalance] 开始扣减用户 {} 余额", userId); + // 扣除余额 + int updateCount = accountDao.reduceBalance(price); + // 扣除成功 + if (updateCount == 0) { + logger.warn("[reduceBalance] 扣除用户 {} 余额失败", userId); + throw new Exception("余额不足"); + } + logger.info("[reduceBalance] 扣除用户 {} 余额成功", userId); + } + + private void checkBalance(Long userId, Integer price) throws Exception { + logger.info("[checkBalance] 检查用户 {} 余额", userId); + Integer balance = accountDao.getBalance(userId); + if (balance < price) { + logger.warn("[checkBalance] 用户 {} 余额不足,当前余额:{}", userId, balance); + throw new Exception("余额不足"); + } + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/StorageServiceImpl.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/StorageServiceImpl.java new file mode 100644 index 000000000..669d5b050 --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/StorageServiceImpl.java @@ -0,0 +1,52 @@ +package cn.iocoder.springboot.lab52.seatademo.service.impl; + +import cn.iocoder.springboot.lab52.seatademo.dao.ProductDao; +import cn.iocoder.springboot.lab52.seatademo.service.StorageService; +import com.baomidou.dynamic.datasource.annotation.DS; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class StorageServiceImpl implements StorageService { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private ProductDao productDao; + + @Override + @DS(value = "storage-ds") + @Transactional(propagation = Propagation.REQUIRES_NEW) // 开启新事物 + public void reduceStock(Long productId, Integer amount) throws Exception { + logger.info("[reduceStock] 当前 XID: {}", RootContext.getXID()); + + // 检查库存 + checkStock(productId, amount); + + logger.info("[reduceStock] 开始扣减 {} 库存", productId); + // 扣减库存 + int updateCount = productDao.reduceStock(productId, amount); + // 扣除成功 + if (updateCount == 0) { + logger.warn("[reduceStock] 扣除 {} 库存成功", productId); + throw new Exception("库存不足"); + } + // 扣除失败 + logger.info("[reduceStock] 扣除 {} 库存失败", productId); + } + + private void checkStock(Long productId, Integer requiredAmount) throws Exception { + logger.info("[checkStock] 检查 {} 库存", productId); + Integer stock = productDao.getStock(productId); + if (stock < requiredAmount) { + logger.warn("[checkStock] {} 库存不足,当前库存: {}", productId, stock); + throw new Exception("库存不足"); + } + } + +} diff --git a/lab-52/lab-52-multiple-datasource/src/main/resources/application.yaml b/lab-52/lab-52-multiple-datasource/src/main/resources/application.yaml new file mode 100644 index 000000000..e0b7d35da --- /dev/null +++ b/lab-52/lab-52-multiple-datasource/src/main/resources/application.yaml @@ -0,0 +1,49 @@ +server: + port: 8081 # 端口 + +spring: + application: + name: multi-datasource-service # 应用名 + +# cloud: +# alibaba: +# # Seata 配置项,对应 SpringCloudAlibabaConfiguration 类 +# seata: +# tx-service-group: my_test_tx_group + + datasource: + # dynamic-datasource-spring-boot-starter 动态数据源的配配项,对应 DynamicDataSourceProperties 类 + dynamic: + primary: order-ds # 设置默认的数据源或者数据源组,默认值即为 master + datasource: + # 订单 order 数据源配置 + order-ds: + url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8 + driver-class-name: com.mysql.jdbc.Driver + username: root + password: + # 支付 pay 数据源配置 + pay-ds: + url: jdbc:mysql://127.0.0.1:3306/seata_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8 + driver-class-name: com.mysql.jdbc.Driver + username: root + password: + # 库存 storage 数据源配置 + storage-ds: + url: jdbc:mysql://127.0.0.1:3306/seata_storage?useSSL=false&useUnicode=true&characterEncoding=UTF-8 + driver-class-name: com.mysql.jdbc.Driver + username: root + password: + +# Seata 配置项,对应 SeataProperties 类 +seata: + application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name} + tx-service-group: ${spring.application.name}-group # Seata 事务组编号,用于 TC 集群名 + # 服务配置项,对应 ServiceProperties 类 + service: + # 虚拟组和分组的映射 + vgroup-mapping: + multi-datasource-service-group: default + # 分组和 Seata 服务的映射 + grouplist: + default: 127.0.0.1:8091 diff --git a/lab-52/pom.xml b/lab-52/pom.xml new file mode 100644 index 000000000..65e4992df --- /dev/null +++ b/lab-52/pom.xml @@ -0,0 +1,19 @@ + + + + labs-parent + cn.iocoder.springboot.labs + 1.0-SNAPSHOT + + 4.0.0 + + lab-52 + pom + + lab-52-multiple-datasource + + + + diff --git a/pom.xml b/pom.xml index 23cdbc653..aa31dfe8e 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ lab-51 labx-15 labx-16 + lab-52