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

 

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