# 第2章      分布式事务框架

# 1 分布式事务实战-seata的AT模式

### 1.2.3 项目简介

#### 1）数据库表结构

**order库**

```sql
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `order_info`
-- ----------------------------
DROP TABLE IF EXISTS `order_info`;
CREATE TABLE `order_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `money` bigint(20) NOT NULL,
  `createtime` datetime NOT NULL,
  `usernumber` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of order_info
-- ----------------------------

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------
INSERT INTO `undo_log` VALUES ('1', '2007238082', '192.168.211.1:8091:2007238081',  , '0', '2019-03-25 18:40:04', '2019-03-25 18:40:04', null);
INSERT INTO `undo_log` VALUES ('3', '2007238092', '192.168.211.1:8091:2007238091',  , '0', '2019-03-25 19:21:46', '2019-03-25 19:21:46', null);
INSERT INTO `undo_log` VALUES ('4', '2007238096', '192.168.211.1:8091:2007238095',  , '0', '2019-03-25 19:23:18', '2019-03-25 19:23:18', null);

```

**item库**

```sql
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `item`
-- ----------------------------
DROP TABLE IF EXISTS `item`;
CREATE TABLE `item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) DEFAULT NULL,
  `price` bigint(20) NOT NULL,
  `num` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of item
-- ----------------------------
INSERT INTO `item` VALUES ('1', '华为荣耀4', '100', '100');

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------
```

**account库**

```sql
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `account`
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `usernumber` varchar(20) NOT NULL,
  `money` bigint(20) NOT NULL,
  `username` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`usernumber`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('itheima', '1000', '王五');

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------

```



### 1.3.2 完善数据库相关配置

```properties
#account
driverClassName_account=com.mysql.jdbc.Driver
url_account=jdbc:mysql://127.0.0.1:3306/seata_account?useUnicode=true&characterEncoding=utf8&autoReconnect=true
username_account=root
password_account=1234

#item
driverClassName_item=com.mysql.jdbc.Driver
url_item=jdbc:mysql://127.0.0.1:3306/seata_storage?useUnicode=true&characterEncoding=utf8&autoReconnect=true
username_item=root
password_item=1234

#order
driverClassName_order=com.mysql.jdbc.Driver
url_order=jdbc:mysql://127.0.0.1:3306/seata_order?useUnicode=true&characterEncoding=utf8&autoReconnect=true
username_order=root
password_order=1234
```

### 1.3.3 完善dubbo的配置

在账户，库存和订单服务的配置文件中提供如下配置，下面以账户服务为例：

```xml
 <!--注册dubbo服务名字-->
    <dubbo:application name="seata-account" />
    <!--注册中心-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <!--提供服务端口-->
    <dubbo:protocol name="dubbo" port="20881" /><!--port每个服务需要提供不同的-->
    <dubbo:annotation package="com.itheima" />
```

### 1.3.4 准备seata-server

第二步：前往[sql语句网址](https://gitee.com/dhb414/seata/blob/master/script/server/db/mysql.sql#)下载三张表的SQL，在资料中也提供给同学们了。进入mysql创建seata数据库，并执行SQL语句建表。

```sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME,
    `gmt_modified`      DATETIME,
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
```



## 1.4 案例实现

### 1.4.1 导入seata坐标

```xml
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.2.0</version>
</dependency>
```

### 1.4.2 配置seata代理数据源

在账户，订单和库存服务的spring-dubbo.xml中配置如下内容：

```xml
<!--代理数据源-->
<bean id="dataSourceProxy" class="io.seata.rm.datasource.DataSourceProxy">
    <!--注入当前(被代理的)数据源-->
    <constructor-arg ref="dataSource" />
</bean>
```

### 1.4.3 改造SqlSessionFactoryBean

```xml
 <!-- SqlSessionFactoryBean -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceProxy" />
</bean>
```

### 1.4.4 改造MapperScannerConfigurer

```xml
<!--
  配置接口扫描包
  如果是单数据源，则可以不配置sqlSessionFactoryBeanName
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
      p:basePackage="com.itheima.mapper"
      p:sqlSessionFactoryBeanName="sqlSessionFactoryBean" />
```

注意：指定sqlSessionFactoryBeanname在配置多数据源时必不可少

### 1.4.5 配置TM和TC的通讯协议

```
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
}
service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "localRgroup"
  #only support single node
  localRgroup.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
}
```

### 1.4.6 配置@GlobalTransaction注解（引入seata全局事务管理）

#### 1）配置全局事务扫描器

```xml
<!--注入全局事务管理器-->
<bean class="io.seata.spring.annotation.GlobalTransactionScanner">
    <constructor-arg value="seata-account"/>
    <constructor-arg value="my_test_tx_group"/>
</bean>
```

#### 2）改造订单服务的OrderInfoService具体实现

```java
@Service
public class OrderInfoServiceImpl implements OrderInfoService {

    @Autowired
    private OrderInfoDao orderInfoDao;

    @Reference(check = false)
    private ItemService itemService;

    /****
     * 创建订单
     * 调用ItemService修改库存(调用AccountService修改余额)
     *
     * @param  usernumber:购买商品的用户
     * @param  id：购买的商品ID
     * @param  count:要减的数量
     */
    @GlobalTransactional(name="fescar-itheima-tx")
    @Override
    public int create(String usernumber, String id, Integer count){
        //从数据库查询商品信息
        Item item = new Item();
        item.setId(id);
        item.setNum(count);
        item.setPrice(100L);
        item.setTitle("华为荣耀4");

        //创建订单
        OrderInfo orderInfo = new OrderInfo();
        String orderId = UUID.randomUUID().toString().replace("-","").toUpperCase();
        orderInfo.setId(orderId);
        orderInfo.setMoney(item.getPrice()*count);
        orderInfo.setCreatetime(new Date());
        orderInfo.setUsernumber(usernumber);
        int acount = orderInfoDao.add(orderInfo);

        System.out.println("添加订单受影响行数："+acount);

        //制造异常
//        System.out.println("订单添加成功后,出现异常。。。。");
//        int q=10/0;

        //调用ItemService(远程调用)
        Account account = new Account();
        account.setUsernumber(usernumber);
        account.setMoney(item.getPrice()*count); //花掉的钱
        itemService.update(item,account);

        //制造异常
        System.out.println("开始报错了。。。。");
        int q=10/0;
        return  acount;
    }
}
```

#### 3）改造ItemService具体实现

```java
@Service
public class ItemServcieImpl implements ItemService {


    @Autowired
    private ItemDao itemDao;

    @Reference(check = false)
    private AccountService accountService;


    /****
     * 修改商品库存，同时修改账户余额
     * @param item
     * @param account
     * @return
     */
    @Override
    public int update(Item item, Account account){
        //修改商品的库存
        int mcount = itemDao.update(item);
        System.out.println("更新库存受影响行数："+mcount);

        //修改账户余额
        int acount = accountService.update(account);
        System.out.println("账户更新余额受影响行数:"+acount);
        return  mcount;
    }
}
```

#### 4）改造AccountService具体实现

```java
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    /***
     * 修改账户余额
     * @param account
     * @return
     */
    @Override
    public int update(Account account){
        return accountDao.update(account);
    }
}
```

### 1.4.7 编写测试代码

```java
public class AccountTest {


    /***
     * 启动服务
     */
    @Test
    public void testAccount() throws IOException {
        ApplicationContext act = new ClassPathXmlApplicationContext("spring/spring-dubbo.xml");

        //阻塞线程
        System.in.read();
    }
}

```

```java
public class OrderInfoTest {

    /***
     * 业务测试
     */
    @Test
    public void testOrder() throws IOException {
        ApplicationContext act = new ClassPathXmlApplicationContext("spring/spring-dubbo.xml");
        OrderInfoService orderInfoService = act.getBean(OrderInfoService.class);
        orderInfoService.create("itheima","1", 1);

    }
}
```

```java
public class ItemTest {


    /***
     * 启动服务
     */
    @Test
    public void testAccount() throws IOException {
        ApplicationContext act = new ClassPathXmlApplicationContext("spring/spring-dubbo.xml");

        //阻塞线程
        System.in.read();
    }
}
```

# 2 分布式事务实战-seata的TCC模式



### 2.2.3 项目简介

#### 1）数据库表结构

```sql
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `branch_table`
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of branch_table
-- ----------------------------

-- ----------------------------
-- Table structure for `global_table`
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of global_table
-- ----------------------------

-- ----------------------------
-- Table structure for `lock_table`
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of lock_table
-- ----------------------------

```

```sql
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `inventory`
-- ----------------------------
DROP TABLE IF EXISTS `inventory`;
CREATE TABLE `inventory` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` varchar(128) COLLATE utf8mb4_bin NOT NULL,
  `total_inventory` int(10) NOT NULL COMMENT '总库存',
  `lock_inventory` int(10) NOT NULL COMMENT '锁定库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of inventory
-- ----------------------------
INSERT INTO `inventory` VALUES ('1', '1', '983', '0');

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  `context` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------

```

```sql
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `account`
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(128) COLLATE utf8mb4_bin NOT NULL,
  `balance` decimal(10,0) NOT NULL COMMENT '用户余额',
  `freeze_amount` decimal(10,0) NOT NULL COMMENT '冻结金额，扣款暂存余额',
  `create_time` datetime NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('1', '10000', '983', '0', '2017-09-18 14:54:22', '2020-07-21 11:22:46');

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  `context` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------

```

```sql
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `order`
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` varchar(100) COLLATE utf8mb4_bin NOT NULL,
  `create_time` datetime NOT NULL,
  `number` varchar(20) COLLATE utf8mb4_bin NOT NULL,
  `status` tinyint(4) NOT NULL,
  `product_id` varchar(128) COLLATE utf8mb4_bin NOT NULL,
  `total_amount` decimal(10,0) NOT NULL,
  `count` int(4) NOT NULL,
  `user_id` varchar(128) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of order
-- ----------------------------
INSERT INTO `order` VALUES ('1', '2020-07-15 15:47:21', '1283307146968535040', '3', '1', '1', '1', '10000');

-- ----------------------------
-- Table structure for `undo_log`
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  `context` varchar(1000) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of undo_log
-- ----------------------------

```



## 2.3 案例准备

## 2.4 案例实现

### 2.4.1 seata的前期准备

#### 1）导入坐标

```xml
<!-- https://mvnrepository.com/artifact/io.seata/seata-all-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.2.0</version>
        </dependency>
```

#### 2）配置seata(三个服务都需要，此处只以账户服务为例)

```xml
 <!--注入全局事务管理器-->
    <bean class="io.seata.spring.annotation.GlobalTransactionScanner">
        <constructor-arg value="seata-tcc-accountservice"/>
        <constructor-arg value="my_test_tx_group"/>
    </bean>

    <!--代理数据源-->
    <bean id="dataSourceProxy" class="io.seata.rm.datasource.DataSourceProxy">
        <!--注入当前(被代理的)数据源-->
        <constructor-arg ref="dataSource" />
    </bean>

    <!--配置undolog解析器-->
    <bean id="undoLogParser" class="io.seata.rm.datasource.undo.parser.JacksonUndoLogParser"></bean>
```

#### 3）改造mybatis的相关配置-引用seata提供的代理数据源(三个服务都需要，此处只以账户服务为例)

```xml
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceProxy"></property>
    <property name="typeAliasesPackage" value="com.itheima.domain"></property>
</bean>
```

### 2.4.2 配置@TwoPhaseBusinessAction注解

```java
@LocalTCC
public interface InventoryService {


    /**
     * 预扣减库存操作
     * @param businessActionContext
     * @param productId  商品id
     * @param count      商品数量
     * @return
     */
    @TwoPhaseBusinessAction(name="decreaseTccTryPhase",commitMethod = "commit",rollbackMethod = "rollback")
    boolean prepareDecrease(BusinessActionContext businessActionContext,
                            @BusinessActionContextParameter(paramName = "productId")String productId,
                            @BusinessActionContextParameter(paramName = "count")Integer count);


    /**
     * 扣减库存的提交补偿
     * @param businessActionContext
     * @return
     */
    public boolean commit(BusinessActionContext businessActionContext) ;

    /**
     * 扣减库存的回滚补偿
     * @param businessActionContext
     * @return
     */
    public boolean rollback(BusinessActionContext businessActionContext) ;
}
```

```java
@LocalTCC
public interface AccountService {


    /**
     * 扣款支付
     * @param businessActionContext
     * @param userId  支付人id
     * @param amount  支付金额
     * @return
     */
    @TwoPhaseBusinessAction(name = "accountTccTryPhase",commitMethod = "commit",rollbackMethod = "rollback")
    boolean preparePayment(BusinessActionContext businessActionContext,
                        @BusinessActionContextParameter(paramName = "userId")String userId,
                        @BusinessActionContextParameter(paramName = "amount")BigDecimal amount);

    /**
     * 提交方法
     * @param businessActionContext
     * @return
     */
    public boolean commit(BusinessActionContext businessActionContext);

    /**
     * 取消方法
     * @param businessActionContext
     * @return
     */
    public boolean rollback(BusinessActionContext businessActionContext);
}
```

```java
@LocalTCC
public interface OrderService {

    /**
     * 创建订单并且进行扣除账户余额支付，并进行库存扣减操作
     *
     * @return string string
     */
    @TwoPhaseBusinessAction(name = "orderPayTccTryPhase",commitMethod = "commit",rollbackMethod = "rollback")
    boolean prepareUpdateState(BusinessActionContext businessActionContext,@BusinessActionContextParameter(paramName = "orderId")String orderId);

    /**
     * 修改订单状态为支付完成
     * @param businessActionContext
     */
    public boolean commit(BusinessActionContext businessActionContext);

    /**
     * 修改订单状态为支付失败
     * @param businessActionContext
     */
    public boolean rollback(BusinessActionContext businessActionContext);


    /**
     * 根据id查询订单
     * @param id
     * @return
     */
    Order findById(String id);
}
```

### 2.4.3 配置@GlobalTransactional注解

```java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
public interface BusinessService {

    /**
     * 外部服务：下单
     * @param orderId 订单Id
     * @return
     */
    String orderPay(String orderId);
}
```

```java
/**
 * @author 黑马程序员
 * @Company http://www.itheima.com
 */
@Service
public class BusinessServiceImpl implements BusinessService {

    @Reference
    private OrderService orderService;

    @Reference
    private InventoryService inventoryService;

    @Reference
    private AccountService accountService;

    @Override
    @GlobalTransactional(name = "seata-tcc-tx",rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public String orderPay(String orderId){
        String xid = RootContext.getXID();
        System.out.println(xid);
        //1.根据订单id查询订单
        Order order = orderService.findById(orderId);
        //2.判断是否有此订单
        if(order != null && order.getStatus().intValue() == OrderStatusEnum.NOT_PAY.getCode()) {
            //3.更新订单状态为支付中
            orderService.prepareUpdateState(null, orderId);
            //4.预扣减余额（冻结账户资金）
            accountService.preparePayment(null,order.getUserId(),order.getTotalAmount());
            //5.预扣减库存（锁定库存）
            inventoryService.prepareDecrease(null,order.getProductId(), order.getCount());
        }else {
            return "订单不存在";
        }
        return "success";
    }
}

```

## 2.5 seat的源码分析

### 2.5.2 资源管理器注册资源

​		TCCResourceManager 负责管理 TCC 模式下资源的注册、分支的注册、提交、和回滚。代码如下：

```java
 @Override
public void registerResource(Resource resource) {
    TCCResource tccResource = (TCCResource)resource;
    tccResourceCache.put(tccResource.getResourceId(), tccResource);
    super.registerResource(tccResource);
}
```




### 2.5.3 事务控制的方法拦截器

​			seata的tcc模式事务控制也是基于Spring的AOP实现的，它里面也是利用了`MethodInterceptor`方法拦截器来进行的增强，它里面提供了一个名称为`TccActionInterceptor`的类，该类实现了`MethodInterceptor`接口，并重写了`invoke`方法。代码如下：

```java
@Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        if (!RootContext.inGlobalTransaction()) {
            //not in transaction
            return invocation.proceed();
        }
        Method method = getActionInterfaceMethod(invocation);
        TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
        //try method
        if (businessAction != null) {
            //save the xid
            String xid = RootContext.getXID();
            //clear the context
            RootContext.unbind();
            RootContext.bindInterceptorType(xid, BranchType.TCC);
            try {
                Object[] methodArgs = invocation.getArguments();
                //Handler the TCC Aspect
                Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
                        invocation::proceed);
                //return the final result
                return ret.get(Constants.TCC_METHOD_RESULT);
            } finally {
                //recovery the context
                RootContext.unbindInterceptorType();
                RootContext.bind(xid);
            }
        }
        return invocation.proceed();
    }
```

而此方法的核心是`MethodInvocation`的`proceed`方法，最终由`ActionInterceptorHandler`类中的`proceed`方法处理。代码如下：

```java
 /**
     * Handler the TCC Aspect
     *
     * @param method         the method
     * @param arguments      the arguments
     * @param businessAction the business action
     * @param targetCallback the target callback
     * @return map map
     * @throws Throwable the throwable
     */
    public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
                                       Callback<Object> targetCallback) throws Throwable {
        Map<String, Object> ret = new HashMap<>(4);

        //TCC name
        String actionName = businessAction.name();
        BusinessActionContext actionContext = new BusinessActionContext();
        actionContext.setXid(xid);
        //set action anme
        actionContext.setActionName(actionName);

        //Creating Branch Record
        String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);
        actionContext.setBranchId(branchId);

        //set the parameter whose type is BusinessActionContext
        Class<?>[] types = method.getParameterTypes();
        int argIndex = 0;
        for (Class<?> cls : types) {
            if (cls.getName().equals(BusinessActionContext.class.getName())) {
                arguments[argIndex] = actionContext;
                break;
            }
            argIndex++;
        }
        //the final parameters of the try method
        ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);
        //the final result
        ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());
        return ret;
    }
```
