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