1. JDBC代理
通过代理JDBC接口可以在访问实际的DB操作方法前或者方法后增加扩展能力,Seata也使用了JDBC代理来增强扩展能力。JDBC代理的核心类结构如下:
代理对象(proxy)实现接口方法,并持有实现了同一接口的原始实现类的对象实例,当调用接口方法时,会先调用代理方法,代理方法调用额外定制的逻辑,并调用原始实现类方法。
2. Seata数据操作核心类结构
序号 | 类型 |
---|---|
StatementProxy | StatementProxy为Statement接口实现,代理真实的Statement类,并调用ExecuteTemplate做实际的数据库操作 |
PreparedStatementProxy | PreparedStatement的实现类,主要用于数据插入操作 |
ExecuteTemplate | 负责流程编排,根据不同的条件生成Executor并执行 |
BaseTransactionalExecutor | Executor的基类,为sql执行入口,提供最基本的共性操作,例如绑定XID,设置全局锁标记,使用模版方法模式,将具体SQL执行交给子类实现。 |
AbstractDMLBaseExecutor | AbstractDMLBaseExecutor控制自动提交分支,使用模版方法将undo日志的镜像操作串起来,具体镜像操作由子类实现。 |
StatementCallback | StatementCallback是一个函数接口,用于方便的调用Statement,本身没有任何逻辑 |
SQLRecognizer | 用于标识SQL类型(Insert/Update/Delete)、记录表名、表别名以及原生SQL |
3. 源码
3.1 SQL执行入口
代理类调用SQL执行模版,所以类似下面的方法都调用执行模版:
StatementProxy.java
@Override
public boolean execute(String sql) throws SQLException {
this.targetSQL = sql;
return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0]), sql);
}
3.2 执行模版流程编排,调用不同的Executor
ExecuteTemplate.java
public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,
StatementProxy<S> statementProxy,
StatementCallback<T, S> statementCallback,
Object... args) throws SQLException {
if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
// Just work as original statement
return statementCallback.execute(statementProxy.getTargetStatement(), args);
}
String dbType = statementProxy.getConnectionProxy().getDbType();
if (CollectionUtils.isEmpty(sqlRecognizers)) {
sqlRecognizers = SQLVisitorFactory.get(
statementProxy.getTargetSQL(),
dbType);
}
Executor<T> executor;
if (CollectionUtils.isEmpty(sqlRecognizers)) {
executor = new PlainExecutor<>(statementProxy, statementCallback);
} else {
if (sqlRecognizers.size() == 1) {
SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
switch (sqlRecognizer.getSQLType()) {
case INSERT:
executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,
new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},
new Object[]{statementProxy, statementCallback, sqlRecognizer});
break;
case UPDATE:
executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
case DELETE:
executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
case SELECT_FOR_UPDATE:
executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
default:
executor = new PlainExecutor<>(statementProxy, statementCallback);
break;
}
} else {
executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);
}
}
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;
}
3.3 Executor入口
绑定XID,设置需要全局锁标记,并调用子类方法:
BaseTransactionalExecutor.java
public T execute(Object... args) throws Throwable {
String xid = RootContext.getXID();
if (xid != null) {
statementProxy.getConnectionProxy().bind(xid);
}
statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
// 这里的模版方法交给子类实现
return doExecute(args);
}
3.4 事务分支控制
AbstractDMLBaseExecutor.java
控制分支:
public T doExecute(Object... args) throws Throwable {
AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
if (connectionProxy.getAutoCommit()) {
return executeAutoCommitTrue(args);
} else {
return executeAutoCommitFalse(args);
}
}
非自动提交,同时会生成UNDO日志:
protected T executeAutoCommitFalse(Object[] args) throws Exception {
if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
throw new NotSupportYetException("multi pk only support mysql!");
}
// 模版方法,交给子类实现
TableRecords beforeImage = beforeImage();
// 这里调用了原始的statement执行SQL
T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
// 模版方法,交给子类实现
TableRecords afterImage = afterImage(beforeImage);
// 准备undo日志
prepareUndoLog(beforeImage, afterImage);
return result;
}
自动提交:
protected T executeAutoCommitTrue(Object[] args) throws Throwable {
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
try {
connectionProxy.changeAutoCommit();
return new LockRetryPolicy(connectionProxy).execute(() -> {
// 自动提交依然会写undo日志
T result = executeAutoCommitFalse(args);
connectionProxy.commit();
return result;
});
} catch (Exception e) {
// when exception occur in finally,this exception will lost, so just print it here
LOGGER.error("execute executeAutoCommitTrue error:{}", e.getMessage(), e);
if (!LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {
connectionProxy.getTargetConnection().rollback();
}
throw e;
} finally {
connectionProxy.getContext().reset();
connectionProxy.setAutoCommit(true);
}
}
end.
其他阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃