实战
at模式
business controller
@RestController
@RequestMapping("/business/dubbo")
@Slf4j
public class BusinessController {
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessController.class);
@Autowired
private BusinessService businessService;
/**
* 模拟用户购买商品下单业务逻辑流程
*/
@PostMapping("/buy")
ObjectResponse handleBusiness(@RequestBody BusinessDTO businessDTO) {
LOGGER.info("请求参数:{}", businessDTO.toString());
return businessService.handleBusiness(businessDTO);
}
}
service
@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
public ObjectResponse handleBusiness(BusinessDTO businessDTO) {
System.out.println("开始全局事务,XID = " + RootContext.getXID());
ObjectResponse<Object> objectResponse = new ObjectResponse<>();
// 1、扣减库存
CommodityDTO commodityDTO = new CommodityDTO();
commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
commodityDTO.setCount(businessDTO.getCount());
ObjectResponse stockResponse = stockDubboService.decreaseStock(commodityDTO);
// 2、创建订单
OrderDTO orderDTO = new OrderDTO();
orderDTO.setUserId(businessDTO.getUserId());
orderDTO.setCommodityCode(businessDTO.getCommodityCode());
orderDTO.setOrderCount(businessDTO.getCount());
orderDTO.setOrderAmount(businessDTO.getAmount());
ObjectResponse<OrderDTO> response = orderDubboService.createOrder(orderDTO);
//打开注释测试事务发生异常后,全局回滚功能
// if (!flag) {
// throw new RuntimeException("测试抛异常后,分布式事务回滚!");
// }
if (stockResponse.getStatus() != 200 || response.getStatus() != 200) {
throw new DefaultException(RspStatusEnum.FAIL);
}
objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
objectResponse.setData(response.getData());
return objectResponse;
}
stockDubboService实现
@Service(version = "1.0.0", protocol = "${dubbo.protocol.id}", application = "${dubbo.application.id}",
registry = "${dubbo.registry.id}", timeout = 3000)
public class StockDubboServiceImpl implements StockDubboService {
@Autowired
private ITStockService stockService;
/**
* 扣减库存
*/
@Override
public ObjectResponse decreaseStock(CommodityDTO commodityDTO) {
System.out.println("全局事务id :" + RootContext.getXID());
return stockService.decreaseStock(commodityDTO);
}
}
orderDubboService实现
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
@Reference(version = "1.0.0")
private AccountDubboService accountDubboService;
/**
* 创建订单
*/
@Override
public ObjectResponse<OrderDTO> createOrder(OrderDTO orderDTO) {
ObjectResponse<OrderDTO> response = new ObjectResponse<>();
// 扣减用户账户
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(orderDTO.getUserId());
accountDTO.setAmount(orderDTO.getOrderAmount());
ObjectResponse objectResponse = accountDubboService.decreaseAccount(accountDTO);
// 随机生成订单号
orderDTO.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
// 生成订单
TOrder tOrder = new TOrder();
BeanUtils.copyProperties(orderDTO, tOrder);
tOrder.setCount(orderDTO.getOrderCount());
tOrder.setAmount(orderDTO.getOrderAmount().doubleValue());
try {
baseMapper.createOrder(tOrder);
} catch (Exception e) {
response.setStatus(RspStatusEnum.FAIL.getCode());
response.setMessage(RspStatusEnum.FAIL.getMessage());
return response;
}
if (objectResponse.getStatus() != 200) {
response.setStatus(RspStatusEnum.FAIL.getCode());
response.setMessage(RspStatusEnum.FAIL.getMessage());
return response;
}
response.setStatus(RspStatusEnum.SUCCESS.getCode());
response.setMessage(RspStatusEnum.SUCCESS.getMessage());
return response;
}
}
tcc模式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName">
<!-- 转出账户数据源配置 -->
<bean id="fromAccountDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>com.mysql.cj.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://127.0.0.1:3306/transfer_from_db</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>root</value>
</property>
</bean>
<bean id="fromDsTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="fromAccountDataSource"/>
</bean>
<bean id="fromDsTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="propagationBehaviorName">
<value>PROPAGATION_REQUIRES_NEW</value>
</property>
<property name="transactionManager">
<ref bean="fromDsTransactionManager"/>
</property>
</bean>
<!-- mybatis -->
<bean id="fromDsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="fromAccountDataSource"/>
<property name="configLocation" value="classpath:sqlmap/sqlMapConfig.xml"/>
</bean>
<bean id="fromDsSqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="fromDsSqlSessionFactory"/>
</bean>
<bean id="fromDsSqlMapClientDAO" abstract="true">
<property name="sqlSession">
<ref bean="fromDsSqlSession"/>
</property>
</bean>
<!-- 转出账户 DAO -->
<bean id="fromAccountDAO" class="io.seata.samples.tcc.transfer.dao.impl.AccountDAOImpl" parent="fromDsSqlMapClientDAO"/>
</beans>
/**
* 一阶段准备,转入资金 准备
*/
@Override
public boolean prepareAdd(final BusinessActionContext businessActionContext, final String accountNo, final double amount) {
//分布式事务ID
final String xid = businessActionContext.getXid();
return toDsTransactionTemplate.execute(status -> {
try {
// 校验账户
Account account = toAccountDAO.getAccountForUpdate(accountNo);
if (account == null) {
System.out.println("prepareAdd: 账户[" + accountNo + "]不存在, txId:" + businessActionContext.getXid());
return false;
}
// 待转入资金作为 不可用金额
double freezedAmount = account.getFreezedAmount() + amount;
account.setFreezedAmount(freezedAmount);
toAccountDAO.updateFreezedAmount(account);
System.out.println(String.format("prepareAdd account[%s] amount[%f], dtx transaction id: %s.", accountNo, amount, xid));
return true;
} catch (Throwable t) {
t.printStackTrace();
status.setRollbackOnly();
return false;
}
});
}
/**
* 二阶段提交
*/
@Override
public boolean commit(BusinessActionContext businessActionContext) {
final String xid = businessActionContext.getXid(); // 分布式事务ID
final String accountNo = String.valueOf(businessActionContext.getActionContext("accountNo")); // 账户ID
final double amount = Double.valueOf(String.valueOf(businessActionContext.getActionContext("amount"))); // 转出金额
return toDsTransactionTemplate.execute(status -> {
try {
Account account = toAccountDAO.getAccountForUpdate(accountNo);
double newAmount = account.getAmount() + amount; // 加钱
account.setAmount(newAmount);
account.setFreezedAmount(account.getFreezedAmount() - amount); // 解除冻结金额
toAccountDAO.updateAmount(account);
System.out.println(String.format("add account[%s] amount[%f], dtx transaction id: %s.", accountNo, amount, xid));
return true;
} catch (Throwable t) {
t.printStackTrace();
status.setRollbackOnly();
return false;
}
});
}
/**
* 二阶段回滚
*/
@Override
public boolean rollback(BusinessActionContext businessActionContext) {
final String xid = businessActionContext.getXid(); // 分布式事务ID
final String accountNo = String.valueOf(businessActionContext.getActionContext("accountNo")); // 账户ID
final double amount = Double.valueOf(String.valueOf(businessActionContext.getActionContext("amount"))); // 转出金额
return toDsTransactionTemplate.execute(status -> {
try {
Account account = toAccountDAO.getAccountForUpdate(accountNo);
if (account == null) return true; // 账户不存在, 无需回滚动作
account.setFreezedAmount(account.getFreezedAmount() - amount); // 解除冻结金额
toAccountDAO.updateFreezedAmount(account);
System.out.println(String.format("Undo prepareAdd account[%s] amount[%f], dtx transaction id: %s.", accountNo, amount, xid));
return true;
} catch (Throwable t) {
t.printStackTrace();
status.setRollbackOnly();
return false;
}
});
}