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