简介
在上篇文章中,我们找到了一个逻辑 SQL 转换到真实 SQL 的关键路径代码,本篇文中,我们就上篇基础上,来探索语句解析生成的一些细节
源码解析
语句的关键解析生成的代码如下:
@RequiredArgsConstructorpublic abstract class AbstractSQLBuilder implements SQLBuilder { private final SQLRewriteContext context; @Override public final String toSQL() { if (context.getSqlTokens().isEmpty()) { return context.getSql(); } Collections.sort(context.getSqlTokens()); StringBuilder result = new StringBuilder(); result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex()); for (SQLToken each : context.getSqlTokens()) { result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each)); result.append(getConjunctionText(each)); } return result.toString(); } protected abstract String getSQLTokenText(SQLToken sqlToken);
private String getComposableSQLTokenText(final ComposableSQLToken composableSQLToken) { StringBuilder result = new StringBuilder(); for (SQLToken each : composableSQLToken.getSqlTokens()) { result.append(getSQLTokenText(each)); result.append(getConjunctionText(each)); } return result.toString(); }
private String getConjunctionText(final SQLToken sqlToken) { return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken)); } private int getStartIndex(final SQLToken sqlToken) { int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex(); return Math.min(startIndex, context.getSql().length()); } private int getStopIndex(final SQLToken sqlToken) { int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken); return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex(); }}
复制代码
从 toSQL 函数可以看到,基本都死用 context 变量里面去获取生成真实 SQL 的,其内容大致如下:
其他的 TableToken 比较丰富,包含了很多的信息,可能是逻辑表名需要转成真实表名,所有需要这么多的信息
我们看到 context.getSqlTokens()的长度是 2,那 result 的就会经过三次添加:
下面我们就仔细跟下这三次添加
初始添加
result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
复制代码
上面的语句就是从字符串中截取子字符串添加,从原始的 SQL 截取的,起点是 0,结束点是第一个 SQLTokens 的 startIndex
相应的变化如下:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO
这步感觉对于所有的 SQL 来说确实是通用的,前面这种语句应该都是不用进行解析变换的(如何获取截取的结束点,这个是 SQLToken 的生成部分,这次先不看,先跟一下处理流程)
第一次循环添加: TableToken
for (SQLToken each : context.getSqlTokens()) { result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each)); result.append(getConjunctionText(each)); }
复制代码
从上面的语句看出,目前有两种处理方式:
我们跟踪得到直接走的: getSQLTokenText
跟踪进入下面的函数,如果是 RouteUnitAware,还需要进行处理
public final class RouteSQLBuilder extends AbstractSQLBuilder { @Override protected String getSQLTokenText(final SQLToken sqlToken) { if (sqlToken instanceof RouteUnitAware) { return ((RouteUnitAware) sqlToken).toString(routeUnit); } return sqlToken.toString(); }}
复制代码
看看 TableToken 是如何 ToString 的,相关的细节如下
public final class TableToken extends SQLToken implements Substitutable, RouteUnitAware { @Override public String toString(final RouteUnit routeUnit) { // 得到了真实的表名,真实表名从逻辑表到真实表的转换Map中获取的 String actualTableName = getLogicAndActualTables(routeUnit).get(tableName.getValue().toLowerCase()); // 如果真实表名是null,则获取tableName的值(还能这么写成一句,学到了) // tableName是value值是原始的SQL的 t_order actualTableName = null == actualTableName ? tableName.getValue().toLowerCase() : actualTableName; return tableName.getQuoteCharacter().wrap(actualTableName); } // 从下面的函数大意就可以看出是构建逻辑表名到真实表的映射转换Map private Map<String, String> getLogicAndActualTables(final RouteUnit routeUnit) { Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames(); Map<String, String> result = new HashMap<>(tableNames.size(), 1); for (RouteMapper each : routeUnit.getTableMappers()) { result.put(each.getLogicName().toLowerCase(), each.getActualName()); // 为啥还要再加一次,这里没有理解 result.putAll(shardingRule.getLogicAndActualTablesFromBindingTable(routeUnit.getDataSourceMapper().getLogicName(), each.getLogicName(), each.getActualName(), tableNames)); } return result; }}
复制代码
return tableName.getQuoteCharacter().wrap(actualTableName) 核心代码大致如下,额外的加一些东西(关键字保留字段处理之类的?)
@Getterpublic enum QuoteCharacter { BACK_QUOTE("`", "`"), SINGLE_QUOTE("'", "'"), QUOTE("\"", "\""), BRACKETS("[", "]"), NONE("", ""); private final String startDelimiter; private final String endDelimiter; /** * Wrap value with quote character. * * @param value value to be wrapped * @return wrapped value */ public String wrap(final String value) { return startDelimiter + value + endDelimiter; }}
复制代码
相应的变化如下:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0
下面来到: result.append(getConjunctionText(each));
public abstract class AbstractSQLBuilder implements SQLBuilder { private String getConjunctionText(final SQLToken sqlToken) { return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken)); } private int getStartIndex(final SQLToken sqlToken) { int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex(); return Math.min(startIndex, context.getSql().length()); } private int getStopIndex(final SQLToken sqlToken) { int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken); return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex(); }}
复制代码
大意就是获取开始和结束截取点,算法目前还没领会......,但和 SQLToken 的关系很大,看后面看 SQLToken 的时候能不能得到解答
相应的变化如下:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status
第二次循环添加:
我们看看第二次循环添加:GeneratedKeyInsertColumnToken
@Override protected String getSQLTokenText(final SQLToken sqlToken) { if (sqlToken instanceof RouteUnitAware) { return ((RouteUnitAware) sqlToken).toString(routeUnit); } // 直接走的这 return sqlToken.toString(); }}
public final class extends SQLToken implements Attachable { @Override public String toString() { // 简单粗暴的直接插入一列名 return String.format(", %s", column); }}
复制代码
从上面的大意看出,就是插入一列名,相应的变化如下:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id
接着: result.append(getConjunctionText(each)),变成:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES
第三次循环添加:
我们接着看第三次循环添加:ShardingInsertValuesToken
public final class RouteSQLBuilder extends AbstractSQLBuilder { @Override protected String getSQLTokenText(final SQLToken sqlToken) { if (sqlToken instanceof RouteUnitAware) { // 走的这 return ((RouteUnitAware) sqlToken).toString(routeUnit); } return sqlToken.toString(); }}
public final class ShardingInsertValuesToken extends InsertValuesToken implements RouteUnitAware { public ShardingInsertValuesToken(final int startIndex, final int stopIndex) { super(startIndex, stopIndex); } @Override public String toString(final RouteUnit routeUnit) { StringBuilder result = new StringBuilder(); // 这里得到了:(?, ?, ?, ?), appendInsertValue(routeUnit, result); // 然后又变成了:(?, ?, ?, ?) result.delete(result.length() - 2, result.length()); return result.toString(); } private void appendInsertValue(final RouteUnit routeUnit, final StringBuilder stringBuilder) { for (InsertValue each : getInsertValues()) { if (isAppend(routeUnit, (ShardingInsertValue) each)) { stringBuilder.append(each).append(", "); } } } private boolean isAppend(final RouteUnit routeUnit, final ShardingInsertValue insertValueToken) { if (insertValueToken.getDataNodes().isEmpty() || null == routeUnit) { return true; } for (DataNode each : insertValueToken.getDataNodes()) { if (routeUnit.findTableMapper(each.getDataSourceName(), each.getTableName()).isPresent()) { return true; } } return false; }}
复制代码
对于这个目前不懂,为啥要经过这个处理,应用场景是啥?
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
接着: result.append(getConjunctionText(each)),变成:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
到这里就解析生成完毕了
总结
本篇中详细查看了逻辑 SQL 转真实 SQL 的处理过程:
原始逻辑 SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
result 从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
转换中使用了相应的 Token 类进行对应的处理,组装成真实的 SQL
但算法之类的,目前还是不甚了解的,后面找找官方资料,有没有这方面的
下面应该要去看看 SQLToken 的生成了,通过上面的跟踪发现,真实 SQL 的生成和 SQLToken 是息息相关的
评论