Seata源码(五)Seata数据库操作


Java极客 | 作者 / 铿然一叶 这是Java极客的第 93 篇原创文章


相关阅读:

萌新快速成长之路
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(五)事件通知模式解耦过程
Java编程思想(六)事件通知模式解耦过程
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
HikariPool源码(二)设计思想借鉴
【极客源码】JetCache源码(一)开篇
【极客源码】JetCache源码(二)顶层视图
如何编写软件设计文档
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock\


1. JDBC代理

通过代理JDBC接口可以在访问实际的DB操作方法前或者方法后增加扩展能力,Seata也使用了JDBC代理来增强扩展能力。JDBC代理的核心类结构如下:

代理对象(proxy)实现接口方法,并持有实现了同一接口的原始实现类的对象实例,当调用接口方法时,会先调用代理方法,代理方法调用额外定制的逻辑,并调用原始实现类方法。

2. Seata数据操作核心类结构

序号 类型
StatementProxy StatementProxy为Statement接口实现,代理真实的Statement类,并调用ExecuteTemplate做实际的数据库操作
PreparedStatementProxy PreparedStatement的实现类,主要用于数据插入操作
ExecuteTemplate 负责流程编排,根据不同的条件生成Executor并执行
BaseTransactionalExecutor Executor的基类,为sql执行入口,提供最基本的共性操作,例如绑定XID,设置全局锁标记,使用模版方法模式,将具体SQL执行交给子类实现。
AbstractDMLBaseExecutor AbstractDMLBaseExecutor控制自动提交分支,使用模版方法将undo日志的镜像操作串起来,具体镜像操作由子类实现。
StatementCallback StatementCallback是一个函数接口,用于方便的调用Statement,本身没有任何逻辑
SQLRecognizer 用于标识SQL类型(Insert/Update/Delete)、记录表名、表别名以及原生SQL

3. 源码

3.1 SQL执行入口

代理类调用SQL执行模版,所以类似下面的方法都调用执行模版:

StatementProxy.java

java 复制代码
    @Override
    public boolean execute(String sql) throws SQLException {
        this.targetSQL = sql;
        return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0]), sql);
    }

3.2 执行模版流程编排,调用不同的Executor

ExecuteTemplate.java

java 复制代码
    public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,
                                                     StatementProxy<S> statementProxy,
                                                     StatementCallback<T, S> statementCallback,
                                                     Object... args) throws SQLException {
        if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
            // Just work as original statement
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        }

        String dbType = statementProxy.getConnectionProxy().getDbType();
        if (CollectionUtils.isEmpty(sqlRecognizers)) {
            sqlRecognizers = SQLVisitorFactory.get(
                    statementProxy.getTargetSQL(),
                    dbType);
        }
        Executor<T> executor;
        if (CollectionUtils.isEmpty(sqlRecognizers)) {
            executor = new PlainExecutor<>(statementProxy, statementCallback);
        } else {
            if (sqlRecognizers.size() == 1) {
                SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
                switch (sqlRecognizer.getSQLType()) {
                    case INSERT:
                        executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,
                                new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},
                                new Object[]{statementProxy, statementCallback, sqlRecognizer});
                        break;
                    case UPDATE:
                        executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
                        break;
                    case DELETE:
                        executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer);
                        break;
                    case SELECT_FOR_UPDATE:
                        executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
                        break;
                    default:
                        executor = new PlainExecutor<>(statementProxy, statementCallback);
                        break;
                }
            } else {
                executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);
            }
        }
        T rs;
        try {
            rs = executor.execute(args);
        } catch (Throwable ex) {
            if (!(ex instanceof SQLException)) {
                // Turn other exception into SQLException
                ex = new SQLException(ex);
            }
            throw (SQLException) ex;
        }
        return rs;
    }

3.3 Executor入口

绑定XID,设置需要全局锁标记,并调用子类方法:

BaseTransactionalExecutor.java

java 复制代码
    public T execute(Object... args) throws Throwable {
        String xid = RootContext.getXID();
        if (xid != null) {
            statementProxy.getConnectionProxy().bind(xid);
        }

        statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
        // 这里的模版方法交给子类实现
        return doExecute(args);
    }

3.4 事务分支控制

AbstractDMLBaseExecutor.java

控制分支:

java 复制代码
    public T doExecute(Object... args) throws Throwable {
        AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
        if (connectionProxy.getAutoCommit()) {
            return executeAutoCommitTrue(args);
        } else {
            return executeAutoCommitFalse(args);
        }
    }

非自动提交,同时会生成UNDO日志:

java 复制代码
    protected T executeAutoCommitFalse(Object[] args) throws Exception {
        if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
            throw new NotSupportYetException("multi pk only support mysql!");
        }
        // 模版方法,交给子类实现
        TableRecords beforeImage = beforeImage();
        // 这里调用了原始的statement执行SQL
        T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
        // 模版方法,交给子类实现
        TableRecords afterImage = afterImage(beforeImage);
        // 准备undo日志
        prepareUndoLog(beforeImage, afterImage);
        return result;
    }

自动提交:

java 复制代码
    protected T executeAutoCommitTrue(Object[] args) throws Throwable {
        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
        try {
            connectionProxy.changeAutoCommit();
            return new LockRetryPolicy(connectionProxy).execute(() -> {
                // 自动提交依然会写undo日志
                T result = executeAutoCommitFalse(args);
                connectionProxy.commit();
                return result;
            });
        } catch (Exception e) {
            // when exception occur in finally,this exception will lost, so just print it here
            LOGGER.error("execute executeAutoCommitTrue error:{}", e.getMessage(), e);
            if (!LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {
                connectionProxy.getTargetConnection().rollback();
            }
            throw e;
        } finally {
            connectionProxy.getContext().reset();
            connectionProxy.setAutoCommit(true);
        }
    }

end.


<--阅过留痕,左边点赞!

相关推荐
小白羊丨4 小时前
问数 Agent 整体架构、意图识别、历史上下文与 NL2SQL
java·面试·架构
Linux-lucky4 小时前
33-Linux学习之旅之MySQL用户管理
java·linux·运维·学习·mysql·ubuntu
SimonKing4 小时前
将cURL 命令直接变成 Java 代码?用JQuick-Curl搞起来
java·后端·程序员
凤山老林4 小时前
企业级文件服务中台:Spring Boot 集成 MinIO 实现分片存储、生命周期管理与多租户隔离
java·spring boot·后端·minio·文件服务
青山木4 小时前
Hot 100 --- 买卖股票的最佳时机
java·数据结构·算法·leetcode·贪心算法
程序员良辰4 小时前
Eclipse Memory Analyzer(MAT)入门教程:从生成 Heap Dump 到定位 Java 内存问题
java·ide·eclipse·tomcat·idea
深圳市益普科技有限公司4 小时前
半导体MES的数字孪生:虚拟调试如何把上线风险提前清零
java·开发语言
星栖与芯4 小时前
STM32MP157 M4 指针避坑(三):生命周期与内存踩踏——HardFault 重灾区
java·网络·stm32
Maynor9964 小时前
「原子弹爆炸」级别:Astra 复刻游戏合集(含实机截图)
java·linux·运维·数据库·gpt·游戏
CV艺术家5 小时前
openssL生成免费的证书
java·linux·服务器