@DgsQuery
@DgsData, @DgsQuery, @DgsMutation and @DgsSubscription Annotations
数据抽取器,用来创建一个数据取用器。用在方法上,同时该方法必须在一个@DgsComponent类中。
@DgsData注解有两个参数。
-
包含该字段的类型。
-
数据采集器所负责的字段

如果没有设置字段参数,方法名将被用作字段名。@DgsQuery、@DgsMutation和@DgsSubscription注解是在查询、突变和订阅类型上定义数据提取器的简写。下面的定义都是等价的。

子数据提取器
前面的例子假设你可以通过一次查询从数据库中加载一个Show对象的列表。用户在GraphQL查询中包含哪些字段并不重要;加载节目的成本是一样的。如果特定字段有额外的成本呢?例如,如果为一个节目加载演员需要一个额外的查询呢?如果演员字段没有被返回给用户,那么运行额外的查询来加载演员就是浪费了。
在这种情况下,最好为这个昂贵的字段创建一个单独的数据采集器。
@DgsQuery
public List<Show> shows() {
//Load shows, which doesn't include "actors"
return shows;
}
@DgsData(parentType = "Show", field = "actors")
public List<Actor> actors(DgsDataFetchingEnvironment dfe) {
Show show = dfe.getSource();
actorsService.forShow(show.getId());
return actors;
}
只有当查询中包含演员字段时,演员数据采集器才会被执行。actors数据采集器还引入了一个新概念:DgsDataFetchingEnvironment。DgsDataFetchingEnvironment允许访问上下文、查询本身、数据加载器和源对象。源对象是包含字段的对象。在这个例子中,源是表演对象,你可以用它来获取表演的标识符,以便在演员的查询中使用。
请注意,shows datafetcher返回的是一个Show的列表,而actors datafetcher获取的是一个单一节目的演员。框架为shows数据采集器返回的每个Show执行actors数据采集器。如果演员从数据库中加载,这将导致N+1的问题。为了解决N+1的问题,你可以使用数据加载器。
通过@DgsData.List进行多个@DgsData注释
从v4.6.0开始,方法可以用多个@DgsData注解来注解。有效地,你可以通过同一个方法实现来解决多个GraphQL类型字段。要做到这一点,你必须利用@DgsData.List注解,例如
@DgsData.List({
@DgsData(parentType = "Query", field = "movies"),
@DgsData(parentType = "Query", field = "shows")
})
@DgsQuery和@DgsMutation都不能在一个方法中被多次定义。请使用@DgsData代替,并明确定义parentType以匹配Query或Mutation。
Using @InputArgument
对于GraphQL查询来说,有一个或多个输入参数是非常常见的。根据GraphQL规范,一个输入参数可以是。
一个输入类型
一个标量
一个枚举
其他类型,如输出类型、联合体和接口,不允许作为输入参数。
你可以使用@InputArgument注解在数据采集器方法中获得输入参数作为方法参数。
type Query {
shows(title: String, filter: ShowFilter): [Show]
}
input ShowFilter {
director: String
genre: ShowGenre
}
enum ShowGenre {
commedy, action, horror
}
-----------------------------------------
@DgsQuery
public List<Show> shows(@InputArgument String title, @InputArgument ShowFilter filter)
@InputArgument注解将使用方法参数的名称与查询中发送的输入参数的名称相匹配。如果参数名称与方法参数名称不匹配,我们可以选择性地在@InputArgument注解中指定名称参数。
该框架将输入参数转换为Java/Kotlin类型。转换输入参数的第一步是graphql-java使用标量实现将原始字符串输入转换为标量代表的任何类型。一个GraphQL Int变成了Java中的Integer,一个格式化的日期字符串变成了LocalDateTime(取决于你使用的标量!),列表变成了java.util.ArrayList的一个实例。输入对象在graphql-java中表示为一个Map<String, Object>。
下一步是DGS框架将Map<String, Object>转换为你用于@InputArgument的Java/Kotlin类。对于Java类,框架会使用无参数构造函数创建一个新的类实例。这意味着需要一个无条件的构造函数。然后它将实例的每个字段设置为输入参数值。
对于Kotlin数据类,只有在构造函数中传入所有参数才能创建实例。这意味着,当字段在GraphQL模式中是可选的时候,你必须确保在数据类中使字段是可选的
如果你使用Codegen插件(你真的应该使用!),输入类型将完美地开箱工作。
很容易将上面描述的转换与你在Jackson等库中熟悉的JSON反序列化混淆。虽然它看起来很相似,但两者的机制完全不相关。输入参数不是JSON,而Scalar机制才是转换工作的真正核心。这也意味着Jackson在Java/Kotlin类型上的注解根本就没有被使用。
Nullability in Kotlin for Input Arguments(Kotlin中输入参数的空值性)
如果你使用Kotlin,你必须考虑一个输入类型是否为nullable。如果模式定义了一个输入参数为nullable,代码必须通过使用nullable类型来反映这一点。如果一个非nullable类型收到一个null值,Kotlin会抛出一个异常。

在列表中使用@InputArgument
一个输入参数也可以是一个列表。如果列表类型是一个输入类型,你必须在 @InputArgument 注解中明确指定该类型。
type Query {
hello(people:[Person]): String
}
public String hello(@InputArgument(collectionType = Person.class) List<Person> people)
使用@InputArgument的可选性
在模式中,输入参数通常被定义为可选的。你的数据采集器代码需要对参数进行空值检查以检查它们是否被提供。你可以将输入参数包裹在一个Optional中,而不是进行空值检查。
public List<Show> shows(@InputArgument(collectionType = ShowFilter.class) Optional<ShowFilter> filter)
当使用复杂类型时,你确实需要在collectionType参数中提供类型,与使用列表类似。如果没有提供该参数,其值将是Optional.empty()。
Codegen常量
在到目前为止的@DgsData的例子中,我们为parentType和field参数使用了字符串值。如果你使用的是代码生成,你可以使用生成的常量来代替。Codegen创建了一个DgsConstants类,为你模式中的每个类型和字段提供常量。使用它,我们可以写一个数据采集器,如下所示。
type Query {
shows: [Show]
}
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = DgsConstants.QUERY.Shows)
public List<Show> shows() {}
使用常量的好处是,你可以在编译时检测你的模式和数据采集器之间的问题。
@RequestHeader, @RequestParam and @CookieValue
有时你需要在数据采集器中评估HTTP头,或请求的其他元素。你可以通过使用@RequestHeader注解来轻松获得HTTP头的值。@RequestHeader注解与Spring WebMVC中使用的注解相同。
@DgsQuery
public String hello(@RequestHeader String host)
可以使用@RequestParam来获得请求参数。@RequestHeader和@RequestParam都支持defaultValue和required参数。如果一个@RequestHeader或@RequestParam是必需的,没有defaultValue并且没有提供,就会抛出一个DgsInvalidInputArgumentException。
为了轻松地获取cookie值,你可以使用Spring的@CookieValue注解。
@DgsQuery
public String usingCookieWithDefault(@CookieValue(defaultValue = "defaultvalue") myCookie: String) {
return myCookie
}
使用DgsRequestData来获取对请求对象的访问权
你也可以获得对代表HTTP请求本身的请求对象的访问权。它存储在DgsDataFetchingEnvironment中的DgsContext对象上。
因为Spring WebMVC和Spring Webflux使用不同的类型来表示请求,所以DgsRequestData根据你运行的环境(WebMVC/WebFlux)而不同。DgsRequestData接口只提供对请求头和扩展的访问。要获得实际的请求对象,你需要将DgsRequestData投向正确的实现。这就是DgsWebMvcRequestData或DgsReactiveRequestData。让我们在一个例子中使用它来设置一个cookie,这是通过响应对象完成的。
让我们先看一个WebMVC的例子。从DgsWebMvcRequestData中你可以得到WebRequest,它可以进一步被投递到ServletWebRequest。
@DgsQuery
@DgsMutation
public String updateCookie(@InputArgument String value, DgsDataFetchingEnvironment dfe) {
DgsWebMvcRequestData requestData = (DgsWebMvcRequestData) dfe.getDgsContext().getRequestData();
ServletWebRequest webRequest = (ServletWebRequest) requestData.getWebRequest();
javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie("mydgscookie", value);
webRequest.getResponse().addCookie(cookie);
return value;
}
DgsRequestData现在是DgsReactiveRequestData的一个实例,它可以访问ServerRequest。
@DgsMutation
public String updateCookie(@InputArgument String value, DgsDataFetchingEnvironment dfe) {
DgsReactiveRequestData requestData = (DgsReactiveRequestData) dfe.getDgsContext().getRequestData();
ServerRequest serverRequest = requestData.getServerRequest();
serverRequest.exchange().getResponse()
.addCookie(ResponseCookie.from("mydgscookie", "webfluxupdated").build());
return value;
}
使用数据获取器的上下文
上一节所述的DgsRequestData对象是数据获取上下文的一部分。DGS框架将DgsRequestData添加到数据获取上下文中。你也可以将你自己的数据添加到上下文中,以便在数据获取器中使用。在查询执行开始之前,该上下文按请求被初始化。
你可以通过创建DgsCustomContextBuilder来定制该上下文并添加你自己的数据。
@Component
public class MyContextBuilder implements DgsCustomContextBuilder<MyContext> {
@Override
public MyContext build() {
return new MyContext();
}
}
public class MyContext {
private final String customState = "Custom state!";
public String getCustomState() {
return customState;
}
}
如果你需要访问请求,例如读取HTTP头信息,你可以实现DgsCustomContextBuilderWithRequest接口来代替。
@Component
public class MyContextBuilder implements DgsCustomContextBuilderWithRequest<MyContext> {
@Override
public MyContext build(Map<String, Object> extensions, HttpHeaders headers, WebRequest webRequest) {
//e.g. you can now read headers to set up context
return new MyContext();
}
}
数据提取器现在可以通过调用getCustomContext()方法来检索上下文。
@DgsData(parentType = "Query", field = "withContext")
public String withContext(DataFetchingEnvironment dfe) {
MyContext customContext = DgsContext.getCustomContext(dfe);
return customContext.getCustomState();
}
同样地,自定义上下文也可以在DataLoader中使用。
@DgsDataLoader(name = "exampleLoaderWithContext")
public class ExampleLoaderWithContext implements BatchLoaderWithContext<String, String> {
@Override
public CompletionStage<List<String>> load(List<String> keys, BatchLoaderEnvironment environment) {
MyContext context = DgsContext.getCustomContext(environment);
return CompletableFuture.supplyAsync(() -> keys.stream().map(key -> context.getCustomState() + " " + key).collect(Collectors.toList()));
}
}