摘要: 原創出處 www.iocoder.cn/Sharding-JD… 「芋道原始碼」歡迎轉載,保留摘要,謝謝!
本文主要基於 Sharding-JDBC 1.5.0 正式版
- 1. 概述
- 2. MergeEngine
- 3. OrderByStreamResultSetMerger
- 4. GroupByStreamResultSetMerger
- 5. GroupByMemoryResultSetMerger
- 6. IteratorStreamResultSetMerger
- 7. LimitDecoratorResultSetMerger
- 666. 彩蛋
???關注微信公眾號:【芋道原始碼】有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
- 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
- 新的原始碼解析文章實時收到通知。每週更新一篇左右。
- 認真的原始碼交流微信群。
1. 概述
本文分享查詢結果歸併的原始碼實現。
正如前文《SQL 執行》提到的“分表分庫,需要執行的 SQL 數量從單條變成了多條”,多個SQL執行結果必然需要進行合併,例如:
SELECT * FROM t_order ORDER BY create_time複製程式碼
在各分片排序完後,Sharding-JDBC 獲取到結果後,仍然需要再進一步排序。目前有 分頁、分組、排序、聚合列、迭代 五種場景需要做進一步處理。當然,如果單分片SQL執行結果是無需合併的。在《SQL 執行》不知不覺已經分享了插入、更新、刪除操作的結果合併,所以下面我們一起看看查詢結果歸併的實現。
Sharding-JDBC 正在收集使用公司名單:傳送門。
? 你的登記,會讓更多人蔘與和使用 Sharding-JDBC。傳送門
Sharding-JDBC 也會因此,能夠覆蓋更多的業務場景。傳送門
登記吧,騷年!傳送門
2. MergeEngine
MergeEngine,分片結果集歸併引擎。
// MergeEngine.java
/**
* 資料庫型別
*/
private final DatabaseType databaseType;
/**
* 結果集集合
*/
private final List<ResultSet> resultSets;
/**
* Select SQL語句物件
*/
private final SelectStatement selectStatement;
/**
* 查詢列名與位置對映
*/
private final Map<String, Integer> columnLabelIndexMap;
public MergeEngine(final DatabaseType databaseType, final List<ResultSet> resultSets, final SelectStatement selectStatement) throws SQLException {
this.databaseType = databaseType;
this.resultSets = resultSets;
this.selectStatement = selectStatement;
// 獲得 查詢列名與位置對映
columnLabelIndexMap = getColumnLabelIndexMap(resultSets.get(0));
}
/**
* 獲得 查詢列名與位置對映
*
* @param resultSet 結果集
* @return 查詢列名與位置對映
* @throws SQLException 當結果集已經關閉
*/
private Map<String, Integer> getColumnLabelIndexMap(final ResultSet resultSet) throws SQLException {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // 後設資料(包含查詢列資訊)
Map<String, Integer> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
result.put(SQLUtil.getExactlyValue(resultSetMetaData.getColumnLabel(i)), i);
}
return result;
}複製程式碼
- 當 MergeEngine 被建立時,會傳入
resultSets
結果集集合,並根據其獲得columnLabelIndexMap
查詢列名與位置對映。通過columnLabelIndexMap
,可以很方便的使用查詢列名獲得在返回結果記錄列( header )的第幾列。
MergeEngine 的 #merge()
方法作為入口提供查詢結果歸併功能。
/**
* 合併結果集.
*
* @return 歸併完畢後的結果集
* @throws SQLException SQL異常
*/
public ResultSetMerger merge() throws SQLException {
selectStatement.setIndexForItems(columnLabelIndexMap);
return decorate(build());
}複製程式碼
#merge()
主體邏輯就兩行程式碼,設定查詢列位置資訊,並返回合適的歸併結果集介面( ResultSetMerger ) 實現。
2.1 SelectStatement#setIndexForItems()
// SelectStatement.java
/**
* 為選擇項設定索引.
*
* @param columnLabelIndexMap 列標籤索引字典
*/
public void setIndexForItems(final Map<String, Integer> columnLabelIndexMap) {
setIndexForAggregationItem(columnLabelIndexMap);
setIndexForOrderItem(columnLabelIndexMap, orderByItems);
setIndexForOrderItem(columnLabelIndexMap, groupByItems);
}複製程式碼
- 部分查詢列是經過推到出來,在 SQL解析 過程中,未獲得到查詢列位置,需要通過該方法進行初始化。對這塊不瞭解的同學,回頭可以看下《SQL 解析(三)之查詢SQL》。? 現在不用回頭,皇冠會掉。
#setIndexForAggregationItem()
處理 AVG聚合計算列 推匯出其對應的 SUM/COUNT 聚合計算列的位置:private void setIndexForAggregationItem(final Map<String, Integer> columnLabelIndexMap) { for (AggregationSelectItem each : getAggregationSelectItems()) { Preconditions.checkState(columnLabelIndexMap.containsKey(each.getColumnLabel()), String.format("Can't find index: %s, please add alias for aggregate selections", each)); each.setIndex(columnLabelIndexMap.get(each.getColumnLabel())); for (AggregationSelectItem derived : each.getDerivedAggregationSelectItems()) { Preconditions.checkState(columnLabelIndexMap.containsKey(derived.getColumnLabel()), String.format("Can't find index: %s", derived)); derived.setIndex(columnLabelIndexMap.get(derived.getColumnLabel())); } } }複製程式碼
#setIndexForOrderItem()
處理 ORDER BY / GROUP BY 列不在查詢列 推匯出的查詢列的位置:private void setIndexForOrderItem(final Map<String, Integer> columnLabelIndexMap, final List<OrderItem> orderItems) { for (OrderItem each : orderItems) { if (-1 != each.getIndex()) { continue; } Preconditions.checkState(columnLabelIndexMap.containsKey(each.getColumnLabel()), String.format("Can't find index: %s", each)); if (columnLabelIndexMap.containsKey(each.getColumnLabel())) { each.setIndex(columnLabelIndexMap.get(each.getColumnLabel())); } } }複製程式碼
2.2 ResultSetMerger
ResultSetMerger,歸併結果集介面。
我們先來看看整體的類結構關係:
從 功能 上分成四種:
- 分組:GroupByMemoryResultSetMerger、GroupByStreamResultSetMerger;包含聚合列
- 排序:OrderByStreamResultSetMerger
- 迭代:IteratorStreamResultSetMerger
- 分頁:LimitDecoratorResultSetMerger
從 實現方式 上分成三種:
- Stream 流式:AbstractStreamResultSetMerger
- Memory 記憶體:AbstractMemoryResultSetMerger
- Decorator 裝飾者:AbstractDecoratorResultSetMerger
什麼時候該用什麼實現方式?
- Stream 流式:將資料遊標與結果集的遊標保持一致,順序的從結果集中一條條的獲取正確的資料。看完下文第三節 OrderByStreamResultSetMerger 可以形象的理解。
- Memory 記憶體:需要將結果集的所有資料都遍歷並儲存在記憶體中,再通過記憶體歸併後,將記憶體中的資料偽裝成結果集返回。看完下文第五節 GroupByMemoryResultSetMerger 可以形象的理解。
- Decorator 裝飾者:可以和前二者任意組合
// MergeEngine.java
/**
* 合併結果集.
*
* @return 歸併完畢後的結果集
* @throws SQLException SQL異常
*/
public ResultSetMerger merge() throws SQLException {
selectStatement.setIndexForItems(columnLabelIndexMap);
return decorate(build());
}
private ResultSetMerger build() throws SQLException {
if (!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) { // 分組 或 聚合列
if (selectStatement.isSameGroupByAndOrderByItems()) {
return new GroupByStreamResultSetMerger(columnLabelIndexMap, resultSets, selectStatement, getNullOrderType());
} else {
return new GroupByMemoryResultSetMerger(columnLabelIndexMap, resultSets, selectStatement, getNullOrderType());
}
}
if (!selectStatement.getOrderByItems().isEmpty()) {
return new OrderByStreamResultSetMerger(resultSets, selectStatement.getOrderByItems(), getNullOrderType());
}
return new IteratorStreamResultSetMerger(resultSets);
}
private ResultSetMerger decorate(final ResultSetMerger resultSetMerger) throws SQLException {
ResultSetMerger result = resultSetMerger;
if (null != selectStatement.getLimit()) {
result = new LimitDecoratorResultSetMerger(result, selectStatement.getLimit());
}
return result;
}複製程式碼
2.2.1 AbstractStreamResultSetMerger
AbstractStreamResultSetMerger,流式歸併結果集抽象類,提供從當前結果集獲得行資料。
public abstract class AbstractStreamResultSetMerger implements ResultSetMerger {
/**
* 當前結果集
*/
private ResultSet currentResultSet;
protected ResultSet getCurrentResultSet() throws SQLException {
if (null == currentResultSet) {
throw new SQLException("Current ResultSet is null, ResultSet perhaps end of next.");
}
return currentResultSet;
}
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
if (Object.class == type) {
return getCurrentResultSet().getObject(columnIndex);
}
if (int.class == type) {
return getCurrentResultSet().getInt(columnIndex);
}
if (String.class == type) {
return getCurrentResultSet().getString(columnIndex);
}
// .... 省略其他資料型別讀取類似程式碼
return getCurrentResultSet().getObject(columnIndex);
}
}複製程式碼
2.2.2 AbstractMemoryResultSetMerger
AbstractMemoryResultSetMerger,記憶體歸併結果集抽象類,提供從記憶體資料行物件( MemoryResultSetRow ) 獲得行資料。
public abstract class AbstractMemoryResultSetMerger implements ResultSetMerger {
private final Map<String, Integer> labelAndIndexMap;
/**
* 記憶體資料行物件
*/
@Setter
private MemoryResultSetRow currentResultSetRow;
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
if (Blob.class == type || Clob.class == type || Reader.class == type || InputStream.class == type || SQLXML.class == type) {
throw new SQLFeatureNotSupportedException();
}
return currentResultSetRow.getCell(columnIndex);
}
}複製程式碼
- 和 AbstractStreamResultSetMerger 對比,貌似區別不大?!確實,從抽象父類上看,兩種實現方式差不多。抽象父類提供給實現子類的是資料讀取的功能,真正的流式歸併、記憶體歸併是在子類實現上體現。
public class MemoryResultSetRow {
/**
* 行資料
*/
private final Object[] data;
public MemoryResultSetRow(final ResultSet resultSet) throws SQLException {
data = load(resultSet);
}
/**
* 載入 ResultSet 當前行資料到記憶體
* @param resultSet 結果集
* @return 行資料
* @throws SQLException 當結果集關閉
*/
private Object[] load(final ResultSet resultSet) throws SQLException {
int columnCount = resultSet.getMetaData().getColumnCount();
Object[] result = new Object[columnCount];
for (int i = 0; i < columnCount; i++) {
result[i] = resultSet.getObject(i + 1);
}
return result;
}
/**
* 獲取資料.
*
* @param columnIndex 列索引
* @return 資料
*/
public Object getCell(final int columnIndex) {
Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
return data[columnIndex - 1];
}
/**
* 設定資料.
*
* @param columnIndex 列索引
* @param value 值
*/
public void setCell(final int columnIndex, final Object value) {
Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
data[columnIndex - 1] = value;
}
}複製程式碼
- 呼叫
#load()
方法,將當前結果集的一條行資料載入到記憶體。
2.2.3 AbstractDecoratorResultSetMerger
AbstractDecoratorResultSetMerger,裝飾結果集歸併抽象類,通過呼叫其裝飾的歸併物件 #getValue()
方法獲得行資料。
public abstract class AbstractDecoratorResultSetMerger implements ResultSetMerger {
/**
* 裝飾的歸併物件
*/
private final ResultSetMerger resultSetMerger;
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
return resultSetMerger.getValue(columnIndex, type);
}
}複製程式碼
3. OrderByStreamResultSetMerger
OrderByStreamResultSetMerger,基於 Stream 方式排序歸併結果集實現。
3.1 歸併演算法
因為各個分片結果集已經排序完成,使用《歸併演算法》能夠充分利用這個優勢。
歸併操作(merge),也叫歸併演算法,指的是將兩個已經排序的序列合併成一個序列的操作。歸併排序演算法依賴歸併操作。
【迭代法】
- 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合併後的序列
- 設定兩個指標,最初位置分別為兩個已經排序序列的起始位置
- 比較兩個指標所指向的元素,選擇相對小的元素放入到合併空間,並移動指標到下一位置
- 重複步驟3直到某一指標到達序列尾
- 將另一序列剩下的所有元素直接複製到合併序列尾
從定義上看,是不是超級符合我們這個場景。? 此時此刻,你是不是捂著胸口,感嘆:“大學怎麼沒好好學資料結構與演算法呢”?反正我是捂著了,都是眼淚。
public class OrderByStreamResultSetMerger extends AbstractStreamResultSetMerger {
/**
* 排序列
*/
@Getter(AccessLevel.NONE)
private final List<OrderItem> orderByItems;
/**
* 排序值物件佇列
*/
private final Queue<OrderByValue> orderByValuesQueue;
/**
* 預設排序型別
*/
private final OrderType nullOrderType;
/**
* 是否第一個 ResultSet 已經呼叫 #next()
*/
private boolean isFirstNext;
public OrderByStreamResultSetMerger(final List<ResultSet> resultSets, final List<OrderItem> orderByItems, final OrderType nullOrderType) throws SQLException {
this.orderByItems = orderByItems;
this.orderByValuesQueue = new PriorityQueue<>(resultSets.size());
this.nullOrderType = nullOrderType;
orderResultSetsToQueue(resultSets);
isFirstNext = true;
}
private void orderResultSetsToQueue(final List<ResultSet> resultSets) throws SQLException {
for (ResultSet each : resultSets) {
OrderByValue orderByValue = new OrderByValue(each, orderByItems, nullOrderType);
if (orderByValue.next()) {
orderByValuesQueue.offer(orderByValue);
}
}
// 設定當前 ResultSet,這樣 #getValue() 能拿到記錄
setCurrentResultSet(orderByValuesQueue.isEmpty() ? resultSets.get(0) : orderByValuesQueue.peek().getResultSet());
}複製程式碼
- 屬性
orderByValuesQueue
使用的佇列實現是優先順序佇列( PriorityQueue )。有興趣的同學可以看看《JDK原始碼研究PriorityQueue》,本文不展開講,不是主角戲份不多。我們記住幾個方法的用途:#offer()
:增加元素。增加時,會將該元素和已有元素們按照優先順序進行排序#peek()
:獲得優先順序第一的元素#pool()
:獲得優先順序第一的元素並移除
一個 ResultSet 構建一個 OrderByValue 用於排序,即上文歸併演算法提到的“空間”。
public final class OrderByValue implements Comparable<OrderByValue> { /** * 已排序結果集 */ @Getter private final ResultSet resultSet; /** * 排序列 */ private final List<OrderItem> orderByItems; /** * 預設排序型別 */ private final OrderType nullOrderType; /** * 排序列對應的值陣列 * 因為一條記錄可能有多個排序列,所以是陣列 */ private List<Comparable<?>> orderValues; /** * 遍歷下一個結果集遊標. * * @return 是否有下一個結果集 * @throws SQLException SQL異常 */ public boolean next() throws SQLException { boolean result = resultSet.next(); orderValues = result ? getOrderValues() : Collections.<Comparable<?>>emptyList(); return result; } /** * 獲得 排序列對應的值陣列 * * @return 排序列對應的值陣列 * @throws SQLException 當結果集關閉時 */ private List<Comparable<?>> getOrderValues() throws SQLException { List<Comparable<?>> result = new ArrayList<>(orderByItems.size()); for (OrderItem each : orderByItems) { Object value = resultSet.getObject(each.getIndex()); Preconditions.checkState(null == value || value instanceof Comparable, "Order by value must implements Comparable"); result.add((Comparable<?>) value); } return result; } /** * 對比 {@link #orderValues},即兩者的第一條記錄 * * @param o 對比 OrderByValue * @return -1 0 1 */ @Override public int compareTo(final OrderByValue o) { for (int i = 0; i < orderByItems.size(); i++) { OrderItem thisOrderBy = orderByItems.get(i); int result = ResultSetUtil.compareTo(orderValues.get(i), o.orderValues.get(i), thisOrderBy.getType(), nullOrderType); if (0 != result) { return result; } } return 0; } }複製程式碼
- 呼叫
OrderByValue#next()
方法時,獲得其對應結果集排在第一條的記錄,通過#getOrderValues()
計算該記錄的排序欄位值。這樣兩個OrderByValue 通過#compareTo()
方法可以比較兩個結果集的第一條記錄。
- 呼叫
if (orderByValue.next()) {
處,呼叫OrderByValue#next()
後,新增到 PriorityQueue。因此,orderByValuesQueue.peek().getResultSet()
能夠獲得多個 ResultSet 中排在第一的。
3.2 #next()
通過呼叫 OrderByStreamResultSetMerger#next()
不斷獲得當前排在第一的記錄。#next()
每次呼叫後,實際做的是當前 ResultSet 的替換,以及當前的 ResultSet 的記錄指向下一條。這樣說起來可能比較繞,我們來看一張圖:
- 白色向下箭頭:OrderByStreamResultSetMerger 對 ResultSet 的指向。
- 黑色箭頭:ResultSet 對當前記錄的指向。
- ps:這塊如果分享的不清晰讓您費勁,十分抱歉。歡迎加我微信(wangwenbin-server)交流下,這樣我也可以優化表述。
// OrderByStreamResultSetMerger.java
@Override
public boolean next() throws SQLException {
if (orderByValuesQueue.isEmpty()) {
return false;
}
if (isFirstNext) {
isFirstNext = false;
return true;
}
// 移除上一次獲得的 ResultSet
OrderByValue firstOrderByValue = orderByValuesQueue.poll();
// 如果上一次獲得的 ResultSet還有下一條記錄,繼續新增到 排序值物件佇列
if (firstOrderByValue.next()) {
orderByValuesQueue.offer(firstOrderByValue);
}
if (orderByValuesQueue.isEmpty()) {
return false;
}
// 設定當前 ResultSet
setCurrentResultSet(orderByValuesQueue.peek().getResultSet());
return true;
}複製程式碼
orderByValuesQueue.poll()
移除上一次獲得的 ResultSet。為什麼不能#setCurrentResultSet()
就移除呢?如果該 ResultSet 裡面還存在下一條記錄,需要繼續參加排序。而判斷是否有下一條,需要呼叫ResultSet#next()
方法,這會導致 ResultSet 指向了下一條記錄。因而orderByValuesQueue.poll()
呼叫是後置的。isFirstNext
變數那的判斷看著是不是很“靈異”?因為#orderResultSetsToQueue()
處設定了第一次的 ResultSet。如果不加這個標記,會導致第一條記錄“不見”了。通過不斷的
Queue#poll()
、Queue#offset()
實現排序。巧妙!彷彿 Get 新技能了:// 移除上一次獲得的 ResultSet OrderByValue firstOrderByValue = orderByValuesQueue.poll(); // 如果上一次獲得的 ResultSet還有下一條記錄,繼續新增到 排序值物件佇列 if (firstOrderByValue.next()) { orderByValuesQueue.offer(firstOrderByValue); }複製程式碼
在看下,我們上文 Stream 方式歸併的定義:將資料遊標與結果集的遊標保持一致,順序的從結果集中一條條的獲取正確的資料。是不是能夠清晰的對上了?!?
4. GroupByStreamResultSetMerger
GroupByStreamResultSetMerger,基於 Stream 方式分組歸併結果集實現。 它繼承自 OrderByStreamResultSetMerger,在排序的邏輯上,實現分組功能。實現原理也較為簡單:
public final class GroupByStreamResultSetMerger extends OrderByStreamResultSetMerger {
/**
* 查詢列名與位置對映
*/
private final Map<String, Integer> labelAndIndexMap;
/**
* Select SQL語句物件
*/
private final SelectStatement selectStatement;
/**
* 當前結果記錄
*/
private final List<Object> currentRow;
/**
* 下一條結果記錄 GROUP BY 條件
*/
private List<?> currentGroupByValues;
public GroupByStreamResultSetMerger(
final Map<String, Integer> labelAndIndexMap, final List<ResultSet> resultSets, final SelectStatement selectStatement, final OrderType nullOrderType) throws SQLException {
super(resultSets, selectStatement.getOrderByItems(), nullOrderType);
this.labelAndIndexMap = labelAndIndexMap;
this.selectStatement = selectStatement;
currentRow = new ArrayList<>(labelAndIndexMap.size());
// 初始化下一條結果記錄 GROUP BY 條件
currentGroupByValues = getOrderByValuesQueue().isEmpty() ? Collections.emptyList() : new GroupByValue(getCurrentResultSet(), selectStatement.getGroupByItems()).getGroupValues();
}
@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
return currentRow.get(columnIndex - 1);
}
@Override
public Object getValue(final String columnLabel, final Class<?> type) throws SQLException {
Preconditions.checkState(labelAndIndexMap.containsKey(columnLabel), String.format("Can't find columnLabel: %s", columnLabel));
return currentRow.get(labelAndIndexMap.get(columnLabel) - 1);
}
}複製程式碼
currentRow
為當前結果記錄,使用#getValue()
、#getCalendarValue()
方法獲得當前結果記錄的查詢列值。currentGroupByValues
為下一條結果記錄 GROUP BY 條件,通過 GroupByValue 生成:public final class GroupByValue { /** * 分組條件值陣列 */ private final List<?> groupValues; public GroupByValue(final ResultSet resultSet, final List<OrderItem> groupByItems) throws SQLException { groupValues = getGroupByValues(resultSet, groupByItems); } /** * 獲得分組條件值陣列 * 例如,`GROUP BY user_id, order_status` 返回的某條記錄結果為 `userId = 1, order_status = 3`,對應的 `groupValues = [1, 3]` * @param resultSet 結果集(單分片) * @param groupByItems 分組列 * @return 分組條件值陣列 * @throws SQLException 當結果集關閉 */ private List<?> getGroupByValues(final ResultSet resultSet, final List<OrderItem> groupByItems) throws SQLException { List<Object> result = new ArrayList<>(groupByItems.size()); for (OrderItem each : groupByItems) { result.add(resultSet.getObject(each.getIndex())); // 從結果集獲得每個分組條件的值 } return result; } }複製程式碼
GroupByStreamResultSetMerger 在建立時,當前結果記錄實際未合併,需要先呼叫
#next()
,在使用#getValue()
等方法獲取值,這個和 OrderByStreamResultSetMerger 不同,可能是個 BUG。
4.1 AggregationUnit
AggregationUnit,歸併計算單元介面,有兩個介面方法:
#merge()
:歸併聚合值#getResult()
:獲取計算結果
一共有三個實現類:
- AccumulationAggregationUnit:累加聚合單元,解決 COUNT、SUM 聚合列
- ComparableAggregationUnit:比較聚合單元,解決 MAX、MIN 聚合列
- AverageAggregationUnit:平均值聚合單元,解決 AVG 聚合列
實現都比較易懂,直接點選連結檢視原始碼,我們就不浪費篇幅貼程式碼啦。
4.2 #next()
我們先看看大體的呼叫流程:
? 看起來程式碼比較多,邏輯其實比較清晰,對照著順序圖順序往下讀即可。
// GroupByStreamResultSetMerger.java
@Override
public boolean next() throws SQLException {
// 清除當前結果記錄
currentRow.clear();
if (getOrderByValuesQueue().isEmpty()) {
return false;
}
//
if (isFirstNext()) {
super.next();
}
// 順序合併下面相同分組條件的記錄
if (aggregateCurrentGroupByRowAndNext()) {
// 生成下一條結果記錄 GROUP BY 條件
currentGroupByValues = new GroupByValue(getCurrentResultSet(), selectStatement.getGroupByItems()).getGroupValues();
}
return true;
}
private boolean aggregateCurrentGroupByRowAndNext() throws SQLException {
boolean result = false;
// 生成計算單元
Map<AggregationSelectItem, AggregationUnit> aggregationUnitMap = Maps.toMap(selectStatement.getAggregationSelectItems(), new Function<AggregationSelectItem, AggregationUnit>() {
@Override
public AggregationUnit apply(final AggregationSelectItem input) {
return AggregationUnitFactory.create(input.getType());
}
});
// 迴圈順序合併下面相同分組條件的記錄
while (currentGroupByValues.equals(new GroupByValue(getCurrentResultSet(), selectStatement.getGroupByItems()).getGroupValues())) {
// 歸併聚合值
aggregate(aggregationUnitMap);
// 快取當前記錄到結果記錄
cacheCurrentRow();
// 獲取下一條記錄
result = super.next();
if (!result) {
break;
}
}
// 設定當前記錄的聚合欄位結果
setAggregationValueToCurrentRow(aggregationUnitMap);
return result;
}
private void aggregate(final Map<AggregationSelectItem, AggregationUnit> aggregationUnitMap) throws SQLException {
for (Entry<AggregationSelectItem, AggregationUnit> entry : aggregationUnitMap.entrySet()) {
List<Comparable<?>> values = new ArrayList<>(2);
if (entry.getKey().getDerivedAggregationSelectItems().isEmpty()) { // SUM/COUNT/MAX/MIN 聚合列
values.add(getAggregationValue(entry.getKey()));
} else {
for (AggregationSelectItem each : entry.getKey().getDerivedAggregationSelectItems()) { // AVG 聚合列
values.add(getAggregationValue(each));
}
}
entry.getValue().merge(values);
}
}
private void cacheCurrentRow() throws SQLException {
for (int i = 0; i < getCurrentResultSet().getMetaData().getColumnCount(); i++) {
currentRow.add(getCurrentResultSet().getObject(i + 1));
}
}
private Comparable<?> getAggregationValue(final AggregationSelectItem aggregationSelectItem) throws SQLException {
Object result = getCurrentResultSet().getObject(aggregationSelectItem.getIndex());
Preconditions.checkState(null == result || result instanceof Comparable, "Aggregation value must implements Comparable");
return (Comparable<?>) result;
}
private void setAggregationValueToCurrentRow(final Map<AggregationSelectItem, AggregationUnit> aggregationUnitMap) {
for (Entry<AggregationSelectItem, AggregationUnit> entry : aggregationUnitMap.entrySet()) {
currentRow.set(entry.getKey().getIndex() - 1, entry.getValue().getResult()); // 獲取計算結果
}
}複製程式碼
5. GroupByMemoryResultSetMerger
GroupByMemoryResultSetMerger,基於 記憶體 分組歸併結果集實現。
區別於 GroupByStreamResultSetMerger,其無法使用每個分片結果集的有序的特點,只能在記憶體中合併後,進行整個重新排序。因而,效能和記憶體都較 GroupByStreamResultSetMerger 會差。
主流程如下:
public final class GroupByMemoryResultSetMerger extends AbstractMemoryResultSetMerger {
/**
* Select SQL語句物件
*/
private final SelectStatement selectStatement;
/**
* 預設排序型別
*/
private final OrderType nullOrderType;
/**
* 記憶體結果集
*/
private final Iterator<MemoryResultSetRow> memoryResultSetRows;
public GroupByMemoryResultSetMerger(
final Map<String, Integer> labelAndIndexMap, final List<ResultSet> resultSets, final SelectStatement selectStatement, final OrderType nullOrderType) throws SQLException {
super(labelAndIndexMap);
this.selectStatement = selectStatement;
this.nullOrderType = nullOrderType;
memoryResultSetRows = init(resultSets);
}
private Iterator<MemoryResultSetRow> init(final List<ResultSet> resultSets) throws SQLException {
Map<GroupByValue, MemoryResultSetRow> dataMap = new HashMap<>(1024); // 分組條件值與記憶體記錄對映
Map<GroupByValue, Map<AggregationSelectItem, AggregationUnit>> aggregationMap = new HashMap<>(1024); // 分組條件值與聚合列對映
// 遍歷結果集
for (ResultSet each : resultSets) {
while (each.next()) {
// 生成分組條件
GroupByValue groupByValue = new GroupByValue(each, selectStatement.getGroupByItems());
// 初始化分組條件到 dataMap、aggregationMap 對映
initForFirstGroupByValue(each, groupByValue, dataMap, aggregationMap);
// 歸併聚合值
aggregate(each, groupByValue, aggregationMap);
}
}
// 設定聚合列結果到記憶體記錄
setAggregationValueToMemoryRow(dataMap, aggregationMap);
// 記憶體排序
List<MemoryResultSetRow> result = getMemoryResultSetRows(dataMap);
// 設定當前 ResultSet,這樣 #getValue() 能拿到記錄
if (!result.isEmpty()) {
setCurrentResultSetRow(result.get(0));
}
return result.iterator();
}
}複製程式碼
#initForFirstGroupByValue()
初始化分組條件到dataMap
,aggregationMap
對映中,這樣可以呼叫#aggregate()
將聚合值歸併到aggregationMap
裡的該分組條件。private void initForFirstGroupByValue(final ResultSet resultSet, final GroupByValue groupByValue, final Map<GroupByValue, MemoryResultSetRow> dataMap, final Map<GroupByValue, Map<AggregationSelectItem, AggregationUnit>> aggregationMap) throws SQLException { // 初始化分組條件到 dataMap if (!dataMap.containsKey(groupByValue)) { dataMap.put(groupByValue, new MemoryResultSetRow(resultSet)); } // 初始化分組條件到 aggregationMap if (!aggregationMap.containsKey(groupByValue)) { Map<AggregationSelectItem, AggregationUnit> map = Maps.toMap(selectStatement.getAggregationSelectItems(), new Function<AggregationSelectItem, AggregationUnit>() { @Override public AggregationUnit apply(final AggregationSelectItem input) { return AggregationUnitFactory.create(input.getType()); } }); aggregationMap.put(groupByValue, map); } }複製程式碼
聚合完每個分組條件後,將聚合列結果
aggregationMap
合併到dataMap
。private void setAggregationValueToMemoryRow(final Map<GroupByValue, MemoryResultSetRow> dataMap, final Map<GroupByValue, Map<AggregationSelectItem, AggregationUnit>> aggregationMap) { for (Entry<GroupByValue, MemoryResultSetRow> entry : dataMap.entrySet()) { // 遍 歷記憶體記錄 for (AggregationSelectItem each : selectStatement.getAggregationSelectItems()) { // 遍歷 每個聚合列 entry.getValue().setCell(each.getIndex(), aggregationMap.get(entry.getKey()).get(each).getResult()); } } }複製程式碼
呼叫
#getMemoryResultSetRows()
方法對記憶體記錄進行記憶體排序。
// GroupByMemoryResultSetMerger.java
private List<MemoryResultSetRow> getMemoryResultSetRows(final Map<GroupByValue, MemoryResultSetRow> dataMap) {
List<MemoryResultSetRow> result = new ArrayList<>(dataMap.values());
Collections.sort(result, new GroupByRowComparator(selectStatement, nullOrderType)); // 記憶體排序
return result;
}
// GroupByRowComparator.java
private int compare(final MemoryResultSetRow o1, final MemoryResultSetRow o2, final List<OrderItem> orderItems) {
for (OrderItem each : orderItems) {
Object orderValue1 = o1.getCell(each.getIndex());
Preconditions.checkState(null == orderValue1 || orderValue1 instanceof Comparable, "Order by value must implements Comparable");
Object orderValue2 = o2.getCell(each.getIndex());
Preconditions.checkState(null == orderValue2 || orderValue2 instanceof Comparable, "Order by value must implements Comparable");
int result = ResultSetUtil.compareTo((Comparable) orderValue1, (Comparable) orderValue2, each.getType(), nullOrderType);
if (0 != result) {
return result;
}
}
return 0;
}複製程式碼
- 總的來說,GROUP BY 記憶體歸併和我們日常使用 Map 計算使用者訂單數是比較相似的。
5.1 #next()
@Override
public boolean next() throws SQLException {
if (memoryResultSetRows.hasNext()) {
setCurrentResultSetRow(memoryResultSetRows.next());
return true;
}
return false;
}複製程式碼
- 記憶體歸併完成後,使用
memoryResultSetRows
不斷獲得下一條記錄。
6. IteratorStreamResultSetMerger
IteratorStreamResultSetMerger,基於 Stream 迭代歸併結果集實現。
public final class IteratorStreamResultSetMerger extends AbstractStreamResultSetMerger {
/**
* ResultSet 陣列迭代器
*/
private final Iterator<ResultSet> resultSets;
public IteratorStreamResultSetMerger(final List<ResultSet> resultSets) {
this.resultSets = resultSets.iterator();
// 設定當前 ResultSet,這樣 #getValue() 能拿到記錄
setCurrentResultSet(this.resultSets.next());
}
@Override
public boolean next() throws SQLException {
// 當前 ResultSet 迭代下一條記錄
if (getCurrentResultSet().next()) {
return true;
}
if (!resultSets.hasNext()) {
return false;
}
// 獲得下一個ResultSet, 設定當前 ResultSet
setCurrentResultSet(resultSets.next());
boolean hasNext = getCurrentResultSet().next();
if (hasNext) {
return true;
}
while (!hasNext && resultSets.hasNext()) {
setCurrentResultSet(resultSets.next());
hasNext = getCurrentResultSet().next();
}
return hasNext;
}
}複製程式碼
7. LimitDecoratorResultSetMerger
LimitDecoratorResultSetMerger,基於 Decorator 分頁結果集歸併實現。
public final class LimitDecoratorResultSetMerger extends AbstractDecoratorResultSetMerger {
/**
* 分頁條件
*/
private final Limit limit;
/**
* 是否全部記錄都跳過了,即無符合條件記錄
*/
private final boolean skipAll;
/**
* 當前已返回行數
*/
private int rowNumber;
public LimitDecoratorResultSetMerger(final ResultSetMerger resultSetMerger, final Limit limit) throws SQLException {
super(resultSetMerger);
this.limit = limit;
skipAll = skipOffset();
}
private boolean skipOffset() throws SQLException {
// 跳過 skip 記錄
for (int i = 0; i < limit.getOffsetValue(); i++) {
if (!getResultSetMerger().next()) {
return true;
}
}
// 行數
rowNumber = limit.isRowCountRewriteFlag() ? 0 : limit.getOffsetValue();
return false;
}
@Override
public boolean next() throws SQLException {
if (skipAll) {
return false;
}
// 獲得下一條記錄
if (limit.getRowCountValue() > -1) {
return ++rowNumber <= limit.getRowCountValue() && getResultSetMerger().next();
}
// 部分db 可以直 offset,不寫 limit 行數,例如 oracle
return getResultSetMerger().next();
}
}複製程式碼
- LimitDecoratorResultSetMerger 可以對其他 ResultSetMerger 進行裝飾,呼叫其他 ResultSetMerger 的
#next()
不斷獲得下一條記錄。
666. 彩蛋
誒?應該是有蠻多地方解釋的不是很清晰,如果讓您閱讀誤解或是阻塞,非常抱歉。程式碼讀起來比較易懂,使用文字來解釋,對錶述能力較差的自己,可能就絞盡腦汁,一臉懵逼。
恩,如果可以,還煩請把讀起來不太爽的地方告訴我,謝謝。
厚著臉皮,道友,分享一波朋友圈可好?
如下是小禮包,嘿嘿
歸併結果集介面 | SQL |
---|---|
OrderByStreamResultSetMerger | SELECT * FROM t_order ORDER BY id |
GroupByStreamResultSetMerger | SELECT uid, AVG(id) FROM t_order GROUP BY uid |
GroupByMemoryResultSetMerger | SELECT uid FROM t_order GROUP BY id ORDER BY id DESC |
IteratorStreamResultSetMerger | SELECT * FROM t_order |
LimitDecoratorResultSetMerger | SELECT * FROM t_order ORDER BY id LIMIT 10 |