Seata源码解析9——数据源(1)

本文详细介绍了Seata中数据源代理DataSourceProxy的重要性和工作原理,包括其构造方法中的3个核心增强功能:资源组ID标识、元数据定时更新和ConnectionProxy的生成。此外,文章讲解了ConnectionProxy的类结构,以及PreparedStatementProxy和StatementProxy如何通过ExecuteTemplate模板方法执行SQL。Seata通过数据源代理自动记录undo log和进行资源锁定,确保分布式事务的顺利进行。
摘要由CSDN通过智能技术生成
1前言

之前我们分析了GlobalTransactional注解的整个生态,但是一些细节性的逻辑还没有深入去看,例如:数据源代理、各种Client的初始化、模板方法等。在这一篇里面,我们会了解一下在上一篇中没有讲完的数据源代理。

数据源代理是非常重要的一个环节。我们知道,在分布式事务运行过程中,很多undo log等的记录、资源的锁定等,都是用户无感知的,因为这些操作都在数据源的代理中完成了。

2数据源代理DataSourceProxy

在上一篇中,我们知道在postProcessAfterInitialization时,会生成对应的数据源代理DataSourceProxy。

DataSourceProxy与普通数据源有哪些区别呢? 它在构造方法中调用了一个自定义的init方法,主要做了以下能力的增强:

1为每个数据源标识了资源组ID

2如果配置打开,会有一个定时线程池定时更新表的元数据信息并缓存到本地

3生成代理连接ConnectionProxy

以下是init方法的代码:

private void init(DataSource dataSource, String resourceGroupId) {
   
  //资源组ID,如果初始化的时候,构造方法没有传,会使用一个“default”这个默认值
    this.resourceGroupId = resourceGroupId;
    try (Connection connection = dataSource.getConnection()) {
   
      //根据原始数据源得到JDBC连接和数据库类型
        jdbcUrl = connection.getMetaData().getURL();
        dbType = JdbcUtils.getDbType(jdbcUrl, null);
    } catch (SQLException e) {
   
        throw new IllegalStateException("can not init dataSource", e);
    }
    DefaultResourceManager.get().registerResource(this);
    if (ENABLE_TABLE_META_CHECKER_ENABLE) {
   
      //如果配置开关打开,会定时线程池不断更新表的元数据信息
        tableMetaExcutor.scheduleAtFixedRate(() -> {
   
            try (Connection connection = dataSource.getConnection()) {
   
                TableMetaCacheFactory.getTableMetaCache(DataSourceProxy.this.getDbType())
                    .refresh(connection, DataSourceProxy.this.getResourceId());
            } catch (Exception e) {
   
            }
        }, 0, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS);
    }
}

这3个增强里面,前两个都比较容易理解,第三是最重要的。我们知道在AT模式里面,会自动记录undo log、资源锁定等等,都是通过ConnectionProxy完成的。

另外,DataSourceProxy重写了几个方法。

一个是getConnection,此时会返回一个ConnectionProxy,而不是原生的Connection,ConnectionProxy我们会在下一个小节详细介绍:

@Override
public ConnectionProxy getConnection() throws SQLException {
   
    Connection targetConnection = targetDataSource.getConnection();
    return new ConnectionProxy(this, targetConnection);
}

@Override
public ConnectionProxy getConnection(String username, String password) throws SQLException {
   
    Connection targetConnection = targetDataSource.getConnection(username, password);
    return new ConnectionProxy(this, targetConnection);
}

另外就是一些辅助性的方法了,如获取资源组、获取分支事务的类型,随便看看就好了:

@Override
public String getResourceGroupId() {
   
    return resourceGroupId;
}

@Override
public String getResourceId() {
   
    if (jdbcUrl.contains("?")) {
   
        return jdbcUrl.substring(0, jdbcUrl.indexOf("?"));
    } else {
   
        return jdbcUrl;
    }
}

@Override
public BranchType getBranchType() {
   
  //分支事务返回AT模式
    return BranchType.AT;
}
3ConnectionProxy的总体类结构

ConnectionProxy继承了AbstractConnectionProxy。一看到Abstract,就知道它的父类封装了很多通用工作。它的父类里面还使用了PreparedStatementProxy、StatementProxy、DataSourceProxy。

在这里插入图片描述
DataSourceProxy在上面已经讲过,只有3个增强点,并重写了getConnection等方法而已,其余都是使用的DataSource原生的方法,因此就不再赘述。

我们先看AbstractConnectionProxy。

4PreparedStatementProxy和StatementProxy

无论是StatementProxy还是PreparedStatementProxy,它们都是使用了一个模板执行具体的逻辑步骤,这个模板叫ExecuteTemplate。举个例子:模板里面定义了1、2、3、4…的步骤,其中4是需要外部实现的,即到底用Statement还是PreparedStatement执行sql:

public boolean execute() throws SQLException {
   
    return ExecuteTemplate.execute(this, new StatementCallback<Boolean, PreparedStatement>() {
   
        @Override
        public Boolean execute(PreparedStatement statement, Object... args) throws SQLException {
   
          //最终个性化的操作就是使用PreparedStatement做execute
            return statement.execute();
        }
    });
}

就如代码里一样,PreparedStatementProxy的execute方法调用了ExecuteTemplate的execute方法,并传入了一个StatementCallback,这个Callback就是第4步的个性化逻辑,其余逻辑都是通用。

我们继续看execute方法,这个也是模板里面唯一的方法:

public static <T, S extends Statement> T execute(SQLRecognizer sqlRecognizer,
                                                 StatementProxy<S> statementProxy,
                                                 StatementCallback<T, S> statementCallback,
                                                 Object... args) throws SQLException {
   

    if (!RootContext.inGlobalTransaction() && !RootContext.requireGlobalLock()) {
   
        // 没有分布式事务,执行像普通sql一样执行
        return statementCallback.execute(statementProxy.getTargetStatement(), args);
    }
		//生成一个sql的识别器对象,它能解析sql
    if (sqlRecognizer == null) {
   
        sqlRecognizer = SQLVisitorFactory.get(
                statementProxy.getTargetSQL(),
                statementProxy.getConnectionProxy().getDbType());
    }
    Executor<T> executor = null;
  //根据sql的类型生成不同的类型的executor,传入不同发statementCallback
    if (sqlRecognizer == null) {
   
        executor = new PlainExecutor<T, S>(statementProxy, statementCallback);
    } else {
   
        switch (sqlRecognizer.getSQLType()) {
   
            case INSERT:
                executor = new InsertExecutor<T, S>(statementProxy, statementCallback, sqlRecognizer);
                break;
            case UPDATE:
                executor = new UpdateExecutor<T, S>(statementProxy, statementCallback, sqlRecognizer);
                break;
            case DELETE:
                executor = new DeleteExecutor
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
您好!要配置Seata来支持动态数据源,您需要进行以下几个步骤: 1. 引入Seata相关依赖:在您的项目中添加Seata的依赖,这将包括seata-all、seata-spring-boot-starter和seata-configuration。 2. 配置Seata数据源代理:在Seata的配置文件中(一般是file.conf),找到store模块的配置部分,将store.mode设置为"db",并配置store.db驱动类型、链接地址、用户名和密码等。 3. 创建Seata数据库表:使用Seata提供的db_store脚本,创建相应的数据库表。这些表用于存储全局事务信息、分支事务信息以及锁信息。 4. 配置动态数据源:根据您使用的动态数据源框架,如Druid、HikariCP等,在应用的配置文件中配置数据源。确保您的数据源配置与Seata配置文件中的数据库信息一致。 5. 配置Seata代理数据源:在应用的配置文件中,使用Seata提供的DataSourceProxyBeanPostProcessor作为Bean后置处理器,将动态数据源包装成Seata可识别的代理数据源。这样Seata就可以拦截并管理您的事务了。 6. 配置分布式事务:在需要进行分布式事务管理的方法上,添加@GlobalTransactional注解。这将启用Seata对该方法的事务管理。确保该方法所在的服务已经接入Seata。 这些步骤是一般的配置流程,具体配置细节可能因您使用的技术栈和版本而有所不同。建议参考Seata官方文档和示例,以确保正确配置Seata的动态数据源支持。希望能对您有所帮助!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值