阿飛Javaer,轉(zhuǎn)載請注明原創(chuàng)出處遗遵,謝謝瘦材!
核心源碼就在sharding-jdbc-core
模塊的com.dangdang.ddframe.rdb.sharding.rewrite
目錄下尸疆,包含兩個(gè)文件SQLBuilder和SQLRewriteEngine;測試用例入口為SQLRewriteEngineTest硼被,下面從SQLRewriteEngineTest中debug源碼分析sharding-jdbc的重寫是如何實(shí)現(xiàn)的:
SQLRewriteEngineTest中某個(gè)測試用例如下--主要包括表名示损,offset,limit(rowCount)的重寫:
@Test
public void assertRewriteForLimit() {
selectStatement.setLimit(new Limit(true));
// offset的值就是limit offset,rowCount中offset的值
selectStatement.getLimit().setOffset(new LimitValue(2, -1));
// rowCount的值就是limit offset,rowCount中rowCount的值
selectStatement.getLimit().setRowCount(new LimitValue(2, -1));
// TableToken的值表示表名table_x在原始SQL語句的偏移量是17的位置
selectStatement.getSqlTokens().add(new TableToken(17, "table_x"));
// OffsetToken的值表示offset在原始SQL語句的偏移量是33的位置(2就是offset的值)
selectStatement.getSqlTokens().add(new OffsetToken(33, 2));
// RowCountToken的值表示rowCount在原始SQL語句的偏移量是36的位置(2就是rowCount的值)
selectStatement.getSqlTokens().add(new RowCountToken(36, 2));
// selectStatement值模擬過程嚷硫,實(shí)際上是SQL解釋過程(SQL解釋會單獨(dú)分析)
SQLRewriteEngine rewriteEngine = new SQLRewriteEngine(shardingRule, "SELECT x.id FROM table_x x LIMIT 2, 2", selectStatement);
// 重寫的核心就是這里了:rewriteEngine.rewrite(true)
assertThat(rewriteEngine.rewrite(true).toSQL(tableTokens), is("SELECT x.id FROM table_1 x LIMIT 0, 4"));
}
重寫方法核心源碼:
從這段源碼可知检访,sql重寫主要包括對表名,limit offset, rowNum以及order by的重寫(ItemsToken值對select col1, col2 from... 即查詢結(jié)果列的重寫--指那些由于ordre by或者group by需要增加的結(jié)果列)仔掸;
public SQLBuilder rewrite(final boolean isRewriteLimit) {
SQLBuilder result = new SQLBuilder();
if (sqlTokens.isEmpty()) {
result.appendLiterals(originalSQL);
return result;
}
int count = 0;
// 根據(jù)Token的beginPosition即出現(xiàn)的位置排序
sortByBeginPosition();
for (SQLToken each : sqlTokens) {
if (0 == count) {
// 第一次處理:截取從原生SQL的開始位置到第一個(gè)token起始位置之間的內(nèi)容脆贵,例如"SELECT x.id FROM table_x x LIMIT 2, 2"這條SQL的第一個(gè)token是TableToken,即table_x所在位置起暮,所以截取內(nèi)容為"SELECT x.id FROM "
result.appendLiterals(originalSQL.substring(0, each.getBeginPosition()));
}
if (each instanceof TableToken) {
// 看后面的"表名重寫分析"
appendTableToken(result, (TableToken) each, count, sqlTokens);
} else if (each instanceof ItemsToken) {
// ItemsToken是指當(dāng)邏輯SQL有order by卖氨,group by這樣的特殊條件時(shí),需要在select的結(jié)果列中增加一些結(jié)果列,例如執(zhí)行邏輯SQL:"SELECT o.* FROM t_order o where o.user_id=? order by o.order_id desc limit 2,3"筒捺,那么還需要增加結(jié)果列o.order_id AS ORDER_BY_DERIVED_0
appendItemsToken(result, (ItemsToken) each, count, sqlTokens);
} else if (each instanceof RowCountToken) {
// 看后面的"rowCount重寫分析"
appendLimitRowCount(result, (RowCountToken) each, count, sqlTokens, isRewriteLimit);
} else if (each instanceof OffsetToken) {
// 看后面的"offset重寫分析"
appendLimitOffsetToken(result, (OffsetToken) each, count, sqlTokens, isRewriteLimit);
} else if (each instanceof OrderByToken) {
appendOrderByToken(result, count, sqlTokens);
}
count++;
}
return result;
}
private void sortByBeginPosition() {
Collections.sort(sqlTokens, new Comparator<SQLToken>() {
// 升序排列
@Override
public int compare(final SQLToken o1, final SQLToken o2) {
return o1.getBeginPosition() - o2.getBeginPosition();
}
});
}
表名重寫分析
private void appendTableToken(final SQLBuilder sqlBuilder, final TableToken tableToken, final int count, final List<SQLToken> sqlTokens) {
String tableName = sqlStatement.getTables().getTableNames().contains(tableToken.getTableName()) ? tableToken.getTableName() : tableToken.getOriginalLiterals();
// append表名特殊處理
sqlBuilder.appendTable(tableName);
int beginPosition = tableToken.getBeginPosition() + tableToken.getOriginalLiterals().length();
appendRest(sqlBuilder, count, sqlTokens, beginPosition);
}
// append表名特殊處理柏腻,把TableToken也要添加到SQLBuilder中(List<Object> segments)
public void appendTable(final String tableName) {
segments.add(new TableToken(tableName));
currentSegment = new StringBuilder();
segments.add(currentSegment);
}
offset重寫分析
private void appendLimitOffsetToken(final SQLBuilder sqlBuilder, final OffsetToken offsetToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {
// offset的重寫比較簡單:如果要重寫,則offset置為0系吭,否則保留offset的值五嫂;
sqlBuilder.appendLiterals(isRewrite ? "0" : String.valueOf(offsetToken.getOffset()));
int beginPosition = offsetToken.getBeginPosition() + String.valueOf(offsetToken.getOffset()).length();
appendRest(sqlBuilder, count, sqlTokens, beginPosition);
}
rowCount重寫分析
private void appendLimitRowCount(final SQLBuilder sqlBuilder, final RowCountToken rowCountToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {
SelectStatement selectStatement = (SelectStatement) sqlStatement;
Limit limit = selectStatement.getLimit();
if (!isRewrite) {
// 如果不需要重寫sql中的limit的話(例如select * from t limit 10),那么肯尺,直接append rowCount的值即可沃缘;
sqlBuilder.appendLiterals(String.valueOf(rowCountToken.getRowCount()));
} else if ((!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems()) {
// 如果要重寫sql中的limit的話,且sql中有g(shù)roup by或者有g(shù)roup by & order by则吟,例如""SELECT o.* FROM t_order o where o.user_id=? group by o.order_id order by o.order_id desc limit 2,3"需要"孩灯,那么重寫為Integer.MAX_VALUE,原因在下文分析逾滥,請點(diǎn)擊連接:
sqlBuilder.appendLiterals(String.valueOf(Integer.MAX_VALUE));
} else {
// 否則只需要將limit offset,rowCount重寫為limit 0, offset+rowCount即可;
sqlBuilder.appendLiterals(String.valueOf(limit.isRowCountRewriteFlag() ? rowCountToken.getRowCount() + limit.getOffsetValue() : rowCountToken.getRowCount()));
}
int beginPosition = rowCountToken.getBeginPosition() + String.valueOf(rowCountToken.getRowCount()).length();
appendRest(sqlBuilder, count, sqlTokens, beginPosition);
}
appendRest分析
private void appendRest(final SQLBuilder sqlBuilder, final int count, final List<SQLToken> sqlTokens, final int beginPosition) {
// 如果SQL解析后只有一個(gè)token败匹,那么結(jié)束位置(endPosition)就是sql末尾寨昙;否則結(jié)束位置就是到下一個(gè)token的起始位置
int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();
sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));
}
所有重寫最后都會調(diào)用appendRest(),即附加上余下部分內(nèi)容掀亩,這個(gè)余下部分內(nèi)容是指從當(dāng)前處理的token到下一個(gè)token之間的內(nèi)容舔哪,例如SQL為
SELECT x.id FROM table_x x LIMIT 5, 10
,當(dāng)遍歷到table_x
槽棍,即處理完TableToken后捉蚤,由于下一個(gè)token為OffsetToken,即5炼七,所以appendRest就是append這一段內(nèi)容:" x LIMIT "--從table_x到5之間的內(nèi)容缆巧;
SQLBuilder.toString()分析
重寫完后,調(diào)用SQLBuilder的toString()方法生成重寫后最終的SQL語句豌拙;
public String toSQL(final Map<String, String> tableTokens) {
StringBuilder result = new StringBuilder();
for (Object each : segments) {
// 如果是TableToken陕悬,并且是分庫分表相關(guān)表,那么append最終的實(shí)際表名按傅,例如t_order的實(shí)際表名可能是t_order_1
if (each instanceof TableToken && tableTokens.containsKey(((TableToken) each).tableName)) {
result.append(tableTokens.get(((TableToken) each).tableName));
} else {
result.append(each);
}
}
return result.toString();
}