• 六、StatementProxy生成undoLog


    所有文章

    https://www.cnblogs.com/lay2017/p/12485081.html

    正文

    前两篇文章中 ,我们分别看到了DataSourceProxy被注册为Resource,ConnectionProxy注册分支事务到全局事务当中。

    本文继续数据源代理的StatementProxy的内容

    我们知道,Statement是由Connection生成的。那么StatementProxy将由ConnectionProxy生成

    StatementProxy的uml类图

    我们先看看类图

    StatementProxy间接实现了Statement,所以也表示一个sql语句的执行对象。向下到最底,又被PreparedStatementProxy扩展。既然StatementProxy作为Statement的代理对象而存在,那么它组合了哪些原始的对象呢?

    我们打开AbstractStatementProxy

    public abstract class AbstractStatementProxy<T extends Statement> implements Statement {
        /**
         * ConnectionProxy对象
         */
        protected AbstractConnectionProxy connectionProxy;
        /**
         * 原始Statement对象
         */
        protected T targetStatement;
        /**
         * 原始的待执行sql
         */
        protected String targetSQL;
    
        public AbstractStatementProxy(AbstractConnectionProxy connectionProxy, T targetStatement, String targetSQL) throws SQLException {
            this.connectionProxy = connectionProxy;
            this.targetStatement = targetStatement;
            this.targetSQL = targetSQL;
        }
    }

    可以看到,除了被代理的Statement对象之外,在构造过程中还把ConnectionProxy和sql给包含进来了。

    execute方法

    Statement最核心的方法就是执行方法,我们以execute为例,看看该StatementProxy在代理Statement的时候做了哪些事

    @Override
    public boolean execute(String sql) throws SQLException {
        this.targetSQL = sql;
        return ExecuteTemplate.execute(this, new StatementCallback<Boolean, T>() {
            @Override
            public Boolean execute(T statement, Object... args) throws SQLException {
                // 回调,执行原始的Statement的execute方法
                return statement.execute((String) args[0]);
            }
        }, sql);
    }

    可见,ExecuteTemplate的execute方法包含了核心的代理逻辑。跟进ExecuteTemplate.execute()

    public static <T, S extends Statement> T execute(StatementProxy<S> statementProxy,
                                                     StatementCallback<T, S> statementCallback,
                                                     Object... args) throws SQLException {
        return execute(null, statementProxy, statementCallback, args);
    }

    继续跟进execute方法,execute方法会解析当前待执行的sql语句,并判断sql语句的类型是insert、update、delete...通过类型来选择对应的Executor,然后执行。

    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()) {
            // 直接执行原始方法
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        }
    
        if (sqlRecognizer == null) {
            // sql解析
            sqlRecognizer = SQLVisitorFactory.get(statementProxy.getTargetSQL(), statementProxy.getConnectionProxy().getDbType());
        }
    
        Executor<T> executor = null;
        if (sqlRecognizer == null) {
            executor = new PlainExecutor<T, S>(statementProxy, statementCallback);
        } else {
            // 根据解析出来的类型选择Executor
            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<T, S>(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case SELECT_FOR_UPDATE:
                    executor = new SelectForUpdateExecutor<T, S>(statementProxy, statementCallback, sqlRecognizer);
                    break;
                default:
                    executor = new PlainExecutor<T, S>(statementProxy, statementCallback);
                    break;
            }
        }
        T rs = null;
        try {
            // executor执行
            rs = executor.execute(args);
        } catch (Throwable ex) {
            if (!(ex instanceof SQLException)) {
                ex = new SQLException(ex);
            }
            throw (SQLException)ex;
        }
        return rs;
    }

    跟进BaseTransactionalExecutor的execute方法

    public Object execute(Object... args) throws Throwable {
        // 如果在全局事务当中
        if (RootContext.inGlobalTransaction()) {
            String xid = RootContext.getXID();
            // 将ConnectionProxy绑定XID
            statementProxy.getConnectionProxy().bind(xid);
        }
        // 设置全局锁标识
        if (RootContext.requireGlobalLock()) {
            statementProxy.getConnectionProxy().setGlobalLockRequire(true);
        } else {
            statementProxy.getConnectionProxy().setGlobalLockRequire(false);
        }
        
        return doExecute(args);
    }

    注意到,XID是在这里被绑定到ConnectionProxy的。

    上一篇文章我们说过,ConnectionProxy在commit的时候会根据是否存在XID来判断要不要注册事务分支之类的操作。所以,这里的bind方法将决定是否当前Connection处于全局事务当中。

    接下来继续跟进AbstractDMLBaseExecutor的doExecute方法

    @Override
    public T doExecute(Object... args) throws Throwable {
        AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
        // 判断自动提交标志位
        if (connectionProxy.getAutoCommit()) {
            return executeAutoCommitTrue(args);
        } else {
            return executeAutoCommitFalse(args);
        }
    }

    doExecute方法根据autoCommit标志位来选择执行方法,执行方法中包含了StatementProxy的生成undoLog的核心点。

    我们选择相对比较清晰的executeAutoCommitFalse这个方法看看

    protected T executeAutoCommitFalse(Object[] args) throws Exception {
        // 前置生成数据记录
        TableRecords beforeImage = beforeImage();
        // 执行sql语句
        T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
        // 生成后置数据记录
        TableRecords afterImage = afterImage(beforeImage);
        // 生成undoLog
        prepareUndoLog(beforeImage, afterImage);
        return result;
    }

    beforeImage和afterImage由子类实现,分别生成sql执行前后的数据记录。

    prepareUndoLog将根据前后的数据记录生成一份SQLUndoLog,顾名思义undo_log就是用于在分支事务rollback的时候做补偿操作的。

    flushUndoLog持久化到数据库

    undoLog到这里还只是存在于内存当中,当ConnectionProxy的执行完register注册分支事务以后,在本地事务提交之前就会将undoLog给flush到数据库中。

    我们跟进AbstractUndoLogManager的flushUndoLogs方法

    @Override
    public void flushUndoLogs(ConnectionProxy cp) throws SQLException {
        ConnectionContext connectionContext = cp.getContext();
        String xid = connectionContext.getXid();
        long branchID = connectionContext.getBranchId();
    
        // 构建一个分支事务undoLog对象
        BranchUndoLog branchUndoLog = new BranchUndoLog();
        branchUndoLog.setXid(xid);
        branchUndoLog.setBranchId(branchID);
        // 获取undoLog内存对象
        branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());
    
        UndoLogParser parser = UndoLogParserFactory.getInstance();
        // 解析器解析后生成字节码
        byte[] undoLogContent = parser.encode(branchUndoLog);
        // insert到数据库表当中
        insertUndoLogWithNormal(xid, branchID, buildContext(parser.getName()), undoLogContent, cp.getTargetConnection());
    }

    核心逻辑就是获取上文中的SqlUndoLog集合,然后持久化到数据库undo_log表当中,给分支事务的rollback预留。

    总结

    到这里,本文就结束了。可以看到,StatementProxy主要就是在sql执行后生成SqlUndoLog。而在ConnectionProxy的commit方法里,将分支事务register以后会把SqlUndoLog给flush到数据库当中。

    undoLog的作用就是用于rollback操作补偿的,所以这里就是预留的一个undo的操作。

    我们再回顾一下数据源代理的三个核心对象

    1)DataSourceProxy注册了Resource

    2)ConnectionProxy注册分支事务,持久化UndoLog,并先提交本地事务

    3)StatementProxy执行sql,生成undoLog

    我们可以暂时简单地把数据源代理这一过程理解为,主要是为rollback预留undo操作,如果执行顺利异步删除undoLog即可。

  • 相关阅读:
    《人件》读书笔记
    《人月神话》读书笔记
    使用表驱动编程设计打印万年历
    maven spring整合mybatis是使用junit测试报字节序列的错误
    idea中建立maven web项卡在Generating Project in Batch mode
    ideaIU-2016.2.5激活
    maven初步入门demo
    Scala基础篇-04 try表达式
    面试题12-旋转数组的最小值
    scala基础篇-03 if与for
  • 原文地址:https://www.cnblogs.com/lay2017/p/12489447.html
Copyright © 2020-2023  润新知