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

698 阅读3分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 96 篇原创文章

1. JDBC代理

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

image.png

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

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

image.png

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

3. 源码

3.1 SQL执行入口

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

StatementProxy.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

    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

    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

控制分支:

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

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

    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;
    }

自动提交:

    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.


其他阅读:

萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃