概述
是时候向我们的应用程序中引入数据加载器了。
在本课程中,我们将
- 实现并注册我们的数据加载器
添加数据加载器
我们在ListingService
类上引入了一种新方法,负责为 多个 房源信息抓取便利设施数据。但是,仅将我们的 Listing.amenities
数据提取器更新为使用这种方法还远远不够。
假设我们执行了类似以下操作,将 amenitiesRequest
的调用替换为 multipleAmenitiesRequest
。
@DgsData(parentType = "Listing")public List<Amenity> amenities(DgsDataFetchingEnvironment dfe) throws IOException {ListingModel listing = dfe.getSource();String id = listing.getId();Map<String, Boolean> localContext = dfe.getLocalContext();if (localContext.get("hasAmenityData")) {return listing.getAmenities();}- return listingService.amenitiesRequest(id);+ return listingService.multipleAmenitiesRequest(id);}
虽然此更改将利用我们的新端点,但它不足以实际提高我们的 查询 性能。 Listing.amenities
数据提取器(为我们请求便利设施的每个房源信息调用)仍然会触发 针对 每个房源信息的单个网络请求。换句话说,我们仍然缺少一个可以在数据提取器执行 之间 为我们需要提取数据的键批量设置的逻辑。
REQUEST FOR LISTING-1 AMENITIES:GET /amenities/listings?ids=listing-1REQUEST FOR LISTING-2 AMENITIES:GET /amenities/listings?ids=listing-2...etc.
实际上,我们所做的将与之前相同 — 为每个列表调用一个 REST 端点。这不会提升性能!
我们的数据加载器类就在这里派上用场了。
循序渐进的加载器
下面是所有内容协同运行的方式。
- 我们创建一个数据加载器类,用 DGS
@DgsDataLoader
注释来表示它。 - 我们更新我们的类,以便它实现
BatchLoader
接口。 - 我们给我们的类一个
load
方法。此方法负责收集需要获取数据的所有必要键(那些将是 查询中每个列表的 ID)。 - 我们更新
Listing.amenities
字段的数据提取方法。它不会直接调用我们的ListingService
方法,而是将责任委派给数据加载器,以收集数据的便利设施并为我们 查询中的 全部 列表提供便利设施数据。而且是一次全部提供!
我们将在本课中完成前两个步骤,然后在下一课中完成所有内容。让我们开始吧!
步骤 1 - 创建数据加载器类
我们通过进入我们的代码并创建一个新目录来开始 dataloaders
,与 datafetchers
、 datasources
和 models
并列存在。
📂 com.example.listings┣ 📂 datafetchers┣ 📂 dataloaders┣ 📂 datasources┣ 📂 models┣ 📄 ListingApplication┗ 📄 WebConfiguration
在 dataloaders
目录中,创建一个名为 AmenityDataLoader
的类。
package com.example.listings.dataloaders;public class AmenityDataLoader {// TODO}
添加 @DgsDataLoader
注释
为了在我们的应用程序中注册为数据加载器,我们将引入一些新导入。
import com.example.listings.datasources.ListingService;import com.example.listings.generated.types.Amenity;import com.netflix.graphql.dgs.DgsDataLoader;import org.dataloader.BatchLoader;import org.springframework.beans.factory.annotation.Autowired;import java.io.IOException;import java.util.List;import java.util.concurrent.CompletableFuture;import java.util.concurrent.CompletionStage;
接下来,我们将使用 @DgsDataLoader
注释将我们的类标记为正式的数据加载器。我们将为它提供 name
属性,我们可以用该属性在其他地方标识此数据加载器。
@DgsDataLoader(name = "amenities")public class AmenityDataLoader {// TODO}
BatchLoader
接口
为了充当真正的 data loader ,我们的类需要实现一个处理批量加载的接口。在此课程中,我们将使用 dataloader
库中的 BatchLoader
。 BatchLoader
是一个接受键列表并加载相应值实用工具。
BatchLoader
接口采用两个参数:
- 批处理加载器将收集的每个标识符的数据类型
- 批处理加载器预期为每个标识符返回的数据类型
我们继续更新我们的类以实现此接口。
对于此特定数据加载器, BatchLoader
会收集某些编号的 String
类型清单 ID;并返回 List
的 Amenity
类型(请记住,每个清单都可以有多种便利设施,这就是为什么我们为每个请求的清单获得返回 List<Amenity>
类型的原因!)
public class AmenityDataLoader implements BatchLoader<String, List<Amenity>> {// TODO}
步骤 2 - 添加 load 方法
接下来,我们将提供该类的 load
方法。此方法具有非常具体的签名。
public CompletionStage<List<SomeJavaClass>> load(List<DataTypeOfIdentifiers> listOfIdentifiers) {// logic to fetch data by identifiers}
它需要接受 List
的键,并返回 CompletionStage
类型,该类型接受 List
的批处理加载程序解析为任何类。
让我们逐个应用这些方法,看看它们如何组合在一起。
首先,我们将编写我们的 load
方法并给它一个 List<String>
参数,称为 listingIds
。
public void load(List<String> listingIds) {// TODO}
接下来,让我们更新返回类型。为了符合 BatchLoader
接口,我们的方法需要返回 CompletionStage
类型,它接受一个类型 变量。
public CompletionStage<> load(List<String> listingIds) {// TODO}
注意:我们在处理异步操作时使用 CompletionStage
接口。我们稍后将使用 CompletableFuture
,这是一个实现 CompletionStage
接口的类。
对于类型 变量,我们将传递我们期望的 ListingService
类 multipleAmenitiesRequest
方法返回的类型,即 List
的 List<Amenity>
类型(每个清单一个便利设施列表)!因此,我们可以更新我们的 load
方法的返回类型,以便 CompletionStage
接受 List<List<Amenity>>
类型 变量。
下面是它的样子。
public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {// TODO}
最后,我们需要调用我们的ListingService
类方法multipleAmenitiesRequest
,使用listingIds
参数,以实际获取我们的便利设施数据。
但我们不能只在此处调用该方法,并返回结果。我们的load
方法的签名指示它返回CompletionStage
类型。
为了满足这一点,我们将使用CompletableFuture
,这是一个实现CompletionStage
接口的类,并调用它的一个方法: supplyAsync
。此方法接受一个函数,我们可以在其中实际调用multipleAmenitiesRequest
方法,传递listingIds
。
public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {return CompletableFuture.supplyAsync(() -> listingService.multipleAmenitiesRequest(listingIds));}
此调用可能会导致抛出异常,因此我们会在try/catch
中包装我们的代码。
public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {return CompletableFuture.supplyAsync(() -> {try {return listingService.multipleAmenitiesRequest(listingIds);} catch (IOException e) {throw new RuntimeException(e);}});}
注意:确保在listingService.multipleAmenitiesRequest
的调用中返回结果,且在try
块中!
我们的AmenityDataLoader
类已接近完成。为了让我们的load
方法有效,我们需要应用@Override
注释(这将覆盖BatchLoader
界面的实现)。我们还需要为我们的数据加载器提供一个ListingService
实例,以便它能正常工作。请注意,我们使用 Spring 的@Autowired
注释以利用依赖项注入。这意味着在创建AmenityDataLoader
类的实例时ListingService
将自动注入。
@DgsDataLoader(name = "amenities")public class AmenityDataLoader implements BatchLoader<String, Amenity> {@AutowiredListingService listingService;@Overridepublic CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {return CompletableFuture.supplyAsync(() -> {try {return listingService.multipleAmenitiesRequest(listingIds);} catch (IOException e) {throw new RuntimeException(e);}});}}
这就是数据加载器完成其工作所需的一切!
练习
要点
- 要在我们的应用中注册数据加载器,我们需要完成三个步骤
- 类需要应用
@DgsDataLoader
注释,并提供名称 - 类需要实现支持批量加载的接口。在此课程中,我们使用了
BatchLoader
接口。 - 类需要一个
load
函数,该函数遵循非常具体的签名
- 类需要应用
接下来
再完成最后一步,我们的数据加载器将为我们完成剩余的繁重工作。在下节课中,我们最终将在数据获取器中应用我们的数据加载器。
分享您关于本课程的问题和评论
此课程当前处于
您需要一个 GitHub 帐户才能在下面发帖。没有帐户吗? 改为在我们的 Odyssey 论坛中发帖。