基于GraphQl-JAVA 11.0
GraphQL的分頁是基于游標(biāo)的慰于,游標(biāo)分頁的方式可以提升用戶體驗(yàn),關(guān)于游標(biāo)分頁這里不作討論瑟枫,我們先來看看基于kotlin的后端服務(wù)如何提供一個(gè)支持分頁的GraphQL接口邑滨。
實(shí)現(xiàn)GraphQL分頁
第一步:定義模型
image
接下來實(shí)現(xiàn)一個(gè)接口分頁查詢學(xué)校里的老師。
第二步:定義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
第四步:實(shí)現(xiàn)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風(fēng)格的捕发,relay風(fēng)格的分頁參數(shù)比較全:
- first:指定取游標(biāo)后的多少個(gè)數(shù)據(jù)疏旨,與after搭配使用
- after:開始游標(biāo),與first搭配使用
- last:指定取游標(biāo)前的多少個(gè)數(shù)據(jù)扎酷,與before搭配使用
- before:結(jié)束游標(biāo)檐涝,與last搭配使用
GraphQL的分頁查詢結(jié)果叫做Connection,比如上面的TeacherConnection法挨,跟我們平時(shí)用Restful一樣谁榜,分頁查詢結(jié)果需要包含是否已經(jīng)查詢見底了,于是有PageInfo凡纳。Edge表示返回列表中的一個(gè)對象窃植,由于relay是基于游標(biāo)的,relay把業(yè)務(wù)對象和游標(biāo)包裝到一起荐糜,叫做Edge巷怜「鸪可以這么理解,把Connection理解成一個(gè)面延塑,一堆的Edge就構(gòu)成了Connection巩掺。
我們打開源碼,找到 SimpleListConnection 页畦,這是GraphQL實(shí)現(xiàn)分頁的核心類胖替,也是一個(gè)DataFetcher:
public Connection<T> get(DataFetchingEnvironment environment) {
// 在初始化SimpleConnection時(shí)需要傳入用戶執(zhí)行分頁的總列表,便利列表中的元素構(gòu)造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();
// 根據(jù)“after”參數(shù)計(jì)算其實(shí)偏移量
int afterOffset = getOffsetFromCursor(environment.getArgument("after"), -1);
int begin = Math.max(afterOffset, -1) + 1;
// 根據(jù)“before”參數(shù)計(jì)算其實(shí)偏移量
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都存在時(shí)優(yōu)先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
);
}