基于GraphQl-JAVA 11.0
GraphQL的分页是基于游标的,游标分页的方式可以提升用户体验,关于游标分页这里不作讨论,我们先来看看基于kotlin的后端服务如何提供一个支持分页的GraphQL接口。
实现GraphQL分页
第一步:定义模型
接下来实现一个接口分页查询学校里的老师。
第二步:定义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
);
}