package com.example.awesomebusiness.api; import com.example.accountapi.api.WalletApi; import org.springframework.cloud.openfeign.FeignClient; /** * account:注册的服务名称; * contextId:注册到spring中的Bean名称,保证唯一性 * path:对应服务端的requestMapping里面的value值,没有的话,可不写 * * @author zouwei * @className AccountApiClient * @date: 2022/9/18 14:42 * @description: */ @FeignClient(name = "account", contextId = "walletApi", path = "/wallet") public interface WalletApiClient extends WalletApi { } 复制代码
package com.example.awesomebusiness.api; import com.example.orderapi.api.OrderApi; import org.springframework.cloud.openfeign.FeignClient; /** * @author zouwei * @className OrderApiClient * @date: 2022/9/27 23:24 * @description: */ @FeignClient(name = "order", contextId = "orderApi", path = "/order") public interface OrderApiClient extends OrderApi { } 复制代码
- 接下来看看
awesome-account
的业务逻辑:
package com.example.awesomeaccount.service.impl; import com.example.awesomeaccount.dao.mapper.WalletEnhanceMapper; import com.example.awesomeaccount.service.IWalletService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * @author zouwei * @className WalletServiceImpl * @date: 2022/9/27 21:20 * @description: */ @Service public class WalletServiceImpl implements IWalletService { @Resource private WalletEnhanceMapper walletEnhanceMapper; @Transactional @Override public Boolean deductMoney(String userId, long amount) { // 判断修改后的影响行数是否大于0 // 对应sql:update wallet_tbl set money = money - #{amount,jdbcType=INTEGER} where user_id = #{userId,jdbcType=VARCHAR} and money <![CDATA[ >= ]]>#{amount,jdbcType=INTEGER} // WalletEnhanceMapper属于自定义的sql mapper,没有写在WalletMapper中,是因为避免重新生成Mapper的时候,自定义sql被覆盖 return walletEnhanceMapper.deductMoney(userId, amount) > 0; } } 复制代码
对应的controller如下:
package com.example.awesomeaccount.controller; import com.example.accountapi.api.WalletApi; import com.example.accountapi.model.AmountInfo; import com.example.awesomeaccount.service.IWalletService; import io.seata.core.context.RootContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * WalletApi接口在account-api模块中 * * @author zouwei * @className WalletController * @date: 2022/9/18 14:58 * @description: */ @Slf4j @RestController @RequestMapping("/wallet") public class WalletController implements WalletApi { @Autowired private IWalletService walletService; @Override public Boolean deductMoney(AmountInfo amountInfo) { String userId = amountInfo.getUserId(); long amount = amountInfo.getAmount(); // 打印分布式事务XID log.warn("XID:{}", RootContext.getXID()); // 扣款 return walletService.deductMoney(userId, amount); } } 复制代码
package com.example.accountapi.api; import com.example.accountapi.model.AmountInfo; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; /** * 供生产者和消费者共同使用 * * @author zouwei * @className AccountApi * @date: 2022/9/18 14:31 * @description: */ public interface WalletApi { /** * 扣钱 * * @param amountInfo * @return */ @PostMapping("/deductMoney") Boolean deductMoney(@RequestBody AmountInfo amountInfo); } 复制代码
awesome-order
与awesome-storage
服务也是类似的代码结构:
package com.example.awesomeorder.service.impl; import com.example.awesomeorder.api.StockApiClient; import com.example.awesomeorder.dao.entity.Order; import com.example.awesomeorder.dao.mapper.OrderMapper; import com.example.awesomeorder.service.IOrderService; import com.example.storageapi.model.OrderInfo; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * @author zouwei * @className OrderServiceImpl * @date: 2022/9/27 22:47 * @description: */ @Service public class OrderServiceImpl implements IOrderService { @Resource private OrderMapper orderMapper; @Resource private StockApiClient stockApiClient; /** * 创建订单 * * @param userId * @param commodityCode * @param count * @param unitPrice * @return */ @Transactional @Override public Boolean createOrder(String userId, String commodityCode, int count, long unitPrice) { // 构建待扣减的库存信息 OrderInfo orderInfo = new OrderInfo(); // 设置商品编码 orderInfo.setCommodityCode(commodityCode); // 设置需要扣减的数量 orderInfo.setCount(count); // 先构建库存 if (stockApiClient.deductStock(orderInfo)) { // 扣减库存成功后,准备创建订单 Order order = new Order(); // 创建时间 order.setCreateTime(LocalDateTime.now()); // 用户ID order.setUserId(userId); // 数量 order.setCount(count); // 商品编码 order.setCommodityCode(commodityCode); // 单价 order.setUnitPrice(unitPrice); // 创建订单 return orderMapper.insert(order) > 0; } // 扣减库存失败,订单创建失败 return Boolean.FALSE; } } 复制代码
package com.example.awesomeorder.api; import com.example.storageapi.api.StockApi; import org.springframework.cloud.openfeign.FeignClient; /** * @author zouwei * @className StockApiClient * @date: 2022/9/28 00:34 * @description: */ @FeignClient(name = "storage", contextId = "stockApi", path = "/stock") public interface StockApiClient extends StockApi { } 复制代码
package com.example.awesomeorder.controller; import com.example.awesomeorder.service.IOrderService; import com.example.orderapi.api.OrderApi; import com.example.orderapi.model.OrderInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zouwei * @className OrderController * @date: 2022/9/27 22:55 * @description: */ @RestController @RequestMapping("/order") public class OrderController implements OrderApi { @Autowired private IOrderService orderService; @Override public Boolean createOrder(OrderInfo orderInfo) { String commodityCode = orderInfo.getCommodityCode(); int count = orderInfo.getCount(); long unitPrice = orderInfo.getUnitPrice(); String userId = orderInfo.getUserId(); return orderService.createOrder(userId, commodityCode, count, unitPrice); } } package com.example.orderapi.api; import com.example.orderapi.model.OrderInfo; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; /** * @author zouwei * @className OrderApi * @date: 2022/9/27 22:57 * @description: */ public interface OrderApi { @PutMapping("/create") Boolean createOrder(@RequestBody OrderInfo orderInfo); } 复制代码
- 接下来,就剩下
awesome-storage
服务了:
package com.example.awesomestorage.service.impl; import com.example.awesomestorage.dao.mapper.StockEnhanceMapper; import com.example.awesomestorage.service.IStockService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * @author zouwei * @className StockServiceImpl * @date: 2022/9/28 00:06 * @description: */ @Service public class StockServiceImpl implements IStockService { @Resource private StockEnhanceMapper stockEnhanceMapper; @Transactional @Override public Boolean deductStock(String commodityCode, int count) { // 扣减库存,判断影响行数是否大于0 // sql如下: update stock_tbl set count = count - #{count,jdbcType=INTEGER} where commodity_code = #{commodityCode,jdbcType=VARCHAR} and count <![CDATA[ >= ]]> #{count,jdbcType=INTEGER} return stockEnhanceMapper.deductStock(commodityCode, count) > 0; } } 复制代码
package com.example.awesomestorage.controller; import com.example.awesomestorage.service.IStockService; import com.example.storageapi.api.StockApi; import com.example.storageapi.model.OrderInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zouwei * @className StockController * @date: 2022/9/28 00:23 * @description: */ @RestController @RequestMapping("/stock") public class StockController implements StockApi { @Autowired private IStockService stockService; @Override public Boolean deductStock(OrderInfo orderInfo) { String commodityCode = orderInfo.getCommodityCode(); int count = orderInfo.getCount(); return stockService.deductStock(commodityCode, count); } } package com.example.storageapi.api; import com.example.storageapi.model.OrderInfo; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; /** * @author zouwei * @className StockApi * @date: 2022/9/28 00:26 * @description: */ public interface StockApi { @PostMapping("/deduct") Boolean deductStock(@RequestBody OrderInfo orderInfo); } 复制代码
至此,这几个服务的核心代码就全部搞定,我们千万别忘记了在项目入口类中加上对应的注解,比如awesome-business
入口类:
package com.example.awesomebusiness; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; // 开启nacos,不过仅仅使用服务发现,不需要注册 @EnableDiscoveryClient(autoRegister = false) // 开启OpenFeign @EnableFeignClients @SpringBootApplication public class AwesomeBusinessApplication { public static void main(String[] args) { SpringApplication.run(AwesomeBusinessApplication.class, args); } } 复制代码
awesome-order
的入口类有些许不同:
package com.example.awesomeorder; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; // 增加mybatis的MapperScan注解 @MapperScan(basePackages = "com.example.awesomeorder.dao.mapper") // RPC调用才需要OpenFeign @EnableFeignClients // 既要注册当前服务,也要发现其他服务,比如storage库存服务 @EnableDiscoveryClient @SpringBootApplication public class AwesomeOrderApplication { public static void main(String[] args) { SpringApplication.run(AwesomeOrderApplication.class, args); } } 复制代码
如果小伙伴们对于mybatis的配置有疑虑的话,可以查看博客:手把手教你springboot集成mybatis;另外我怕也会将当前项目代码上传到github,想了解的小伙伴可以自己下载:awesome-seata。
小结
通过以上示例,我们需要了解到以下几点,才能帮助我们更好地继承seata AT模式到我们的项目当中:
1.TM只负责全局事务的管理,比如全局事务的开启、提交或回滚;
2.TM可以和RM共存,也就是说,一个服务中,可以既有TM,也有RM;
3.RM只负责分支事务的管理,比如分支事务的注册、上报、提交或回滚;
4.多个分支事务是否在同一个分布式事务当中,是通过XID来判断的,XID是通过RPC来传递的;
5.TC相当于一个传话筒和记事本,负责记录和维护全局事务和分支事务的状态,以及下发TM提交或回滚的指令给到RM;
6.在RM当中是不需要使用@GlobalTransactional注解的,只需要使用@Transactional注解来保证分支事务的原子性;
7.所有的RM服务都需要有undo_log数据表,这样才能使用AT模式;