sharding-jdbc系列之order by结果归并(八)

 

上一节已经介绍了,在上面情况下,sharding-jdbc会选用上面结果合并器,今天介绍的是order by排序结果合并

public OrderByStreamResultSetMerger(final List<ResultSet> resultSets, final List<OrderItem> orderByItems) throws SQLException {
        this.orderByItems = orderByItems; // 排序字段
        this.orderByValuesQueue = new PriorityQueue<>(resultSets.size());  // 构建优先级队列
        orderResultSetsToQueue(resultSets); // 将结果集数据压入队列
        isFirstNext = true; // 设置直接取第一个结果集就好,
    }

构建了一个具有优先级的队列,然后把结果集全部放入队列中,主要逻辑在orderResultSetsToQueue这个方法里面。

orderResultSetsToQueue

 private void orderResultSetsToQueue(final List<ResultSet> resultSets) throws SQLException {
           // 遍历结果集
        for (ResultSet each : resultSets) {
              // 构建一个排序对象
            OrderByValue orderByValue = new OrderByValue(each, orderByItems);
              // 调用其next方法
            if (orderByValue.next()) {
                  // 将排序对象放入队列
                orderByValuesQueue.offer(orderByValue);
            }
        }
           // 如果orderByValuesQueue不为空,则设置当前结果集为队列顶部的元素,否则默认第一个 , 这个在next方法时,默认会直接取这个第一个结果集。
           // peek 只是返回第一个元素,并不会删除头部元素
        setCurrentResultSet(orderByValuesQueue.isEmpty() ? resultSets.get(0) : orderByValuesQueue.peek().getResultSet());
    }
@Override
public boolean next() throws SQLException {
    //1. 调用next()判断是否还有值, 如果队列为空, 表示没有任何值, 那么直接返回false
    if (orderByValuesQueue.isEmpty()) {
        return false;
    }
    //2. 如果队列不为空, 那么第一次一定返回true;即有结果可取(且将isFirstNext置为false,表示接下来的请求都不是第一次请求next()方法)
    if (isFirstNext) {
        isFirstNext = false;
        return true;
    }
    //3. 从队列中弹出第一个元素
    OrderByValue firstOrderByValue = orderByValuesQueue.poll();
    // 如果它的next()存在,那么将它的next()再添加到队列中
    if (firstOrderByValue.next()) {
        orderByValuesQueue.offer(firstOrderByValue);
    }
    //4. 队列中所有元素全部处理完后就返回false
    if (orderByValuesQueue.isEmpty()) {
        return false;
    }
    //5. 再次重置currentResultSet的位置为队列的顶部位置
    setCurrentResultSet(orderByValuesQueue.peek().getResultSet());
    return true;
}

步骤说明,按照我标的号来说:

1.当队列为空时,说明所有的结果集已经被取完了,都没有值了,直接返回false, 表示此处查询结束

2.isFirstNext这个值,在初始化OrderByStreamResultSetMerger这个对象的时候,会设置为true,也就是说第一次进来的时候会设置为true,

为啥进来判断isFirstNext=true,然后设置isFirstNext = false就返回了呢? 这是因为在构建OrderByStreamResultSetMerger调用的orderResultSetsToQueue

方法中,已经将队列中的头部元素设置为setCurrentResultSet了。因此,第一次直接返回

3.到了第三步,说明已经是第二次进来了,为啥这个时候要调用poll()方法呢,大家都知道,poll方法会移除并返回队列的头部元素。

这是因为当前队列的头部元素是第一次取的那个, 既然已经取过了,当然需要移除了,移除完之后呢,判断firstOrderByValue里面的

resultSet是否还有行记录,如果还有,那么再次进入队列,如果没有,不好意思,直接拜拜、

4.队列空了,直接返回false ,双重检查

5.将当前队列的头部元素的resultSet设置为currentResultSet

OrderByValue

    public boolean next() throws SQLException {
          // 判断当前resultSet是否还拥有记录
        boolean result = resultSet.next();
          // 取到当前游标指向的行记录 , orderValues会在 队列进行排序的时候使用。
        orderValues = result ? getOrderValues() : Collections.<Comparable<?>>emptyList();
          // 返回结果
        return result;
    }

在上面orderResultSetsToQueue的方法中,orderByValue.next()为true的时候才会放入队里了, next()方法主要作用是判断当前

OrderByValue里面的resultSet里面是否还有记录。

 /**
    * 根据排序字段,获取行记录
    */
    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;
    }

从resultSet中根据排序字段名称获取当前的行记录

    /**
    * OrderByValue 插入队列的时候,会使用这个方法来进行排序,判定在队列的位置
    */
    @Override
    public int compareTo(final OrderByValue o) {
          // 循环排序字段
        for (int i = 0; i < orderByItems.size(); i++) {
            OrderItem thisOrderBy = orderByItems.get(i);
              // 将排序的字段值用来和另外一个OrderByValue进行对比,thisOrderBy.getType()为排序类型 ACS或者DESC
            int result = ResultSetUtil.compareTo(orderValues.get(i), o.orderValues.get(i), thisOrderBy.getType(), thisOrderBy.getNullOrderType());
            if (0 != result) {
                return result;
            }
        }
        return 0;
    }

compareTo这个方法比较重要了,我们都知道orderByValuesQueue这个队列是PriorityQueue优先级队列,而OrderByValue这个对象作为队列里面的元素,

如何确定OrderByValue在队列里面的位置,就是通过compareTo方法来比较确定的。

步骤说明:

1.获取排序字段

2.将当前排序值和需要比较的值以及排序类型(ACS,DESC),使用ResultSetUtil.compareTo来进行比较。得到比较结果,确定元素的位置。

代码介绍到这里,排序代码已经讲完了,其实很多人没看懂,因为这个很绕,下面举个例子实际说明一下。

举例说明

例:

select u.* from t_user u order by u.id 

上面这条SQL经过SQL路由改写,得到如下SQL

dataSource1 ::: select u.* , u.id AS ORDER_BY_DERIVED_0 from t_user_00 u order by u.id 
dataSource2 ::: select u.* , u.id AS ORDER_BY_DERIVED_0 from t_user_00 u order by u.id 

从两个数据源里面分别取数据,假如取到的数据如下:

dataSource1 : 9,7,5,3,1
dataSource2 : 10,8,6,4,2

为了方便画图

假设dataSource1中的OrderByValue的代号为 r1

假设dataSource2中的OrderByValue的代号为 r2

mybatis获取值

为了理解的更加全面,我们回到最开始的代码

// ShardingPreparedStatement.java 
@Override
    public ResultSet getResultSet() throws SQLException {
        //省略代码N行。。。。
          // 结果归并后得到ShardingResultSet对象,
        currentResultSet = new ShardingResultSet(resultSets, new MergeEngine(resultSets, (SelectStatement) routeResult.getSqlStatement()).merge());
        return currentResultSet;
    }

//ResultSetMerger.java
public final class ShardingResultSet extends AbstractResultSetAdapter {
      // 结果合并的对象
    private final ResultSetMerger mergeResultSet;
    // 结果,
    public ShardingResultSet(final List<ResultSet> resultSets, final ResultSetMerger mergeResultSet) {
        super(resultSets);
        this.mergeResultSet = mergeResultSet;
    }
    // mybatis调用next方法判断是否取完了数据
    @Override
    public boolean next() throws SQLException {
        return mergeResultSet.next();
    }

  @Override
    public int getInt(final String columnLabel) throws SQLException {
        return (int) ResultSetUtil.convertValue(mergeResultSet.getValue(columnLabel, int.class), int.class);
    }
      // 省略代码N行
}

mybatis调用getResultSet()获取结果,因为我们这里讲的是排序,所以这个时候mergeResultSet 肯定是OrderByStreamResultSetMerger ,

所以当mybatis调用next方法判断是否取完了数据,最终会调用OrderByStreamResultSetMerger .next方法。

mybatis得知next结果为true的时候,就会调用getInt()这个方法来获取数据了。**PS: 前提是数据类型就是int **

getInt方法中,最终调用的是mergeResultSet 的getValue方法,该方法如下。

@Override
    public Object getValue(final String columnLabel, final Class<?> type) throws SQLException {
        Object result;
        if (Object.class == type) {
            result = getCurrentResultSet().getObject(columnLabel);
        } else if (boolean.class == type) {
            result = getCurrentResultSet().getBoolean(columnLabel);
        } else if (byte.class == type) {
            result = getCurrentResultSet().getByte(columnLabel);
        } else if (short.class == type) {
            result = getCurrentResultSet().getShort(columnLabel);
        } else if (int.class == type) {
            result = getCurrentResultSet().getInt(columnLabel);
        } // 省略代码N行
        wasNull = getCurrentResultSet().wasNull();
        return result;
    }

这个方法,最重要的一点是什么,就是getCurrentResultSet() ,这个方法,这个就是当我们在OrderByStreamResultSetMerger .next方法中设置的当前排序在最前面的resultSet,

经过首次初始化OrderByStreamResultSetMerger的时候,队列中的数据如下:

当从mybatis从CurrentResultSet中取了值之后,再次调用OrderByStreamResultSetMerger .next的方法,也就是第二次进来

这个时候getCurrentResultSet()对应的是取到的9那条记录。 如此反复,始终将排序最上面的那个resultSet设置为currentResultSet,然后取值返回,最终达到了排序合并。

猜你喜欢

转载自blog.csdn.net/u012394095/article/details/81558487