GraphQL(七):GraphQL分页及原理分析

基于GraphQl-JAVA 11.0

GraphQL的分页是基于游标的,游标分页的方式可以提升用户体验,关于游标分页这里不作讨论,我们先来看看基于kotlin的后端服务如何提供一个支持分页的GraphQL接口。

实现GraphQL分页

第一步:定义模型

image

接下来实现一个接口分页查询学校里的老师。

第二步:定义Connection、Edge、PageInfo

type TeacherConnection{
    edges:[TeacherEdge]
    pageInfo:PageInfo
}

type TeacherEdge{
    cursor:String
    node:Teacher
}

type PageInfo{
    hasPreviousPage:Boolean!
    hasNextPage:Boolean!
}

第三步:定义分页接口

    teachers(schoolId:String,first:Int,after:String,last:Int,before:String):TeacherConnection

第四步:实现teachers的Resolver

    fun teachers(schoolId: String, first: Int, after: String?,last:Int,before:String? ,env: DataFetchingEnvironment): Connection<Teacher> {
        return SimpleListConnection(DataStore.getTeachersBySchoolIds(listOf(schoolId))).get(env)
    }

原理分析

GraphQL分页是relay风格的,relay风格的分页参数比较全:

  • first:指定取游标后的多少个数据,与after搭配使用
  • after:开始游标,与first搭配使用
  • last:指定取游标前的多少个数据,与before搭配使用
  • before:结束游标,与last搭配使用

GraphQL的分页查询结果叫做Connection,比如上面的TeacherConnection,跟我们平时用Restful一样,分页查询结果需要包含是否已经查询见底了,于是有PageInfo。Edge表示返回列表中的一个对象,由于relay是基于游标的,relay把业务对象和游标包装到一起,叫做Edge。可以这么理解,把Connection理解成一个面,一堆的Edge就构成了Connection。

我们打开源码,找到 SimpleListConnection ,这是GraphQL实现分页的核心类,也是一个DataFetcher:

public Connection<T> get(DataFetchingEnvironment environment) {

        // 在初始化SimpleConnection时需要传入用户执行分页的总列表,便利列表中的元素构造Edge集合
        List<Edge<T>> edges = buildEdges();

        if (edges.size() == 0) {
            return emptyConnection();
        }

        ConnectionCursor firstPresliceCursor = edges.get(0).getCursor();
        ConnectionCursor lastPresliceCursor = edges.get(edges.size() - 1).getCursor();

        // 根据“after”参数计算其实偏移量
        int afterOffset = getOffsetFromCursor(environment.getArgument("after"), -1);
        int begin = Math.max(afterOffset, -1) + 1;

        // 根据“before”参数计算其实偏移量
        int beforeOffset = getOffsetFromCursor(environment.getArgument("before"), edges.size());
        int end = Math.min(beforeOffset, edges.size());

        if (begin > end) begin = end;

        edges = edges.subList(begin, end);
        if (edges.size() == 0) {
            return emptyConnection();
        }

        Integer first = environment.getArgument("first");
        Integer last = environment.getArgument("last");

        // 在first和last都存在时优先first
        if (first != null) {
            if (first < 0) {
                throw new InvalidPageSizeException(format("The page size must not be negative: 'first'=%s", first));
            }
            edges = edges.subList(0, first <= edges.size() ? first : edges.size());
        }
        if (last != null) {
            if (last < 0) {
                throw new InvalidPageSizeException(format("The page size must not be negative: 'last'=%s", last));
            }
            edges = edges.subList(last > edges.size() ? 0 : edges.size() - last, edges.size());
        }

        if (edges.isEmpty()) {
            return emptyConnection();
        }

        Edge<T> firstEdge = edges.get(0);
        Edge<T> lastEdge = edges.get(edges.size() - 1);

        PageInfo pageInfo = new DefaultPageInfo(
                firstEdge.getCursor(),
                lastEdge.getCursor(),
                !firstEdge.getCursor().equals(firstPresliceCursor),
                !lastEdge.getCursor().equals(lastPresliceCursor)
        );

        return new DefaultConnection<>(
                edges,
                pageInfo
        );
    }