上一节已经介绍了,在上面情况下,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,然后取值返回,最终达到了排序合并。