一、hmily框架
1、到github拉取最新的源码
idea:File - New - Project form Version Control
2、切换release版本
此时拉下来的是master版本,我们就用master分支吧。在右下角可以选择分支,比如选择分支2.2.1版本
3、pom.xml中添加hmily-demo
<modules>
<module>hmily-common</module>
<module>hmily-core</module>
<module>hmily-annotation</module>
<module>hmily-spring-boot-starter</module>
<module>hmily-spi</module>
<module>hmily-serializer</module>
<module>hmily-repository</module>
<module>hmily-config</module>
<module>hmily-spring</module>
<module>hmily-rpc</module>
<module>hmily-tcc</module>
<module>hmily-tac</module>
<module>hmily-metrics</module>
<module>hmily-bom</module>
<module>hmily-all</module>
<module>hmily-xa</module>
<module>hmily-demo</module>
</modules>
4、初始化sql
hmily-demo/sql/hmily-demo.sql
建立库:
hmily:框架自带的库,事务的管理数据库
hmily_account:账户
hmily_order:订单
hmily_stock:库存
5、测试项目路径
hmily-demo/hmily-demo-tcc/hmily-demo-tcc-springcloud
hmily-demo-tcc-springcloud-account
hmily-demo-tcc-springcloud-eureka
hmily-demo-tcc-springcloud-inventory
hmily-demo-tcc-springcloud-order
6、业务说明
下订单的同时,减库存,扣余额。这三个数据库要么同时提交,要么同时回滚。
7、修改数据库配置
hmily-demo-tcc-springcloud-order
application.yml文件:
spring:
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmily_order?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
application:
name: order-service
hmily.yml文件:
repository:
database:
driverClassName: com.mysql.jdbc.Driver
url : jdbc:mysql://127.0.0.1:3306/hmily?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
maxActive: 20
minIdle: 10
connectionTimeout: 30000
idleTimeout: 600000
maxLifetime: 1800000
hmily-demo-tcc-springcloud-account、hmily-demo-tcc-springcloud-inventory做相同修改。
8、启动服务
先起eureka
再起order
再起inventory
再起account
9、体验
http://127.0.0.1:8090/swagger-ui.html
测试/order/orderPay
10、查询数据库
账户:
mysql> select * from hmily_account.account;
+----+---------+---------+---------------+---------------------+---------------------+
| id | user_id | balance | freeze_amount | create_time | update_time |
+----+---------+---------+---------------+---------------------+---------------------+
| 1 | 10000 | 9999991 | 0 | 2017-09-18 14:54:22 | 2022-04-27 14:08:03 |
+----+---------+---------+---------------+---------------------+---------------------+
1 row in set (0.00 sec)
订单:
mysql> select * from hmily_order.order;
+----+---------------------+----------------------+--------+------------+--------------+-------+---------+
| id | create_time | number | status | product_id | total_amount | count | user_id |
+----+---------------------+----------------------+--------+------------+--------------+-------+---------+
| 1 | 2022-04-27 14:08:03 | -6725939227652632576 | 4 | 1 | 9 | 1 | 10000 |
+----+---------------------+----------------------+--------+------------+--------------+-------+---------+
1 row in set (0.00 sec)
库存:
mysql> select * from hmily_stock.inventory;
+----+------------+-----------------+----------------+
| id | product_id | total_inventory | lock_inventory |
+----+------------+-----------------+----------------+
| 1 | 1 | 9999999 | 0 |
+----+------------+-----------------+----------------+
1 row in set (0.00 sec)
11、hmily库
+-------------------------------+
| Tables_in_hmily |
+-------------------------------+
| hmily_lock |
| hmily_participant_undo |
| hmily_transaction_global |
| hmily_transaction_participant |
+-------------------------------+
4 rows in set (0.00 sec)
二、分布式事务订单支付的原理
1、try
第一步:
try:插入一条订单数据,状态为支付中
@Override
@HmilyTCC(confirmMethod = "confirmOrderStatus", cancelMethod = "cancelOrderStatus")
public void makePayment(Order order) {
updateOrderStatus(order, OrderStatusEnum.PAYING);
// //检查数据
// final BigDecimal accountInfo = accountClient.findByUserId(order.getUserId());
// final Integer inventoryInfo = inventoryClient.findByProductId(order.getProductId());
// if (accountInfo.compareTo(order.getTotalAmount()) < 0) {
// throw new HmilyRuntimeException("余额不足!");
// }
// if (inventoryInfo < order.getCount()) {
// throw new HmilyRuntimeException("库存不足!");
// }
accountClient.payment(buildAccountDTO(order));
inventoryClient.decrease(buildInventoryDTO(order));
}
第二步:rpc调用,减账户
try:账户余额 - 消耗,冻结余额 + 消耗
@Override
@HmilyTCC(confirmMethod = "confirm", cancelMethod = "cancel")
public boolean payment(final AccountDTO accountDTO) {
LOGGER.info("============执行try付款接口===============");
accountMapper.update(accountDTO);
return Boolean.TRUE;
}
@Update("update account set balance = balance - #{amount}," +
" freeze_amount= freeze_amount + #{amount} ,update_time = now()" +
" where user_id =#{userId} and balance >= #{amount} ")
int update(AccountDTO accountDTO);
第三步:rpc调用,减库存
try:总库存 - 支出,冻结库存 + 支出
/**
* 扣减库存操作.
* 这一个tcc接口
*
* @param inventoryDTO 库存DTO对象
* @return true
*/
@Override
@HmilyTCC(confirmMethod = "confirmMethod", cancelMethod = "cancelMethod")
public Boolean decrease(InventoryDTO inventoryDTO) {
LOGGER.info("==========try扣减库存decrease===========");
inventoryMapper.decrease(inventoryDTO);
return true;
}
@Update("update inventory set total_inventory = total_inventory - #{count}," +
" lock_inventory= lock_inventory + #{count} " +
" where product_id =#{productId} and total_inventory > 0 ")
int decrease(InventoryDTO inventoryDTO);
2、事务管理器
事务管理器把所有的事务记录到hmily库。
事务管理器调用confirm、cancel。异步确认或异步取消。
3、confirm
订单:
confirm:订单状态改为支付成功
public void confirmOrderStatus(Order order) {
updateOrderStatus(order, OrderStatusEnum.PAY_SUCCESS);
LOGGER.info("=========进行订单confirm操作完成================");
}
账户:
confirm:冻结余额 - 消耗
public boolean confirm(final AccountDTO accountDTO) {
LOGGER.info("============执行confirm 付款接口===============");
return accountMapper.confirm(accountDTO) > 0;
}
@Update("update account set " +
" freeze_amount= freeze_amount - #{amount}" +
" where user_id =#{userId} and freeze_amount >= #{amount} ")
int confirm(AccountDTO accountDTO);
库存:
confirm:冻结库存 - 支出
public Boolean confirmMethod(InventoryDTO inventoryDTO) {
LOGGER.info("==========confirmMethod库存确认方法===========");
return inventoryMapper.confirm(inventoryDTO) > 0;
}
@Update("update inventory set " +
" lock_inventory = lock_inventory - #{count} " +
" where product_id =#{productId} and lock_inventory > 0 ")
int confirm(InventoryDTO inventoryDTO);
4、cancel
订单:
cancel:订单状态改为支付失败
public void cancelOrderStatus(Order order) {
updateOrderStatus(order, OrderStatusEnum.PAY_FAIL);
LOGGER.info("=========进行订单cancel操作完成================");
}
账户:
cancel:账户余额 + 消耗,冻结余额 - 消耗
public boolean cancel(final AccountDTO accountDTO) {
LOGGER.info("============执行cancel 付款接口===============");
return accountMapper.cancel(accountDTO) > 0;
}
@Update("update account set balance = balance + #{amount}," +
" freeze_amount= freeze_amount - #{amount} " +
" where user_id =#{userId} and freeze_amount >= #{amount}")
int cancel(AccountDTO accountDTO);
库存:
cancel:总库存 + 支出,冻结库存 - 支出
public Boolean cancelMethod(InventoryDTO inventoryDTO) {
LOGGER.info("==========cancelMethod库存取消方法===========");
return inventoryMapper.cancel(inventoryDTO) > 0;
}
@Update("update inventory set total_inventory = total_inventory + #{count}," +
" lock_inventory= lock_inventory - #{count} " +
" where product_id =#{productId} and lock_inventory > 0 ")
int cancel(InventoryDTO inventoryDTO);
5、小结
6、hmily-admin
master分支最新代码,去除了这个模块
三、txx-transaction和例子
1、到github拉取源码
https://github.com/changmingxie/tcc-transaction.git
2、选择master分支
3、初始化数据库
tcc-transaction-tutorial-sample/src/dbscripts
create_db_tcc.sql:事务管理器初始化数据
create_db_red.sql:红包账户数据库
create_db_ord.sql:订单数据库
create_db_cap.sql:账户余额数据库
创建了4个库:
tcc
tcc_cap
tcc_ord
tcc_red
4、txx-transaction例子示例图
5、改配置
tcc-transaction/tcc-transaction-tutorial-sample/pom.xml
<modules>
<!-- <module>tcc-transaction-dubbo-sample</module>-->
<module>tcc-transaction-http-sample</module>
<module>tcc-transaction-sample-domain</module>
<module>tcc-transaction-multiple-tier-sample</module>
</modules>
使用的模块:
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-capital:账户余额
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-order:订单
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-redpacket:红包
6、改jdbc连接
没找到
经排查,例子里数据库链接用的是h2内存数据库,在tcc-transaction-sample-domain模块里
7、部署tomcat
8、体验
地址:http://localhost:8080/o/
(1)第一步:点击商品列表链接
(2)第二步:点击商品
(3)第三步:输入红包金额
(4)第四步:点击支付
(5)日志信息
capital try record called. time seq:2022-05-16 15:17:22
red packet try record called. time seq:2022-05-16 15:17:23
order confirm make payment called. time seq:2022-05-16 15:17:24
capital confirm record called. time seq:2022-05-16 15:17:25
red packet confirm record called. time seq:2022-05-16 15:17:26
四、txx-transaction组合支付原理
1、入口
@RequestMapping(value = "/placeorder", method = RequestMethod.POST)
public RedirectView placeOrder(@RequestParam String redPacketPayAmount,
@RequestParam long shopId,
@RequestParam long payerUserId,
@RequestParam long productId) {
PlaceOrderRequest request = buildRequest(redPacketPayAmount, shopId, payerUserId, productId);
String merchantOrderNo = placeOrderService.placeOrder(request.getPayerUserId(), request.getShopId(),
request.getProductQuantities(), request.getRedPacketPayAmount());
return new RedirectView("payresult/" + merchantOrderNo);
}
2、创建订单
public String placeOrder(long payerUserId, long shopId, List<Pair<Long, Integer>> productQuantities, BigDecimal redPacketPayAmount) {
Shop shop = shopRepository.findById(shopId);
Order order = orderService.createOrder(payerUserId, shop.getOwnerUserId(), productQuantities);
order.needToPay(redPacketPayAmount,order.getTotalAmount().subtract(redPacketPayAmount));
orderService.update(order);
Boolean result = false;
try {
paymentService.makePayment(order.getMerchantOrderNo());
} catch (ConfirmingException confirmingException) {
//exception throws with the tcc transaction status is CONFIRMING,
//when tcc transaction is confirming status,
// the tcc transaction recovery will try to confirm the whole transaction to ensure eventually consistent.
result = true;
} catch (CancellingException cancellingException) {
//exception throws with the tcc transaction status is CANCELLING,
//when tcc transaction is under CANCELLING status,
// the tcc transaction recovery will try to cancel the whole transaction to ensure eventually consistent.
} catch (Throwable e) {
//other exceptions throws at TRYING stage.
//you can retry or cancel the operation.
e.printStackTrace();
}
return order.getMerchantOrderNo();
}
3、使用了tcc的注解
@Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment", asyncConfirm = true)
@Transactional
public void makePayment(String orderNo) {
System.out.println("order try make payment called.time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
Order order = orderRepository.findByMerchantOrderNo(orderNo);
String result = tradeOrderServiceProxy.record(buildCapitalTradeOrderDto(order));
String result2 = tradeOrderServiceProxy.record(buildRedPacketTradeOrderDto(order));
// String result = tradeOrderServiceProxy.record(null,buildCapitalTradeOrderDto(order));
// String result2 = tradeOrderServiceProxy.record(null,buildRedPacketTradeOrderDto(order));
}
4、红包接口,也是tcc,先冻结,再提交
@Override
@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord")
@Transactional
public String record(TransactionContext transactionContext, RedPacketTradeOrderDto tradeOrderDto) {
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("red packet try record called. time seq:" + DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
TradeOrder foundTradeOrder = tradeOrderRepository.findByMerchantOrderNo(tradeOrderDto.getMerchantOrderNo());
//check if the trade order has need recorded.
//if record, then this method call return success directly.
if (foundTradeOrder == null) {
TradeOrder tradeOrder = new TradeOrder(
tradeOrderDto.getSelfUserId(),
tradeOrderDto.getOppositeUserId(),
tradeOrderDto.getMerchantOrderNo(),
tradeOrderDto.getAmount()
);
try {
tradeOrderRepository.insert(tradeOrder);
RedPacketAccount transferFromAccount = redPacketAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
transferFromAccount.transferFrom(tradeOrderDto.getAmount());
redPacketAccountRepository.save(transferFromAccount);
} catch (DataIntegrityViolationException e) {
}
}
return "success";
}