Fescar undoExecutor介绍

简介: 开篇  这篇文章的目的主要是讲解RM的执行回滚的Executor对象即undoExecutor,执行回滚日志就是由undoExecutor去执行的。 undoExecutor源码分析 public class UndoExecutorFactory { public static Ab.

开篇

 这篇文章的目的主要是讲解RM的执行回滚的Executor对象即undoExecutor,执行回滚日志就是由undoExecutor去执行的。


undoExecutor源码分析

public class UndoExecutorFactory {

    public static AbstractUndoExecutor getUndoExecutor(
         String dbType, SQLUndoLog sqlUndoLog) {
        if (!dbType.equals(JdbcConstants.MYSQL)) {
            throw new NotSupportYetException(dbType);
        }
        switch (sqlUndoLog.getSqlType()) {
            case INSERT:
                return new MySQLUndoInsertExecutor(sqlUndoLog);
            case UPDATE:
                return new MySQLUndoUpdateExecutor(sqlUndoLog);
            case DELETE:
                return new MySQLUndoDeleteExecutor(sqlUndoLog);
            default:
                throw new ShouldNeverHappenException();
        }
    }
}

说明:

  • UndoExecutorFactory负责根据不同的回滚日志返回对应的undoExecutor对象。



UndoExecutor类依赖图

说明:

  • AbstractUndoExecutor作为回滚类的抽象基类。
  • MySQLUndoDeleteExecutor负责回滚delete操作。
  • MySQLUndoInsertExecutor负责回滚insert操作。
  • MySQLUndoUpdateExecutor负责回滚update操作。


AbstractUndoExecutor

public abstract class AbstractUndoExecutor {

    protected SQLUndoLog sqlUndoLog;

    protected abstract String buildUndoSQL();

    public AbstractUndoExecutor(SQLUndoLog sqlUndoLog) {
        this.sqlUndoLog = sqlUndoLog;
    }

    public void executeOn(Connection conn) throws SQLException {
        dataValidation(conn);

        try {
            // 拼接undoSql的模板
            String undoSQL = buildUndoSQL();
            // 获取PreparedStatement对象
            PreparedStatement undoPST = conn.prepareStatement(undoSQL);
            // 获取回滚的记录
            TableRecords undoRows = getUndoRows();
            // 遍历所有待回滚的记录然后一条条的拼接字段
            for (Row undoRow : undoRows.getRows()) {
                ArrayList<Field> undoValues = new ArrayList<>();
                Field pkValue = null;
                for (Field field : undoRow.getFields()) {
                    if (field.getKeyType() == KeyType.PrimaryKey) {
                        pkValue = field;
                    } else {
                        undoValues.add(field);
                    }
                }
                // 针对每一条回滚记录进行准备
                undoPrepare(undoPST, undoValues, pkValue);
               // 执行回滚操作
                undoPST.executeUpdate();
            }

        } catch (Exception ex) {
            if (ex instanceof SQLException) {
                throw (SQLException) ex;
            } else {
                throw new SQLException(ex);
            }
        }

    }

    protected void undoPrepare(PreparedStatement undoPST, 
              ArrayList<Field> undoValues, 
              Field pkValue) throws SQLException {
        int undoIndex = 0;
        for (Field undoValue : undoValues) {
            undoIndex++;
            undoPST.setObject(undoIndex, undoValue.getValue(), undoValue.getType());
        }
        // PK is at last one.
        // INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?)
        // UPDATE a SET x=?, y=?, z=? WHERE pk = ?
        // DELETE FROM a WHERE pk = ?
        undoIndex++;
        undoPST.setObject(undoIndex, pkValue.getValue(), pkValue.getType());
    }

    protected abstract TableRecords getUndoRows();

    protected void dataValidation(Connection conn) throws SQLException {
        // Validate if data is dirty.
    }
}

说明:

  • AbstractUndoExecutor定义了回滚操作的整个命令行模板流程。
  • 拼接undoSql的模板,buildUndoSQL()。
  • 获取PreparedStatement对象,conn.prepareStatement(undoSQL)。
  • 遍历所有待回滚的记录然后一条条的拼接字段。
  • 针对每一条回滚记录进行准备,undoPrepare(undoPST, undoValues, pkValue)。
  • 执行回滚操作,undoPST.executeUpdate()。
  • buildUndoSQL()和getUndoRows()由子类具体实现。


MySQLUndoInsertExecutor

public class MySQLUndoInsertExecutor extends AbstractUndoExecutor {

    @Override
    protected String buildUndoSQL() {
        TableRecords afterImage = sqlUndoLog.getAfterImage();
        List<Row> afterImageRows = afterImage.getRows();
        if (afterImageRows == null || afterImageRows.size() == 0) {
            throw new ShouldNeverHappenException("Invalid UNDO LOG");
        }
        Row row = afterImageRows.get(0);
        StringBuffer mainSQL = new StringBuffer(
        "DELETE FROM " + sqlUndoLog.getTableName());
        StringBuffer where = new StringBuffer(" WHERE ");
        boolean first = true;
        for (Field field : row.getFields()) {
            if (field.getKeyType() == KeyType.PrimaryKey) {
                where.append(field.getName() + " = ? ");
            }

        }
        return mainSQL.append(where).toString();
    }

    @Override
    protected void undoPrepare(PreparedStatement undoPST, 
           ArrayList<Field> undoValues, Field pkValue) 
        throws SQLException {
        undoPST.setObject(1, pkValue.getValue(), pkValue.getType());
    }

    public MySQLUndoInsertExecutor(SQLUndoLog sqlUndoLog) {
        super(sqlUndoLog);
    }

    @Override
    protected TableRecords getUndoRows() {
        return sqlUndoLog.getAfterImage();
    }
}

说明:

  • Insert的回滚操作在于逆向进行delete操作,MySQLUndoInsertExecutor负责拼接delete的SQL。
  • delete的SQL的where条件就是insert生成的主键primary key。
  • 整个回滚操作在父类AbstractUndoExecutor定义。


MySQLUndoDeleteExecutor

public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor {

    public MySQLUndoDeleteExecutor(SQLUndoLog sqlUndoLog) {
        super(sqlUndoLog);
    }

    @Override
    protected String buildUndoSQL() {
        TableRecords beforeImage = sqlUndoLog.getBeforeImage();
        List<Row> beforeImageRows = beforeImage.getRows();
        if (beforeImageRows == null || beforeImageRows.size() == 0) {
            throw new ShouldNeverHappenException("Invalid UNDO LOG");
        }
        Row row = beforeImageRows.get(0);

        StringBuffer insertColumns = new StringBuffer();
        StringBuffer insertValues = new StringBuffer();
        Field pkField = null;
        boolean first = true;
        for (Field field : row.getFields()) {
            if (field.getKeyType() == KeyType.PrimaryKey) {
                pkField = field;
                continue;
            } else {
                if (first) {
                    first = false;
                } else {
                    insertColumns.append(", ");
                    insertValues.append(", ");
                }
                insertColumns.append(field.getName());
                insertValues.append("?");
            }

        }
        if (first) {
            first = false;
        } else {
            insertColumns.append(", ");
            insertValues.append(", ");
        }
        insertColumns.append(pkField.getName());
        insertValues.append("?");

        return "INSERT INTO " + sqlUndoLog.getTableName() 
        + "(" + insertColumns.toString() + ") 
        VALUES (" + insertValues.toString() + ")";
    }

    @Override
    protected TableRecords getUndoRows() {
        return sqlUndoLog.getBeforeImage();
    }
}

说明:

  • Delete的回滚操作在于逆向进行Insert操作,MySQLUndoDeleteExecutor负责拼接Insert的SQL。
  • Insert的拼接的SQL是insert tableName (column1,column2) values (?,?).
  • 整个回滚操作在父类AbstractUndoExecutor定义。


MySQLUndoUpdateExecutor

public class MySQLUndoUpdateExecutor extends AbstractUndoExecutor {

    @Override
    protected String buildUndoSQL() {
        TableRecords beforeImage = sqlUndoLog.getBeforeImage();
        List<Row> beforeImageRows = beforeImage.getRows();
        if (beforeImageRows == null || beforeImageRows.size() == 0) {
            throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO
        }
        Row row = beforeImageRows.get(0);
        StringBuffer mainSQL = new StringBuffer(
          "UPDATE " + sqlUndoLog.getTableName() + " SET ");
        StringBuffer where = new StringBuffer(" WHERE ");
        boolean first = true;
        for (Field field : row.getFields()) {
            if (field.getKeyType() == KeyType.PrimaryKey) {
                where.append(field.getName() + " = ?");
            } else {
                if (first) {
                    first = false;
                } else {
                    mainSQL.append(", ");
                }
                mainSQL.append(field.getName() + " = ?");
            }

        }
        return mainSQL.append(where).toString();
    }

    public MySQLUndoUpdateExecutor(SQLUndoLog sqlUndoLog) {
        super(sqlUndoLog);
    }

    @Override
    protected TableRecords getUndoRows() {
        return sqlUndoLog.getBeforeImage();
    }
}

说明:

  • update的回滚操作在于逆向进行update操作,MySQLUndoUpdateExecutor负责拼接update的SQL。
  • Insert的拼接的SQL是update tableName set column1=? where column=?。
  • 整个回滚操作在父类AbstractUndoExecutor定义。


Fescar源码分析连载

Fescar 源码解析系列

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
4月前
|
数据采集 存储 人工智能
阿里云为何要将数据采集开发套件开源
开源 LoongSuite ,成为 AI 可观测体系中的一块通用拼图。
378 54
|
JavaScript 关系型数据库 MySQL
开源论坛 flarum 的快速创建 - docker版
开源论坛 flarum 的快速创建 - docker版
2427 2
开源论坛 flarum 的快速创建 - docker版
|
机器学习/深度学习 人工智能 分布式计算
「开源人说」| 大数据王峰——云原生时代,做不忘初心开源牧码人
王峰 阿里巴巴开源委员会大数据AI领域副主席 阿里云开源大数据平台负责人 Flink中文社区发起人
143622 6
「开源人说」| 大数据王峰——云原生时代,做不忘初心开源牧码人
复习单片机:温度计DS18B20代码部分(内含代码部分详解+示意图)
复习单片机:温度计DS18B20代码部分(内含代码部分详解+示意图)
1619 0
复习单片机:温度计DS18B20代码部分(内含代码部分详解+示意图)
|
编解码 算法 数据可视化
Google Earth Engine(GEE)——全球河流宽度数据集1970—2017年
Google Earth Engine(GEE)——全球河流宽度数据集1970—2017年
1004 0
Google Earth Engine(GEE)——全球河流宽度数据集1970—2017年
|
存储 前端开发 NoSQL
项目中的 DO、DTO、BO、VO、POJO的区别是什么
项目中的 DO、DTO、BO、VO、POJO的区别是什么?
|
数据采集 机器学习/深度学习 存储
无感验证码将成新趋势,凯格无感验证码深度剖析
滑动拼图验证码,智能无感验证,文字点选验证码,成语点选验证码,行为验证码,凯格科技,空间推理验证码,字体识别验证码,中文字符验证码
870 0
无感验证码将成新趋势,凯格无感验证码深度剖析
|
前端开发 JavaScript 流计算
CSS3-动画-文字闪烁
CSS3-动画-文字闪烁
1285 0
CSS3-动画-文字闪烁
|
JSON IDE Java
建议收藏 | SpringBoot 元数据配置原来可以这么玩!
Spring Boot jar 包含元数据文件,提供所有支持的配置属性的详细信息。该文件旨在让 IDE 开发者提供上下文相关的帮助和“代码完成”。总结本篇文章希望对从事相关工作的同学能够有所帮助或者启发。
620 0
建议收藏 | SpringBoot 元数据配置原来可以这么玩!