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;
目录
相关文章
|
架构师 Java 数据库连接
Java异常处理的20个最佳实践:告别系统崩溃
你是否在为如何处理异常而困扰? 你是否曾被面试官问道Java异常处理的最佳实践有哪些? 本文汇总了Java异常处理的20个最佳实践:让你告别系统崩溃,面试游刃有余
1908 2
Java异常处理的20个最佳实践:告别系统崩溃
|
8月前
|
数据采集 存储 人工智能
阿里云为何要将数据采集开发套件开源
开源 LoongSuite ,成为 AI 可观测体系中的一块通用拼图。
553 75
|
存储 人工智能 关系型数据库
钉钉ONE选用阿里云PolarDB数据库,实现百亿级数据的高效向量检索
阿里云瑶池PolarDB PostgreSQL版作为钉钉ONE的底层数据库,凭借分布式架构与向量检索能力,支撑百亿级数据、高并发与AI智能推荐,助力钉钉实现“事找人”的办公新范式。
|
8月前
|
弹性计算 容灾 网络安全
2026租赁阿里云服务器的3种方式:ECS配置选择丰富vs费用价格优惠,如何选择?
2026年阿里云服务器租用三大入口:①活动中心抢特价机(低至38元/年),配置固定、价格最优;②ECS页面自定义购买,灵活选地域/规格/带宽等,适合高阶用户;③快速购买,流程极简,新手友好。按需选择,省钱又高效。(239字)
616 5
|
存储 关系型数据库 MySQL
MySQL数据库中进行日期比较的多种方法介绍。
以上方法提供了灵活多样地处理和对比MySQL数据库中存储地不同格式地日子信息方式。根据实际需求选择适当方式能够有效执行所需操作并保证性能优化。
1295 10
|
分布式计算 监控 安全
产品评测|从数据标准到实时监控,深度解析Dataphin如何以智能提效与安全合规驱动企业数据价值释放
Dataphin是阿里巴巴基于OneData方法论打造的一站式数据治理与建设平台,帮助企业实现数据全生命周期管理。本文详细记录了使用Dataphin搭建离线数仓的全流程,包括环境准备、数仓规划、数据引入、处理、周期任务补数据、数据验证与分析等环节。体验中发现其离线管道任务、周期调度、补数据功能便捷高效,但也存在系统稳定性不足、文档更新滞后等问题。建议增强对JSON文件支持、优化资源推荐机制并完善脱敏操作功能,进一步提升用户体验。
视频结构化——原子能力解析
视频结构化是指对视频数据进行结构化处理,通过对原视频进行智能分析,提取出视频中的关键信息,以标签文本的形式进行描述。
1322 0
视频结构化——原子能力解析
|
存储 NoSQL MongoDB
微服务——MongoDB常用命令1——数据库操作
本节介绍了 MongoDB 中数据库的选择、创建与删除操作。使用 `use 数据库名称` 可选择或创建数据库,若数据库不存在则自动创建。通过 `show dbs` 或 `show databases` 查看所有可访问的数据库,用 `db` 命令查看当前数据库。注意,集合仅在插入数据后才会真正创建。数据库命名需遵循 UTF-8 格式,避免特殊字符,长度不超过 64 字节,且部分名称如 `admin`、`local` 和 `config` 为系统保留。删除数据库可通过 `db.dropDatabase()` 实现,主要用于移除已持久化的数据库。
983 0
|
人工智能 安全 物联网
物联网在智能家居中的应用:技术革新与未来展望
【7月更文挑战第3天】物联网在智能家居中推动技术革新,整合智能安防、照明、家电控制及语音助手,提升生活便捷与节能。未来,设备间互联将加强,AI融合优化用户体验,安全隐私保护技术升级,云端服务支持远程管理,预示智能家居更智能、个性化的发展趋势。
1558 3
|
前端开发 JavaScript API
Vue和React,哪个更适合做电商
【4月更文挑战第10天】Vue和React是电商网站开发的热门框架。Vue以其简单易用、响应式数据绑定和完善的生态系统受到青睐;而React凭借强大的组件化、卓越性能及丰富生态脱颖而出。选择取决于项目需求、团队技术栈和性能要求。在决定前,可进行技术调研和原型开发。
431 3