ShardingJDBC源码阅读(八)结果合并(上)

1,268 阅读11分钟

前言

本章分析ShardingJDBC的核心步骤:结果合并。

一、QueryResult

public interface QueryResult {

    boolean next() throws SQLException;

    Object getValue(int columnIndex, Class<?> type) throws SQLException;

    Object getCalendarValue(int columnIndex, Class<?> type, Calendar calendar) throws SQLException;

    InputStream getInputStream(int columnIndex, String type) throws SQLException;

    boolean wasNull() throws SQLException;
 
    int getColumnCount() throws SQLException;

    String getColumnName(int columnIndex) throws SQLException;

    String getColumnLabel(int columnIndex) throws SQLException;
}

QueryResult是对java.sql.ResultSet的封装,提供了和ResultSet一样的next方法,其他方法看名字也很容易理解。

重点看一下QueryResult的两个实现。

1、MemoryQueryResult

MemoryQueryResult对应CONNECTION_STRICTLY连接限制模式。当sql数量大于单次查询最大连接数限制(max.connections.size.per.query默认1)启用。采用内存归并一次性读取ResultSet结果集放入内存,防止使用过多数据库连接。

public final class MemoryQueryResult implements QueryResult {
    // java.sql.ResultSetMetaData ResultSet元数据信息
    private final ResultSetMetaData resultSetMetaData;
    // 一次性读取ResultSet结果集的行集合
    private final Iterator<List<Object>> rows;
    // 当前行
    private List<Object> currentRow;
}

MemoryQueryResult构造时,将ResultSet结果集全部读入内存,放入rows。

public MemoryQueryResult(final ResultSet resultSet) throws SQLException {
  resultSetMetaData = resultSet.getMetaData();
  rows = getRows(resultSet);
}

private Iterator<List<Object>> getRows(final ResultSet resultSet) throws SQLException {
  Collection<List<Object>> result = new LinkedList<>();
  while (resultSet.next()) {
      List<Object> rowData = new ArrayList<>(resultSet.getMetaData().getColumnCount());
      for (int columnIndex = 1; columnIndex <= resultSet.getMetaData().getColumnCount(); columnIndex++) {
          Object rowValue = getRowValue(resultSet, columnIndex);
          rowData.add(resultSet.wasNull() ? null : rowValue);
      }
      result.add(rowData);
  }
  return result.iterator();
}

一般使用ResultSet都是用next方法和getXXX方法,对应QueryResult是next方法和getValue方法。

private final Iterator<List<Object>> rows;
private List<Object> currentRow;
@Override
public boolean next() {
    // 如果还有数据,移动currentRow至下一行,并返回true
    if (rows.hasNext()) {
        currentRow = rows.next();
        return true;
    }
    // 没数据返回false
    currentRow = null;
    return false;
}

@Override
public Object getValue(final int columnIndex, final Class<?> type) {
    // 获取当前行的columnIndex-1下标对应的字段值
    return currentRow.get(columnIndex - 1);
}

2、StreamQueryResult

StreamQueryResult对应MEMORY_STRICTLY内存限制模式。当sql数量小于等于单次查询最大连接数限制(max.connections.size.per.query默认1)启用。采用流式归并移动ResultSet指针获取数据,减少因为一次性读取结果集造成过多的内存开销。

public final class StreamQueryResult implements QueryResult {
    // java.sql.ResultSetMetaData
    private final ResultSetMetaData resultSetMetaData;
    // java.sql.ResultSet
    private final ResultSet resultSet;
    public StreamQueryResult(final ResultSet resultSet) throws SQLException {
        resultSetMetaData = resultSet.getMetaData();
        this.resultSet = resultSet;
    }
}

StreamQueryResult简单封装了ResultSet,基本所有QueryResult接口方法的实现,都是直接委托给ResultSet。

@Override
public boolean next() throws SQLException {
  return resultSet.next();
}

@Override
public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
  if (boolean.class == type) {
      return resultSet.getBoolean(columnIndex);
  } else if (byte.class == type) {
      return resultSet.getByte(columnIndex);
  }
  // ...
}

二、根据ConnectionMode选择QueryResult实现

上一章讲过,ConnectionMode是用于选择QueryResult实现的。

如果maxConnectionsSizePerQuery(单次查询最大连接数)< sql数量,使用CONNECTION_STRICTLY连接限制。采用内存归并,一次性读取ResultSet数据到内存,减少数据库连接开销。 如果maxConnectionsSizePerQuery(单次查询最大连接数)>= sql数量,使用MEMORY_STRICTLY内存限制。采用流式归并,ResultSet移动游标读取数据到内存,减少内存开销。

但是其实也不然,如果用户调用ResultSet resultSet = statement.executeQuery()执行sql,是根据ConnectionMode选择QueryResult实现,这是没问题的。具体见PreparedStatementExecutor#executeQuery

public List<QueryResult> executeQuery() throws SQLException {
  final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
  // callback
  SQLExecuteCallback<QueryResult> executeCallback = new SQLExecuteCallback<QueryResult>(getDatabaseType(), isExceptionThrown) {
      @Override
      protected QueryResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode) throws SQLException {
          return getQueryResult(statement, connectionMode);
      }
  };
  return executeCallback(executeCallback);
}

private QueryResult getQueryResult(final Statement statement, final ConnectionMode connectionMode) throws SQLException {
  PreparedStatement preparedStatement = (PreparedStatement) statement;
  ResultSet resultSet = preparedStatement.executeQuery();
  getResultSets().add(resultSet);
  // 根据ConnectionMode选择QueryResult实现
  return ConnectionMode.MEMORY_STRICTLY == connectionMode ? new StreamQueryResult(resultSet) : new MemoryQueryResult(resultSet);
}

如果用户调用的是statement.getResultSet,像下面这样。

statement.execute();
ResultSet resultSet = statement.getResultSet();

QueryResult实现会选择流式归并StreamQueryResult,代码见ShardingPreparedStatement#getResultSet

private ExecutionContext executionContext;
// ShardingResultSet
private ResultSet currentResultSet;
@Override
public ResultSet getResultSet() throws SQLException {
  if (null != currentResultSet) {
      return currentResultSet;
  }
  if (executionContext.getSqlStatementContext() instanceof SelectStatementContext || executionContext.getSqlStatementContext().getSqlStatement() instanceof DALStatement) {
      // 对每个statement执行getResultSet得到结果集
      List<ResultSet> resultSets = getResultSets();
      // 将ResultSet都转换为QueryResult
      List<QueryResult> queryResults = getQueryResults(resultSets);
      // 执行MergeEngine
      MergedResult mergedResult = mergeQuery(queryResults);
      // 生成ShardingResultSet返回
      currentResultSet = new ShardingResultSet(resultSets, mergedResult, this, executionContext);
  }
  return currentResultSet;
}

getQueryResults方法,直接new了StreamQueryResult。

private List<QueryResult> getQueryResults(final List<ResultSet> resultSets) throws SQLException {
  List<QueryResult> result = new ArrayList<>(resultSets.size());
  for (ResultSet each : resultSets) {
      if (null != each) {
          result.add(new StreamQueryResult(each));
      }
  }
  return result;
}

三、MergedResult

结果归并最终都会交由MergeEngine处理,入参是QueryResult集合,出参是MergedResult。MergedResult的方法与QueryResult大多都类似,只是MergedResult代表的是合并之后的结果。

public interface MergedResult {
    
    boolean next() throws SQLException;
    
    Object getValue(int columnIndex, Class<?> type) throws SQLException;
    
    Object getCalendarValue(int columnIndex, Class<?> type, Calendar calendar) throws SQLException;
    
    InputStream getInputStream(int columnIndex, String type) throws SQLException;
    
    boolean wasNull() throws SQLException;
}

MergedResult多种多样,这里看四个有代表性的抽象实现。

1、DecoratorMergedResult

@RequiredArgsConstructor
@Getter
public abstract class DecoratorMergedResult implements MergedResult {
    // 合并结果
    private final MergedResult mergedResult;
    @Override
    public final Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
        return mergedResult.getValue(columnIndex, type);
    }
    @Override
    public final Object getCalendarValue(final int columnIndex, final Class<?> type, final Calendar calendar) throws SQLException {
        return mergedResult.getCalendarValue(columnIndex, type, calendar);
    }
    @Override
    public final InputStream getInputStream(final int columnIndex, final String type) throws SQLException {
        return mergedResult.getInputStream(columnIndex, type);
    }
    @Override
    public final boolean wasNull() throws SQLException {
        return mergedResult.wasNull();
    }
}

DecoratorMergedResult的想法,类似笛卡尔积路由引擎ShardingCartesianRoutingEngine。它只会在有合并结果之后被构造使用,用于装饰某个MergedResult。默认用上次合并结果实现了四个方法,子类需要实现关键的next方法。

DecoratorMergedResult有三个实现类,都与分页有关,这里看MySQL的实现LimitDecoratorMergedResult

public final class LimitDecoratorMergedResult extends DecoratorMergedResult {
    // 分页上下文 从 SelectStatementContext中获取的
    private final PaginationContext pagination;
    // 是否跳过所有结果
    private final boolean skipAll;
    // 当前行数
    private int rowNumber;
    // 构造
    public LimitDecoratorMergedResult(final MergedResult mergedResult, final PaginationContext pagination) throws SQLException {
        super(mergedResult);
        this.pagination = pagination;
        // 计算是否需要跳过所有行
        skipAll = skipOffset();
    }
    
    private boolean skipOffset() throws SQLException {
        // 如果结果集中的数据量,不足offset,返回false
        for (int i = 0; i < pagination.getActualOffset(); i++) {
            if (!getMergedResult().next()) {
                return true;
            }
        }
        // 否则返回true,当前行设置为0
        rowNumber = 0;
        return false;
    }
    
    @Override
    public boolean next() throws SQLException {
        // 如果结果集中的数据量不足offset,直接返回false
        if (skipAll) {
            return false;
        }
        // 如果rowCount不存在,返回装饰的MergedResult.next
        if (!pagination.getActualRowCount().isPresent()) {
            return getMergedResult().next();
        }
        // 返回 当前行小于rowCount 且 MergedResult.next
        return ++rowNumber <= pagination.getActualRowCount().get() && getMergedResult().next();
    }
}

总得来说,就是对于select * from t_order limit 20,10(分页查询订单第三页10条数据),如果结果集不满20条数据(不存在第三页),next方法返回false(skipAll=true);如果rowNumber小于分页行数(10条数据),返回包装MergeResult的next方法。

2、TransparentMergedResult

TransparentMergedResult把所有的MergedResult需要实现的方法都直接委托QueryResult实现了,方法名字都一样,代码就不贴了。适配器模式,类似FutureTask持有Callable实现Runnable。

3、MemoryMergedResult

MemoryMergedResult和MemoryQueryResult类似,都是将输入的数据完全读入内存的一个集合中,然后提供给外部next、get方法迭代获取元素。

public abstract class MemoryMergedResult<T extends BaseRule> implements MergedResult {
    // 游标
    private final Iterator<MemoryQueryResultRow> memoryResultSetRows;
    // 当前行
    private MemoryQueryResultRow currentResultSetRow;
    // 是否为空
    private boolean wasNull;
    
    protected MemoryMergedResult(final T rule, final SchemaMetaData schemaMetaData, final SQLStatementContext sqlStatementContext, final List<QueryResult> queryResults) throws SQLException {
        // 子类实现init方法构造MemoryQueryResultRow集合
        List<MemoryQueryResultRow> memoryQueryResultRowList = init(rule, schemaMetaData, sqlStatementContext, queryResults);
        memoryResultSetRows = memoryQueryResultRowList.iterator();
        if (!memoryQueryResultRowList.isEmpty()) {
            currentResultSetRow = memoryQueryResultRowList.get(0);
        }
    }
}

MemoryMergedResult父类通过memoryResultSetRows迭代器和memoryResultSetRows当前行可以实现next等MergedResult接口的方法。

@Override
public final boolean next() {
    if (memoryResultSetRows.hasNext()) {
        currentResultSetRow = memoryResultSetRows.next();
        return true;
    }
    return false;
}

@Override
public final 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();
    }
    Object result = currentResultSetRow.getCell(columnIndex);
    wasNull = null == result;
    return result;
}

只需要子类提供init方法的实现,返回一个MemoryQueryResultRow集合。

MemoryQueryResultRow构造时将QueryResult中的数据通过load方法放入成员变量data数组中,这也是MemoryMergedResult区别于StreamMergedResult的地方,它将数据全部读入内存中处理。

此外MemoryQueryResultRow提供了get、set方法修改data数组。

public final class MemoryQueryResultRow {
    
    private final Object[] data;
    
    public MemoryQueryResultRow(final QueryResult queryResult) throws SQLException {
        data = load(queryResult);
    }
    
    private Object[] load(final QueryResult queryResult) throws SQLException {
        int columnCount = queryResult.getColumnCount();
        Object[] result = new Object[columnCount];
        for (int i = 0; i < columnCount; i++) {
            result[i] = queryResult.getValue(i + 1, Object.class);
        }
        return result;
    }
    public Object getCell(final int columnIndex) {
        Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
        return data[columnIndex - 1];
    }
    public void setCell(final int columnIndex, final Object value) {
        Preconditions.checkArgument(columnIndex > 0 && columnIndex < data.length + 1);
        data[columnIndex - 1] = value;
    }
}

MemoryMergedResult的实现类GroupByMemoryMergedResult处理groupby分组聚合逻辑。看看它的init方法。

@Override
protected List<MemoryQueryResultRow> init(final ShardingRule shardingRule,
                                        final SchemaMetaData schemaMetaData, 
                                        final SQLStatementContext sqlStatementContext, 
                                        final List<QueryResult> queryResults) throws SQLException {
  SelectStatementContext selectStatementContext = (SelectStatementContext) sqlStatementContext;
  // 单个groupby值 - 合并结果集
  Map<GroupByValue, MemoryQueryResultRow> dataMap = new HashMap<>(1024);
  // 单个groupby值 - 聚合条件(比如sum、avg、count) - 聚合结果
  Map<GroupByValue, Map<AggregationProjection, AggregationUnit>> aggregationMap = new HashMap<>(1024);
  for (QueryResult each : queryResults) {
      while (each.next()) {
          // 从每行返回的数据里,取出groupby对应的字段的值,构造GroupByValue
          GroupByValue groupByValue = new GroupByValue(each, selectStatementContext.getGroupByContext().getItems());
          // 初始化dataMap和aggregationMap对应的第一个kv对
          initForFirstGroupByValue(selectStatementContext, each, groupByValue, dataMap, aggregationMap);
          // 合并 调用AggregationUnit执行合并操作,结果暂存在AggregationUnit
          aggregate(selectStatementContext, each, groupByValue, aggregationMap);
      }
  }
  // 将aggMap中AggregationUnit计算得到的数据,更新到dataMap的MemoryQueryResultRow中
  setAggregationValueToMemoryRow(selectStatementContext, dataMap, aggregationMap);
  // 根据表的元数据信息,判断字段是否大小写敏感(后续排序的依据)
  List<Boolean> valueCaseSensitive = queryResults.isEmpty() ? Collections.emptyList() : getValueCaseSensitive(queryResults.iterator().next(), selectStatementContext, schemaMetaData);
  // 将dataMap的MemoryQueryResultRow排序并返回
  return getMemoryResultSetRows(selectStatementContext, dataMap, valueCaseSensitive);
}

举个例子:select user_id, date_format(created_at, '%Y-%m'), avg(paid_amount) from t_order group by user_id, date_format(created_at,'%Y-%m')统计用户每月的每单平均支付金额。(这里SelectStatementContext#isSameGroupByAndOrderByItems返回true,其实不会走GroupByMemoryMergedResult,会走GroupByStreamMergedResult,但是逻辑是一样的

针对每个user_id和月(GroupByValue),累计单量,累计支付金额,然后用累计支付金额/累计单量即可(AggregationProjection&AggregationUnit)。

GroupByValue根据groupby字段,找出对应的groupby字段的值,作为每行数据的聚合维度。比如groupby字段是user_id,这行数据的user_id是1,那groupValues列表就是[1]。

@Getter
@EqualsAndHashCode // 重写了equals&hasCode方法 作为Map的Key
public final class GroupByValue {
    // groupby的值列表 比如user_id=1,月份=2020-12
    private final List<?> groupValues;
    
    public GroupByValue(final QueryResult queryResult, final Collection<OrderByItem> groupByItems) throws SQLException {
        groupValues = getGroupByValues(queryResult, groupByItems);
    }
    // 根据groupby字段所处select字段的下标,找到groupby的值放入groupValues
    private List<?> getGroupByValues(final QueryResult queryResult, final Collection<OrderByItem> groupByItems) throws SQLException {
        List<Object> result = new ArrayList<>(groupByItems.size());
        for (OrderByItem each : groupByItems) {
            result.add(queryResult.getValue(each.getIndex(), Object.class));
        }
        return result;
    }
}

AggregationProjection代表一个select语句中的聚合字段,存储了别名,聚合类型,聚合函数里的表达式,衍生聚合字段(求avg平均值ProjectionsTokenGenerator会生成sum和count两个聚合查询),groupby字段对应select字段的下标位置。

@EqualsAndHashCode
@ToString
public class AggregationProjection implements Projection {
    // AVG
    private final AggregationType type;
    // (paid_amount)
    private final String innerExpression;
    // null
    private final String alias;
    // 衍生聚合字段 SUM(paid_amount),COUNT(*)
    private final List<AggregationProjection> derivedAggregationProjections = new ArrayList<>(2);
    // groupby字段 对应 select字段 的 下标位置
    @Setter
    private int index = -1;
}

AggregationUnit负责处理不同的聚合逻辑,由工厂统一创建。

public final class AggregationUnitFactory {
    public static AggregationUnit create(final AggregationType type, final boolean isDistinct) {
        switch (type) {
            case MAX:
                return new ComparableAggregationUnit(false);
            case MIN:
                return new ComparableAggregationUnit(true);
            case SUM:
                return isDistinct ? new DistinctSumAggregationUnit() : new AccumulationAggregationUnit();
            case COUNT:
                return isDistinct ? new DistinctCountAggregationUnit() : new AccumulationAggregationUnit();
            case AVG:
                return isDistinct ? new DistinctAverageAggregationUnit() : new AverageAggregationUnit();
            default:
                throw new UnsupportedOperationException(type.name());
        }
    }
}

比如AverageAggregationUnit处理普通的avg逻辑,merge合并方法,累加count和sum;getResult方法返回sum/count。

@RequiredArgsConstructor
public final class AverageAggregationUnit implements AggregationUnit {
    
    private BigDecimal count;
    
    private BigDecimal sum;
    
    @Override
    public void merge(final List<Comparable<?>> values) {
        if (null == values || null == values.get(0) || null == values.get(1)) {
            return;
        }
        if (null == count) {
            count = new BigDecimal("0");
        }
        if (null == sum) {
            sum = new BigDecimal("0");
        }
        count = count.add(new BigDecimal(values.get(0).toString()));
        sum = sum.add(new BigDecimal(values.get(1).toString()));
    }
    
    @Override
    public Comparable<?> getResult() {
        if (null == count || BigDecimal.ZERO.equals(count)) {
            return count;
        }
        // TODO use metadata to fetch float number precise for database field
        return sum.divide(count, 4, BigDecimal.ROUND_HALF_UP);
    }
}

4、StreamMergedResult

StreamMergedResult流式归并结果,实现除了next以外其他方法,子类需要实现next方法移动游标,设置currentQueryResult(当前QueryResult)。

@Setter
public abstract class StreamMergedResult implements MergedResult {
    // 当前结果集
    private QueryResult currentQueryResult;
    // 是否为空
    private boolean wasNull;
    
    protected final QueryResult getCurrentQueryResult() throws SQLException {
        return currentQueryResult;
    }
    @Override
    public Object getValue(final int columnIndex, final Class<?> type) throws SQLException {
        Object result = getCurrentQueryResult().getValue(columnIndex, type);
        wasNull = getCurrentQueryResult().wasNull();
        return result;
    }
    @Override
    public Object getCalendarValue(final int columnIndex, final Class<?> type, final Calendar calendar) throws SQLException {
        Object result = getCurrentQueryResult().getCalendarValue(columnIndex, type, calendar);
        wasNull = getCurrentQueryResult().wasNull();
        return result;
    }
    @Override
    public final InputStream getInputStream(final int columnIndex, final String type) throws SQLException {
        InputStream result = getCurrentQueryResult().getInputStream(columnIndex, type);
        wasNull = getCurrentQueryResult().wasNull();
        return result;
    }
    @Override
    public final boolean wasNull() {
        return wasNull;
    }
}

StreamMergedResult有三个实现类

  • IteratorStreamMergedResult:普通合并。
  • OrderByStreamMergedResult:排序合并。
  • GroupByStreamMergedResult:分组聚合合并。 其中GroupByStreamMergedResult与GroupByMemoryMergedResult类似,就不看了。

IteratorStreamMergedResult

IteratorStreamMergedResult处理普通场景的流式合并结果。比如select * from t_order,需要合并不同数据源和不同表中的数据。

public final class IteratorStreamMergedResult extends StreamMergedResult {
    
    private final Iterator<QueryResult> queryResults;
    
    public IteratorStreamMergedResult(final List<QueryResult> queryResults) {
        this.queryResults = queryResults.iterator();
        setCurrentQueryResult(this.queryResults.next());
    }
    
    @Override
    public boolean next() throws SQLException {
        if (getCurrentQueryResult().next()) {
            return true;
        }
        if (!queryResults.hasNext()) {
            return false;
        }
        setCurrentQueryResult(queryResults.next());
        boolean hasNext = getCurrentQueryResult().next();
        if (hasNext) {
            return true;
        }
        while (!hasNext && queryResults.hasNext()) {
            setCurrentQueryResult(queryResults.next());
            hasNext = getCurrentQueryResult().next();
        }
        return hasNext;
    }
}

需要注意的是QueryResult的迭代方式与java.util.Iterator的迭代方式不同,前者的next方法是移动游标并判断是否有数据;后者通过hasNext方法判断是否还有数据,next方法移动游标。

next方法,优先判断当前QueryResult是否还有数据,如果没有则通过Iterator移动到下一个QueryResult。

OrderByStreamMergedResult

OrderByStreamMergedResult处理排序合并的流式合并结果,思想其实就是如何将局部有序合并为全局有序。下面是OrderByStreamMergedResult的三个成员变量。

public class OrderByStreamMergedResult extends StreamMergedResult {
    // SelectStatementContext中获取的orderby信息
    private final Collection<OrderByItem> orderByItems;
    // 元素是OrderByValue的优先级队列
    private final Queue<OrderByValue> orderByValuesQueue;
    // 第一次执行next方法的标志位 初始化true
    private boolean isFirstNext;
}

OrderByStreamMergedResult的构造方法初始化了orderByValuesQueue优先级队列。

public OrderByStreamMergedResult(final List<QueryResult> queryResults, final SelectStatementContext selectStatementContext, final SchemaMetaData schemaMetaData) throws SQLException {
  this.orderByItems = selectStatementContext.getOrderByContext().getItems();
  this.orderByValuesQueue = new PriorityQueue<>(queryResults.size());
  orderResultSetsToQueue(queryResults, selectStatementContext, schemaMetaData);
  isFirstNext = true;
}

private void orderResultSetsToQueue(final List<QueryResult> queryResults, final SelectStatementContext selectStatementContext, final SchemaMetaData schemaMetaData) throws SQLException {
  for (QueryResult each : queryResults) {
      // OrderByValue持有QueryResult,可以移动QueryResult游标
      OrderByValue orderByValue = new OrderByValue(each, orderByItems, selectStatementContext, schemaMetaData);
      // 移动QueryResult游标并判断是否有数据
      if (orderByValue.next()) {
          // 放入优先队列
          orderByValuesQueue.offer(orderByValue);
      }
  }
  // 如果没有一个QueryResult有数据,会导致优先队列为空,直接返回第一个QueryResult,否则返回优先级队列中的第一个QueryResult
  setCurrentQueryResult(orderByValuesQueue.isEmpty() ? queryResults.get(0) : orderByValuesQueue.peek().getQueryResult());
}

看一下OrderByValue是什么,与之前的GroupByValue类似,只不过GroupByValue没有操作游标,而OrderByValue持有QueryResult(ResultSet)。

public final class OrderByValue implements Comparable<OrderByValue> {
    // 持有的QueryResult
    @Getter
    private final QueryResult queryResult;
    // SelectStatementContext中获取的orderby信息
    private final Collection<OrderByItem> orderByItems;
    // 如果是字符串 每个orderby字段是否大小写敏感(用于排序比较)
    private final List<Boolean> orderValuesCaseSensitive;
    // 和GroupByValue#groupValues一个意思,代表当前行orderby字段列表对应的select字段值列表
    private List<Comparable<?>> orderValues;
}

重点看一下OrderByValue两个方法,第一个是next方法,移动QueryResult游标,更新当前orderValues。

public boolean next() throws SQLException {
  // 移动QueryResult游标并判断是否有数据
  boolean result = queryResult.next();
  // 如果有数据,更新获取当前行orderby字段列表对应的值列表orderValues,否则更新为空集合
  orderValues = result ? getOrderValues() : Collections.emptyList();
  // 返回QueryResult是否有数据
  return result;
}

第二个是compareTo方法,用于优先级队列排序,循环每个orderby字段,按照排序规则、字符串大小写是否敏感,比较orderby字段值大小。

@Override
public int compareTo(final OrderByValue o) {
    int i = 0;
    for (OrderByItem each : orderByItems) {
        int result = CompareUtil.compareTo(orderValues.get(i), o.orderValues.get(i), each.getSegment().getOrderDirection(),
            each.getSegment().getNullOrderDirection(), orderValuesCaseSensitive.get(i));
        if (0 != result) {
            return result;
        }
        i++;
    }
    return 0;
}

回到OrderByStreamMergedResult,经过构造方法之后orderByValuesQueue优先级队列已经构造好了,队列的头部是全局排序最靠前的一行数据对应的QueryResult对应的OrderByValue。接下来看一下OrderByStreamMergedResult的next方法,逻辑都写在注释里了。

// 元素是OrderByValue的优先级队列
private final Queue<OrderByValue> orderByValuesQueue;
// 第一次执行next方法的标志位 初始化true
private boolean isFirstNext;
@Override
public boolean next() throws SQLException {
  // 如果队列为空,返回false
  if (orderByValuesQueue.isEmpty()) {
      return false;
  }
  // 如果第一次执行next方法,返回true
  if (isFirstNext) {
      isFirstNext = false;
      return true;
  }
  // 获取队列第一个OrderByValue
  OrderByValue firstOrderByValue = orderByValuesQueue.poll();
  // 判断是否还有数据 移动QueryResult指针
  if (firstOrderByValue.next()) {
      // 如果有则重新把OrderByValue放入优先队列
      orderByValuesQueue.offer(firstOrderByValue);
  }
  // 队列为空返回false
  if (orderByValuesQueue.isEmpty()) {
      return false;
  }
  // 设置当前QueryResult为队列第一个OrderByValue持有的QueryResult 并返回true
  setCurrentQueryResult(orderByValuesQueue.peek().getQueryResult());
  return true;
}

下面举个例子select * from t_order order by user_id asc。假如按user_id%2分片,得到两个结果集,第一个结果集有两条数据user_id=1和user_id=3,第二个结果集有两条数据user_id=2和user_id=4。 构造方法会将QueryResult封装为OrderValue放入优先级队列,队列的头部是全局排序最靠前的一行数据(user_id=1)对应的QueryResult对应的OrderByValue。 第一次执行OrderByStreamMergedResult的next方法,OrderByValue1被弹出后重新放入优先级队列导致排序排在OrderByValue2之后。设置当前QueryResult为OrderByValue1对应的QueryResult1,得到当前QueryResult指向user_id=1的那一行数据。 第二次执行OrderByStreamMergedResult的next方法,OrderByValue2被弹出后重新放入优先级队列导致排序排在OrderByValue1之后。设置当前QueryResult为OrderByValue2对应的QueryResult2,得到当前QueryResult指向user_id=2的那一行数据。 第三次执行OrderByStreamMergedResult的next方法,OrderByValue1被弹出后它对应的QueryResult1不再有数据,但是OrderByValue1还会放入优先级队列(因为QueryResult的next方法是先判断有数据再移动指针)。设置当前QueryResult为OrderByValue1对应的QueryResult1,得到当前QueryResult指向user_id=3的那一行数据。 第四次执行OrderByStreamMergedResult的next方法,弹出OrderByValue1,但由于没有数据不会被放回队列。设置当前QueryResult为队列头部(peek)的OrderByValue2对应的QueryResult2,得到当前QueryResult指向user_id=4的那一行数据。 最后一次执行OrderByStreamMergedResult的next方法,弹出OrderByValue2,但由于没有数据不会被放回队列。最终判断优先级队列为空,返回false。

总结

1、流式归并?内存归并?

  • 如果执行statement.executeQuery获取ResultSet,通过ConnectionMode选择。
    • 如果maxConnectionsSizePerQuery(单次查询最大连接数)< sql数量,使用CONNECTION_STRICTLY连接限制。采用内存归并MemoryQueryResult,一次性读取ResultSet数据到内存,减少数据库连接开销。
    • 如果maxConnectionsSizePerQuery(单次查询最大连接数)>= sql数量,使用MEMORY_STRICTLY内存限制。采用流式归并StreamQueryResult,ResultSet移动游标读取数据到内存,减少内存开销。
  • 如果执行statement.getResultSet获取ResultSet,会选择使用流式归并StreamQueryResult。

2、QueryResult经过MergeEngine处理,得到归并结果MergedResult。

3、MergedResult分类

  • DecoratorMergedResult:类似笛卡尔积路由引擎ShardingCartesianRoutingEngine。它只会在有合并结果之后被构造使用,用于装饰某个MergedResult。有三个实现类,都与分页有关。
  • TransparentMergedResult:适配器模式,把所有的MergedResult需要实现的方法都直接委托QueryResult实现。
  • MemoryMergedResult:内存归并,将数据完全读入内存集合,提供给外部元素迭代的方法。子类GroupByMemoryMergedResult处理groupby分组聚合逻辑。
  • StreamMergedResult:流式归并,移动游标迭代元素。有三个实现类:
    • IteratorStreamMergedResult:普通合并。
    • OrderByStreamMergedResult:排序合并。
    • GroupByStreamMergedResult:分组聚合合并。