Seata集成ShardingJDBC

整合Seata AT事务时,需要把TM,RM,TC的模型融入到ShardingSphere 分布式事务的SPI的生态中。在数据库资源上,Seata通过对接DataSource接口,让JDBC操作可以同TC进行RPC通信。同样,ShardingSphere也是面向DataSource接口对用户配置的物理DataSource进行了聚合,因此把物理DataSource二次包装为Seata 的DataSource后,就可以把Seata AT事务融入到ShardingSphere的分片中。

这里注意ShardingSphere分布式事务的SPI的生态中,已经提供了整合的实现类SeataATShardingTransactionManager,只需要导入对应jar包就可以使用了

1. POM

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-seata-at</artifactId>
    <version>4.1.1</version>
</dependency>

我们来看下SeataATShardingTransactionManager里面的内容:

/*
 * 自定义的实现了 ShardingTransactionManager 接口的类
 */
public final class SeataATShardingTransactionManager implements ShardingTransactionManager {
    //用于存储数据源的代理
    private final Map<String, DataSource> dataSourceMap = new HashMap();
    private final FileConfiguration configuration = new FileConfiguration("seata.conf");

    public SeataATShardingTransactionManager() {
    }

    /*
     * 将所有数据源封装成seata的代理数据源DataSourceProxy,并放入dataSourceMap中备用。
     */
    public void init(DatabaseType databaseType, Collection<ResourceDataSource> resourceDataSources) {
        //初始化了 Seata 客户端
        this.initSeataRPCClient();
        //遍历传入的数据源集合,将每个数据源封装成 DataSourceProxy,并存放到 dataSourceMap 中
        Iterator var3 = resourceDataSources.iterator();
        while (var3.hasNext()) {
            ResourceDataSource each = (ResourceDataSource) var3.next();
            this.dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource()));
        }
    }
    
    /*
     * 初始化TM(事务管理器)和RM(资源管理器)客户端
     */
    private void initSeataRPCClient() {
        //应用程序ID
        String applicationId = this.configuration.getConfig("client.application.id");
        Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file");
        //逻辑事务分组
        String transactionServiceGroup = this.configuration.getConfig("client.transaction.service.group", "default");
        TMClient.init(applicationId, transactionServiceGroup);
        RMClient.init(applicationId, transactionServiceGroup);
    }

    /*
     * 返回事务类型, 这里是 TransactionType.BASE,表示基本的本地事务。
     */
    public TransactionType getTransactionType() {
        return TransactionType.BASE;
    }

    /*
     * 检查是否在一个事务中,通过判断是否有全局事务上下文(XID)来判断。
     */
    public boolean isInTransaction() {
        return null != RootContext.getXID();
    }

    /*
     * 通过数据源名称获取连接,使用了 DataSourceProxy 封装的数据源。
     */
    public Connection getConnection(String dataSourceName) throws SQLException {
        return ((DataSource) this.dataSourceMap.get(dataSourceName)).getConnection();
    }

    /*
     * 开启
     */
    public void begin() {
        try {
            //设置了当前线程的事务上下文
            SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());
            //在该上下文中开始一个分布式事务
            SeataTransactionHolder.get().begin();
            SeataTransactionBroadcaster.collectGlobalTxId();
        } catch (Throwable var2) {
            throw var2;
        }
    }

    /*
     * 提交
     */
    public void commit() {
        try {
            try {
                //提交当前线程的分布式事务
                SeataTransactionHolder.get().commit();
            } finally {
                //清理相关的事务上下文
                SeataTransactionBroadcaster.clear();
                SeataTransactionHolder.clear();
            }
        } catch (Throwable var5) {
            throw var5;
        }
    }

    /*
     * 回滚
     */
    public void rollback() {
        try {
            try {
                //回滚当前线程的分布式事务
                SeataTransactionHolder.get().rollback();
            } finally {
                //清理相关的事务上下文
                SeataTransactionBroadcaster.clear();
                SeataTransactionHolder.clear();
            }
        } catch (Throwable var5) {
            throw var5;
        }
    }

    /*
     * 关闭
     */
    public void close() {
        //清理了数据源和事务上下文
        this.dataSourceMap.clear();
        SeataTransactionHolder.clear();
        //销毁 TM 和 RM 客户端
        TmRpcClient.getInstance().destroy();
        RmRpcClient.getInstance().destroy();
    }
}

从源码得知SeataATShardingTransactionManager主要做了三件事:

第一:将所有数据源封装成seata的代理数据源DataSourceProxy,并放入dataSourceMap中备用。

第二:初始化TM和RM。

第三:提供开启,提交,回滚和关闭事务的方法。

看到这三个功能,是不是很像seata中GlobalTransactionScanner的作用?

没错!

这二者的功能是一模一样的,说白了,这里ShardingSphere就是拿自己分库分表的配置文件做里子,用seata的提供好的DataSourceProxy,TMClient,RMClient和GlobalTransaction这几个对象做衣服,重新封装了一个控制分布式事务的对象。

说人话,就是SeataATShardingTransactionManager对GlobalTransactionScanner进行了二次封装,不仅满足单库微服务的分布式事务,还满足分库分表微服务的分布式事务,变得更强了。

2. 那么如何使用SeataATShardingTransactionManager呢?

非常简单,两步搞定!

第一:在resources下提供seata.conf配置文件,内容如下:

client {
application.id = seata-server
transaction.service.group = test_tx_group
}

第二:用@Transactional搭配@ShardingTransactionType(TransactionType.BASE)来开启全局事务

//这里切记不要加@GlobalTransactional
@Transactional
@ShardingTransactionType(TransactionType.BASE)
public void seataDemo(Boolean hasError) {
    //下单操作
    Order order = new Order();
    order.setOrderName("测试数据");
    order.setBuyNum(2);
    orderMapper.insert(order);

    //减库存(这里参数什么的就自己脑补了)
    productClient.minusStock();

    //异常模拟
    if(hasError){
        int i=1/0;
    }
}

但是这里要注意一个问题!!!!!!

虽然SeataATShardingTransactionManager和GlobalTransactionScanner做的事情一样,但是二者绝对不能混合使用!!!!!!

也就是说@ShardingTransactionType(TransactionType.BASE)和@GlobalTransactional绝对不能同时出现!!!!!!

看过上面SeataATShardingTransactionManager你就能明白,ShardingSphere把获取到的全局事务,放到了线程的局部变量里面了。而GlobalTransactionScanner则是采用动态代理的方式对方法进行增强。根本不能放在一起

 

posted @ 2023-08-21 19:29  yifanSJ  阅读(515)  评论(0编辑  收藏  举报