「整合系列」分散式事務seata
本篇文章主要內容是使用Seata解決分散式事務問題。
場景說明
訂單服務order-service需要對外提供建立訂單的介面,建立訂單的業務邏輯如下:
先呼叫本地的orderService儲存訂單操作,然後通過feign呼叫遠端的accout-service進行賬戶餘額扣減,最後再通過feign呼叫遠端的product-service進行庫存扣減操作。
關鍵的邏輯程式碼如下:
order-service
(1)OrderController對外提供建立訂單的介面
@PostMapping("/order/create") public Result<String> create(@RequestBody OrderDTO orderDTO){ log.info("create orderDTO:{}",orderDTO); orderDTO.setOrderNo(UUID.randomUUID().toString()); orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount()))); orderService.createOrder(orderDTO); return Result.success("create order success"); }
OrderDTO如下:
@Data
public class OrderDTO extends Order{
private BigDecimal price;
}
(2)OrderServiceImpl負責處理建立訂單的業務邏輯
public interface OrderService extends IService<Order> { void createOrder(OrderDTO orderDTO); } @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { @Autowired private ProductFeign productFeign; @Autowired private AccountFeign accountFeign; @Transactional(rollbackFor = RuntimeException.class) @Override public void createOrder(OrderDTO orderDTO) { Order order = new Order(); BeanUtils.copyProperties(orderDTO, order); //本地儲存Order this.saveOrUpdate(order); //庫存扣減 productFeign.deduct(orderDTO.getProductCode(), order.getCount()); //賬戶餘額扣減 accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount()); } }
本地先儲存,然後呼叫兩個遠端服務進行扣減操作。
order-feign
這裡我們用到了ProductFeign和AccountFeign的兩個方法。
(1)在ProductFegin中新增方法
@PostMapping("/product/deduct")
Result deduct(@RequestParam("productCode") String productCode, @RequestParam("deductCount") Integer deductCount);
(2)在AccountFeign中新增方法
@PostMapping("/account/reduce") Result<String> reduce(@RequestParam("accountCode")String accountCode, @RequestParam("amount")BigDecimal amount);
product-service
(1)在ProductController中提供一個供ProductFeign呼叫的方法
@PostMapping("/product/deduct")
public Result deduct(String productCode, Integer deductCount){
log.info("deduct product,productCode is {}", productCode);
productService.deduct(productCode, deductCount);
return Result.success("操作成功");
}
(2)ProductServiceImpl負責庫存扣減的邏輯
public interface ProductService extends IService<Product> {
void deduct(String productCode, Integer deductCount);
}
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
@Resource
private ProductMapper productMapper;
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void deduct(String productCode, Integer deductCount) {
Product product = productMapper.selectByCode(productCode);
if(null == product){
throw new RuntimeException("can't deduct product,product is null");
}
int surplus = product.getCount() - deductCount;
if(surplus < 0){
throw new RuntimeException("can't deduct product,product's count is less than deduct count");
}
product.setCount(surplus);
productMapper.updateById(product);
}
}
做些簡單的校驗,當產品庫存不足時不允許扣減操作。
(3)ProductMapper如下:
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
@Select("select * from product where product_code = #{productCode}")
Product selectByCode(String productCode);
}
account-service
(1)在AccountController中提供一個供AccountFeign呼叫的方法
@PostMapping("/account/reduce")
public Result reduce(String accountCode, BigDecimal amount){
log.info("reduce account,accountCode is {}", accountCode);
accountService.reduceAccount(accountCode, amount);
return Result.success("reduceAccount成功");
}
(2)AccountServiceImpl負責賬戶扣減的邏輯
public interface AccountService extends IService<Account> {
void reduceAccount(String accountCode, BigDecimal amount);
}
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Resource
private AccountMapper accountMapper;
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void reduceAccount(String accountCode, BigDecimal amount) {
Account account = accountMapper.selectByCode(accountCode);
if(null == account){
throw new RuntimeException("can't reduce amount,account is null");
}
BigDecimal subAmount = account.getAmount().subtract(amount);
if(subAmount.compareTo(BigDecimal.ZERO) < 0){
throw new RuntimeException("can't reduce amount,account'amount is less than reduce amount");
}
account.setAmount(subAmount);
accountMapper.updateById(account);
}
}
做些簡單的校驗,當賬戶餘額不足的時候不允許扣減操作。
(3)AccountMapper如下:
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
@Select("select * from account where account_code = #{accountCode}")
Account selectByCode(String accountCode);
}
介面測試
入參:
{
"accountCode":"javadaily",
"productCode":"P004",
"count":10,
"price":0.5
}
使用postman呼叫:http://localhost:8040/order/create,結果如下:
檢視account、product、order表,資料都有變化了。
order-service、product-service、account-service分屬不同的服務,當其中一個服務丟擲異常無法提交時就會導致分散式事務,如當使用feign呼叫account-service執行扣減賬戶餘額時,account-service校驗賬戶餘額不足丟擲異常,但是order-service的儲存操作不會回滾;或者是前兩步執行成功但是product-service校驗不通過前面的操作也不會回滾,這就導致了資料不一致,也就是分散式事務問題!