Seata源码分析之AbstractUndoExecutor

目录

一、UndoExecutorFactory

二、AbstractUndoExecutor

三、MySQLUndoInsertExecutor

四、MySQLUndoDeleteExecutor 

五、MySQLUndoUpdateExecutor 


UndoExecutor为生成执行undoSql的核心。如果全球事务回滚,它会根据beforeImage和afterImage以及sql类型生成对应的反向sql执行回滚数据,并添加脏数据校验机制,使回滚数据更加可靠。

一、UndoExecutorFactory

UndoExecutorFactory根据sqlType生成对应的AbstractUndoExecutor

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

二、AbstractUndoExecutor

AbstractUndoExecutor是抽象类,提供MySQLUndoInsertExecutor,MySQLUndoUpdateExecutor,MySQLUndoDeleteExecutor 3个实现类。主要在业务失败回滚时,解析sqlUndoLog日志生成对应的回滚sql执行。如果业务是insert那么回滚时生成delete语句,如果业务是update那么回滚时生成的update语句的值为beforeImage的,如果业务是delete那么回滚时生成insert语句。

public abstract class AbstractUndoExecutor {

    // 执行undolog前检查数据sql模板
    private static final String CHECK_SQL_TEMPLATE = "SELECT * FROM %s WHERE %s in (%s)";

    protected abstract String buildUndoSQL();

    protected abstract TableRecords getUndoRows();

    // executeOn执行undolog模板方法
    public void executeOn(Connection conn) throws SQLException {
         // 如果开启了执行回滚日志前检查,则先调用dataValidationAndGoOn方法检查
        if (IS_UNDO_DATA_VALIDATION_ENABLE && !dataValidationAndGoOn(conn)) {
            return;
        }
        try {
	     // 生成回滚sql
            String undoSQL = buildUndoSQL();
            PreparedStatement undoPST = conn.prepareStatement(undoSQL);
	    // 获取undoRows
            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);
		// 执行sql
                undoPST.executeUpdate();
            }

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

        }

    }

    // 检查数据
    protected boolean dataValidationAndGoOn(Connection conn) throws SQLException {
        
        TableRecords beforeRecords = sqlUndoLog.getBeforeImage();
        TableRecords afterRecords = sqlUndoLog.getAfterImage();

        // 检查beforeRecords和afterRecords,如果相等则不用回滚
        if (DataCompareUtils.isRecordsEquals(beforeRecords, afterRecords)) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Stop rollback because there is no data change " +
                        "between the before data snapshot and the after data snapshot.");
            }
            // no need continue undo.
            return false;
        }

        // 检查是否有脏数据
        TableRecords currentRecords = queryCurrentRecords(conn);
        // compare with current data and after image.
        if (!DataCompareUtils.isRecordsEquals(afterRecords, currentRecords)) {
            
            // 如果当前数据和执行后数据不相等,则检查当前数据和之前的数据
            if (DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords)) {
	        // 如果当前数据和之前的数据相等,则表示sql没有执行成功,无需回滚
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Stop rollback because there is no data change " +
                            "between the before data snapshot and the current data snapshot.");
                }
                // no need continue undo.
                return false;
            } else {
	        // 和之前之后数据都不相同,有其他业务操作的脏数据,抛出异常
                throw new SQLException("Has dirty records when undo.");
            }
        }
        return true;
    }


    // 根据主键查询当前数据
    protected TableRecords queryCurrentRecords(Connection conn) throws SQLException {
        TableRecords undoRecords = getUndoRows();
        TableMeta tableMeta = undoRecords.getTableMeta();
        String pkName = tableMeta.getPkName();
        int pkType = tableMeta.getColumnMeta(pkName).getDataType();

        // pares pk values
        Object[] pkValues = parsePkValues(getUndoRows());
        if (pkValues.length == 0) {
            return TableRecords.empty(tableMeta);
        }
        StringBuffer replace = new StringBuffer();
        for (int i = 0; i < pkValues.length; i++) {
            replace.append("?,");
        }
        // build check sql
        String checkSQL = String.format(CHECK_SQL_TEMPLATE, sqlUndoLog.getTableName(), pkName,
                replace.substring(0, replace.length() - 1));
        
        PreparedStatement statement = null;
        ResultSet checkSet = null;
        TableRecords currentRecords;
        try {
            statement = conn.prepareStatement(checkSQL);
            for (int i = 1; i <= pkValues.length; i++) {
                statement.setObject(i, pkValues[i - 1], pkType);
            }
            checkSet = statement.executeQuery();
            currentRecords = TableRecords.buildRecords(tableMeta, checkSet);
        } finally {
            if (checkSet != null) {
                try {
                    checkSet.close();
                } catch (Exception e) {
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (Exception e) {
                }
            }
        }
        return currentRecords;
    }
}

三、MySQLUndoInsertExecutor

public class MySQLUndoInsertExecutor extends AbstractUndoExecutor {

    /**
     * DELETE FROM a WHERE pk = ?
     */
    private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s = ?";

    // 生成删除语句
    protected String buildUndoSQL() {
        KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
        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);
        Field pkField = row.primaryKeys().get(0);
        return String.format(DELETE_SQL_TEMPLATE,
                             keywordChecker.checkAndReplace(sqlUndoLog.getTableName()),
                             keywordChecker.checkAndReplace(pkField.getName()));
    }

    // 设置PreparedStatement,只有主键一个参数
    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);
    }

    // 获取生成sql的参数数据为执行后新增的数据
    protected TableRecords getUndoRows() {
        return sqlUndoLog.getAfterImage();
    }
}

四、MySQLUndoDeleteExecutor 

public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor {

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

    /**
     * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?)
     */
    private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)";

    
    // 生成insert语句
    protected String buildUndoSQL() {
        KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
        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);
        List<Field> fields = new ArrayList<>(row.nonPrimaryKeys());
        Field pkField = row.primaryKeys().get(0);
        // PK is at last one.
        fields.add(pkField);

        String insertColumns = fields.stream()
            .map(field -> keywordChecker.checkAndReplace(field.getName()))
            .collect(Collectors.joining(", "));
        String insertValues = fields.stream().map(field -> "?")
            .collect(Collectors.joining(", "));

        return String.format(INSERT_SQL_TEMPLATE, keywordChecker.checkAndReplace(sqlUndoLog.getTableName()),
                             insertColumns, insertValues);
    }

    // 获取生成undosql的数据参数,为删除前的数据
    protected TableRecords getUndoRows() {
        return sqlUndoLog.getBeforeImage();
    }
}

五、MySQLUndoUpdateExecutor 

public class MySQLUndoUpdateExecutor extends AbstractUndoExecutor {

    /**
     * UPDATE a SET x = ?, y = ?, z = ? WHERE pk = ?
     */
    private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s = ?";

    // 生成update的语句,set值为之前的数据,where条件为主键值
    protected String buildUndoSQL() {
        KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL);
        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);
        Field pkField = row.primaryKeys().get(0);
        List<Field> nonPkFields = row.nonPrimaryKeys();
        String updateColumns = nonPkFields.stream()
            .map(field -> keywordChecker.checkAndReplace(field.getName()) + " = ?")
            .collect(Collectors.joining(", "));
        return String.format(UPDATE_SQL_TEMPLATE, keywordChecker.checkAndReplace(sqlUndoLog.getTableName()),
                             updateColumns, keywordChecker.checkAndReplace(pkField.getName()));
    }

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

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章