文章目录
有侵入的事务方案
TCC–两个阶段的三个事务操作
- Try–预留资源,冻结
- Confirm–确认资源,使用第一阶段冻结的数据来完成业务数据处理,commit
- Cancel–取消预留的资源,把第一阶段冻结的数据,恢复回去,rollback
1.seata依赖
2.三个配置文件
- application.yml–更改组名
- registry.conf–注册中心地址
- file.conf–组对应使用的协调器
3.修改Mapper,添加新的数据操作
orderMapper:
package cn.tedu.mapper;
import cn.tedu.entity.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface OrderMapper extends BaseMapper<Order> {
void create(Order order);
//插入冻结订单
void insertFrozen(Order order);
//修改订单状态
void updateStatus(Long orderId,Integer status);
//删除订单
}
修改映射文件,添加sql语句
<insert id="insertFrozen">
INSERT INTO `order` (`id`,`user_id`,`product_id`,`count`,`money`,`status`)
VALUES(#{id}, #{userId}, #{productId}, #{count}, #{money},0);
</insert>
<update id="updateStatus">
update `order` set status=#{status} where id=#{orderId}
</update>
<delete id="deleteById">
delete from `order` where identity =#{orderId}
</delete>
4.按照seata的规则,添加TccAction接口和实现,使用Mapper完成TCC数据库操作
接口
package cn.tedu.tcc;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
/*
* tcc操作接口
* 定义三个操作的方法
* */
@LocalTCC
public interface OrderTccActrion {
@TwoPhaseBusinessAction(
name = "OrderTccActrion",
commitMethod = "commit",
rollbackMethod = "rollback")//T方法注解,名字随意起
boolean prepare(BusinessActionContext ctx,
@BusinessActionContextParameter(paramName = "orderId") Long orderId,//将此属性放入ctx中
Long userId,
Long productId,
Integer count,
BigDecimal money);//为了避开seata的一个bug,不传order封装对象,而是把数据打散,一个个的单独传递
boolean commit(BusinessActionContext ctx);
boolean rollback(BusinessActionContext ctx);
}
实现类
@Component
public class OrderTccActionImpl implements OrderTccAction {
@Autowired
private OrderMapper orderMapper;
@Transactional
@Override
public boolean prepare(BusinessActionContext ctx, Long orderId, Long userId, Long productId, Integer count, BigDecimal money) {
orderMapper.insertFrozen(
new Order(orderId,userId,productId,count,money,0)
);
return true;
}
@Transactional
@Override
public boolean commit(BusinessActionContext ctx) {
Long orderId = Long.valueOf(ctx.getActionContext("orderId").toString());//orderId类型消失,需要重新变成Long
orderMapper.updateStatus(orderId,1);
return true;
}
@Transactional
@Override
public boolean rollback(BusinessActionContext ctx) {
Long orderId = Long.valueOf(ctx.getActionContext("orderId").toString());//orderId类型消失,需要重新变成Long
orderMapper.updateStatus(orderId,1)
return true;
}
}
5.修改业务方法,调用TccAction的第一阶段方法来冻结数据
OrderServiceImpl:
// orderMapper.create(order);
//手动调用第一阶段,冻结订单
//第二阶段的两个方法,是由seata的rm自动调用
tcc.prepare(null,
order.getId(),
order.getUserId(),
order.getProductId(),
order.getCount(),
order.getMoney());
6.在第一个模块上添加:@GlobalTransactional 启动全局事务
@GlobalTransactional
@Override
public void create(Order order) {
// TODO: 从全局唯一id发号器获得id
String s = easyIdClient.getId("order_business");
Long orderId = Long.valueOf(s);
order.setId(orderId);
// orderMapper.create(order);
//手动调用第一阶段,冻结订单
//第二阶段的两个方法,是由seata的rm自动调用
tcc.prepare(null,
order.getId(),
order.getUserId(),
order.getProductId(),
order.getCount(),
order.getMoney());
子模块
storage模块
1.Mapper映射文件
<select id="selectByProductId" resultMap="BaseResultMap">
select * from storage where product_id = #{productId}
</select>
<update id="updateResidueToFrozen">
update storage
set Residue=Residue-#{count},Frozen=Frozen+#{count}
where product_id = #{productId}
</update>
<update id="updateFrozenToUsed">
update storage
set Frozen = Frozen - #{count},Used = Used+ #{count}
where product_id = #{productId}
</update>
<update id="updateFrozenToResidue">
update storage
set Frozen = Frozen - #{count},Residue = Residue+ #{count}
where product_id = #{productId}
</update>
2.Mapper:
// 查询库存
Storage selectByProductId(Long productId);
// 可用 ---> 冻结
void updateResidueToFrozen(Long productId, Integer count);
// 冻结 ---> 已使用
void updateFrozenToUsed(Long productId, Integer count);
// 冻结 ---> 可用
void updateFrozenToResidue(Long productId, Integer count);
3.Service实现类
@Service
public class StorageServiceImpl implements StorageService {
@Autowired
private StorageTccAction tcc;
@Override
public void decrease(Long productId, Integer count) {
// 不直接直接减库存的业务,而是冻结库存
tcc.prepare(null, productId, count);
}
}
4.TCC
接口:
@LocalTCC
public interface StorageTccAction {
@TwoPhaseBusinessAction(name = "StorageTccAction")
boolean prepare(BusinessActionContext ctx,
@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count);
boolean commit(BusinessActionContext ctx);
boolean rollback(BusinessActionContext ctx);
}
实现类
@Component
public class StorageTccActionImpl implements StorageTccAction {
@Autowired
private StorageMapper storageMapper;
@Transactional
@Override
public boolean prepare(BusinessActionContext ctx, Long productId, Integer count) {
// 查询库存判断有没有足够的库存
Storage storage = storageMapper.selectByProductId(productId);
if (storage.getResidue() < count) {
throw new RuntimeException("库存不足");
}
// 冻结库存
storageMapper.updateResidueToFrozen(productId, count);
return true;
}
@Transactional
@Override
public boolean commit(BusinessActionContext ctx) {
Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
storageMapper.updateFrozenToUsed(productId, count);
return true;
}
@Transactional
@Override
public boolean rollback(BusinessActionContext ctx) {
Long productId = Long.valueOf(ctx.getActionContext("productId").toString());
Integer count = Integer.valueOf(ctx.getActionContext("count").toString());
storageMapper.updateFrozenToResidue(productId, count);
return true;
}
}