Springboot+nacos+seata实现简单的分布式事务
上一篇文章把三个服务都注册进nacos中了,这次就开始写业务代码
首先先创建三个数据库,每个数据库都需要有一张记录表,一张回滚表
order表
CREATE TABLE `t_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL ,
`product_id` bigint DEFAULT NULL ,
`count` int DEFAULT NULL ,
`money` decimal(11,0) DEFAULT NULL ,
`status` int DEFAULT NULL ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
storage表
CREATE TABLE `t_storage` (
`id` bigint NOT NULL AUTO_INCREMENT,
`product_id` bigint DEFAULT NULL,
`total` int DEFAULT NULL,
`used` int DEFAULT NULL,
`residue` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
--在表中插入一条数据
INSERT INTO `t_storage` VALUES ('1', '1', '100', '0', '100');
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';
account表
CREATE TABLE `t_account` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL,
`total` decimal(10,0) DEFAULT NULL,
`used` decimal(10,0) DEFAULT NULL,
`residue` decimal(10,0) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
--插入一条数据
INSERT INTO `t_account` VALUES ('1', '1', '1000', '0', '1000');
CREATE TABLE `undo_log` (
`branch_id` bigint NOT NULL COMMENT 'branch transaction id',
`xid` varchar(128) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
到此,三个数据库六张表建好了下面写代码
order服务
新建order实体
public class Order {
private int id;
private int userId;
private int productId;
private int count;
private int money;
private int status;
}
//我这自己创建了get,set方法
OrderMapper类,用于操作数据库
@Mapper
public interface OrderMapper {
@Insert("insert into t_order(user_id,product_id,count,money,status) values(#{userId},#{productId},#{count},#{money},#{status})")
int create(Order order);
@Update("update t_order set status = #{status} where user_id = 1;")
void update(int status);
}
StorageService接口用于使用openfeign调用另一个服务
//@FeignClient:将远程服务xxx映射为一个本地Java方法调用
@FeignClient(name = "seataStorage")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
int decrease(@RequestParam("count") Object count,@RequestParam("XID") String XID);
}
/**
*count参数是自己定义的用于模拟发生条件不成立而回滚,XID就是TC 针对这个全局事务生成一个全局唯一的 XID
*XID参数必须传,如果不传的话Storage服务不会回滚
*/
AccountService接口用于使用openfeign调用另一个服务
@FeignClient(name = "seataAccount")
public interface AccountService {
@PostMapping(value = "/account/decrease")
int decrease(@RequestParam("money") Object money , @RequestParam("XID") String XID);
}
OrderService接口及其实现类
public interface OrderService {
int create(Order order);
}
@Service
public class OrderImpl implements OrderService {
@Autowired
OrderMapper orderMapper;
@Resource
AccountService accountService;
@Resource
StorageService storageService;
@Override
//这个注解表示是一个分布式事务,在这个注解包括的代码块中,不管事本服务的sql还是feign调用的服务的sql都会被seata增强
@GlobalTransactional(name = "service.vgroupMapping.mytest", rollbackFor = Exception.class)
public int create(Order order) {
order.setUserId(1);
order.setStatus(0);
orderMapper.create(order);
System.out.println(RootContext.getXID());
storageService.decrease(order.getCount(),RootContext.getXID());
accountService.decrease(order.getMoney(),RootContext.getXID());
orderMapper.update(0);
return 1;
}
}
OrderController类用于接口调用
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("/test")
public void create(int count , int money) {
Order order = new Order();
order.setProductId(100);
order.setCount(count);
order.setMoney(money);
orderService.create(order);
}
}
启动类
@SpringBootApplication
//让注册中心能够发现,扫描到该服务
@EnableDiscoveryClient
//告诉框架扫描所有使用注解@FeignClient定义的feign客户端
@EnableFeignClients(basePackages = "com.example.springdemoOrder")
public class Springdemo1Application {
public static void main(String[] args) {
SpringApplication.run(Springdemo1Application.class, args);
}
}
Storage服务
新建order实体
public class Storage {
private int id;
private int productId;
private int total;
private int used;
private int residue;
//自己建get,set方法
}
StorageMapper接口
@Mapper
public interface StorageMapper {
@Results(id = "storage" , value = {
@Result(property = "id" , column = "id" , id = true),
@Result(property = "productId" , column = "product_id"),
@Result(property = "total" , column = "total"),
@Result(property = "used" , column = "used"),
@Result(property = "residue" , column = "residue")
})
@Select("select * from t_storage where id = #{id}")
Storage selectByProductId(int id);
@Update("update t_storage set total = #{total},used = #{used} , residue = #{residue} where id = #{id}")
int update(Storage record);
}
StorageService接口及其实现类
public interface StorageService {
int decrease(Object count);
}
@Service
public class StorageServiceImpl implements StorageService {
@Autowired
StorageMapper storageMapper;
@Override
@Transactional
public int decrease(Object count) {
Storage storage = storageMapper.selectByProductId(1);
int co = (int) count;
if (storage != null && storage.getResidue().intValue() >= co) {
Storage storage2 = new Storage();
storage2.setId(1);
storage.setUsed(storage.getUsed() + co);
storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
int decrease = storageMapper.update(storage);
System.out.println("成功");
return decrease;
} else {
System.out.println("开始回滚!");
throw new RuntimeException("失败!");
}
}
}
StorageController类
@RestController
@RequestMapping("/storage")
public class StorageController {
@Autowired
StorageService storageService;
@PostMapping("/decrease")
public int test(@RequestParam("count") String count, @RequestParam("XID") String XID){
int num = Integer.valueOf(count);
RootContext.bind(XID);
System.out.println(RootContext.getXID());
int decrease = storageService.decrease(num);
int result;
if (decrease > 0) {
result = 1;
} else {
result = 0;
}
return result;
}
}
Account服务
Account实体
public class Account {
private int id;
private int userId;
private int total;
private int used;
private int residue;
}
AccountMapper接口
@Mapper
public interface AccountMapper {
@Results(id = "acount",value = {
@Result(property = "id",column = "id",id = true),
@Result(property = "userId",column = "user_id"),
@Result(property = "total",column = "total"),
@Result(property = "used",column = "used"),
@Result(property = "residue",column = "residue")
})
@Select("select * from t_account where id = 1")
Account selectByUserId();
@Update("update t_account set used = #{used} , residue = #{residue} where id = 1")
int decrease(Account account);
}
ACcountService接口及其实现类
public interface AccountService {
int decrease(Object money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountMapper accountDao;
@Override
@Transactional
public int decrease(Object money) {
Account account = accountDao.selectByUserId();
int used = account.getUsed();
int residue = account.getResidue();
int mo = (int) money;
//如果传过来的参数小于数据库的数据就回滚
if (mo<residue){
account.setUsed(used+mo);
account.setResidue(residue-mo);
accountDao.decrease(account);
}else {
throw new RuntimeException("错误");
}
return 1;
}
}
AccountController类
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
AccountService account;
@PostMapping("/decrease")
public int test(@RequestParam("money") String money ,@RequestParam("XID") String XID){
RootContext.bind(XID);
System.out.println(RootContext.getXID());
int m = Integer.valueOf(money);
int decrease = account.decrease(m);
int result;
if (decrease==1){
result = 1;
}else {
result = 0;
}
return result;
}
}
接下来启动三个服务试试,然后调用接口试试正常的情况
storage表
account表
接下来我们模拟回滚
参数:
然后发现主服务抛出错误
但是我们也没法知道到底发没发生回滚啊,去idea打个断点试试,因为我们是模拟的account服务抛出错误,所以在主服务的OrderImpl打个断点试试
进入断点
Order表已经插入数据了
Order表的回滚表
Storage表也修改了数据
Storage表的回滚表
断点去了之后,因为account服务抛出错误,所以开始回滚
Order表
Storage表
到这里就结束了,代码地址https://github.com/Melons-skin/seata-nacos-springboot-