从哪开始分析呢?
seata的基本原理可以参照2PC, 而本地事务方面的实现是通过代理方式实现扩展, 如: 在执行提交前后生成image等
StatementProxy
-
熟悉JDBC的同学应该都知道Statement的作用吧, seata实现Statement功能的代理类就是StatementProxy
-
通过观察StatementProxy里面的方法可以发现所有的操作基本都是委派给
ExecuteTemplate
实现的@Override public int executeUpdate(String sql) throws SQLException { this.targetSQL = sql; return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0]), sql); }
ExecuteTemplate
-
继续追踪
execute()
方法, 上面的内容主要是做一些执行前的判断, 还有就是选择合适的Executor
, 如UpdateExecutor
-
我们只关注较为核心的
executor.execute(args)
public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers, StatementProxy<S> statementProxy, StatementCallback<T, S> statementCallback, Object... args) throws SQLException { ... T rs; try { // 核心代码 rs = executor.execute(args); } catch (Throwable ex) { if (!(ex instanceof SQLException)) { // Turn other exception into SQLException ex = new SQLException(ex); } throw (SQLException) ex; } return rs; }
-
继续则可以一直追踪到
io.seata.rm.datasource.exec.AbstractDMLBaseExecutor#doExecute
@Override public T doExecute(Object... args) throws Throwable { AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); // 通过判断是否自动提交分别执行方法, 这里我们只关注executeAutoCommitFalse, 感兴趣的同学查看executeAutoCommitTrue原理一探究竟 if (connectionProxy.getAutoCommit()) { return executeAutoCommitTrue(args); } else { return executeAutoCommitFalse(args); } }
executeAutoCommitFalse
executeAutoCommitFalse主要做了这么几件事
- 判断只有mysql支持复合主键
- 执行语句之前生成beforeImage
- 执行语句
- 执行语句之后生成afterImage
- 根据beforeImage和afterImage生成UndoLog
protected T executeAutoCommitFalse(Object[] args) throws Exception {
if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && getTableMeta().getPrimaryKeyOnlyName().size() > 1)
{
throw new NotSupportYetException("multi pk only support mysql!");
}
TableRecords beforeImage = beforeImage();
T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
TableRecords afterImage = afterImage(beforeImage);
prepareUndoLog(beforeImage, afterImage);
return result;
}
-
我们先来大体看一下
beforeImage
到底是在干什么@Override protected TableRecords beforeImage() throws SQLException { ArrayList<List<Object>> paramAppenderList = new ArrayList<>(); // 获取数据表元数据信息 TableMeta tmeta = getTableMeta(); // 构建一个select语句, tmeta里有表名, paramAppenderList用来存储参数 String selectSQL = buildBeforeImageSQL(tmeta, paramAppenderList); // 核心操作 return buildTableRecords(tmeta, selectSQL, paramAppenderList); }
protected TableRecords buildTableRecords(TableMeta tableMeta, String selectSQL, ArrayList<List<Object>> paramAppenderList) throws SQLException { ... // 执行获取的select语句, 将结果集rs传入, 接下来方法执行的就是取出结果集的操作, 这里不再深追 return TableRecords.buildRecords(tableMeta, rs); } finally { IOUtil.close(rs); } }
-
afterImage
原理相同, 也就是说beforeImage
和afterImage
的作用就是把执行操作之前的结果集和执行操作之后的结果集查询出来 -
接下来我们看
prepareUndoLog
方法protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException { if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) { return; } ConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage; // 构建全局锁的key String lockKeys = buildLockKey(lockKeyRecords); connectionProxy.appendLockKey(lockKeys); SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage); connectionProxy.appendUndoLog(sqlUndoLog); }
-
buildLockKey()
方法- 注意注释中的
return
, 说明了构建的全局锁的key到底长什么样, 具体构建过程先省略了, 其实就是把记录中包含的所有主键按照规律拼接字符串
/** * build lockKey * * @param rowsIncludingPK the records * @return the string as local key. the local key example(multi pk): "t_user:1_a,2_b" */ protected String buildLockKey(TableRecords rowsIncludingPK) { ... }
- 注意注释中的
-
buildUndoItem()
方法/** * build a SQLUndoLog * * @param beforeImage the before image * @param afterImage the after image * @return sql undo log */ protected SQLUndoLog buildUndoItem(TableRecords beforeImage, TableRecords afterImage) { // 获取sql类型和表名, 创建sqlundoLog对象 SQLType sqlType = sqlRecognizer.getSQLType(); String tableName = sqlRecognizer.getTableName(); SQLUndoLog sqlUndoLog = new SQLUndoLog(); sqlUndoLog.setSqlType(sqlType); sqlUndoLog.setTableName(tableName); sqlUndoLog.setBeforeImage(beforeImage); sqlUndoLog.setAfterImage(afterImage); return sqlUndoLog; }
总结
通过上面的分析我们知道了:
- beforeImage和afterImage是在执行sql之前和之后查询的结果集
- 全局锁的key是由结果集中的主键按照一定规律拼接而成
- undoLog由beforeImage和afterImage组成