Spring for GraphQL中@SchemaMapping如何访问父级资源的父级数据
我无法找到如何结合嵌套数据结构中上层属性与深层嵌套属性来加载关联资源。现有如下实体类(省略getter/setter):
class Resource { int tenantId; List<SubResource> subResources; } class SubResource { String name; int userId; } class Tenant { int id; String name; } class User { int id; String name; }
对应的GraphQL Schema:
type Resource { tenantName: String subResources: [SubResource] } type SubResource { userName: String }
我已在ResourceController中通过如下方法解析Resource.tenantName属性:
@SchemaMapping public CompletableFuture<String> tenantName(Resource resource, DataLoader<Integer, Tenant> loader) { return loader.load(resource.getTenantId()).thenApply(Tenant::getName); }
但SubResource.userName的解析遇到问题:查询User需要同时使用tenantId和userId,而tenantId来自上层的Resource,我无法在SubResource的@SchemaMapping方法中获取该值。目前我的代码如下:
@SchemaMapping public CompletableFuture<String> userName(SubResource subResource, DataLoader<TenantAndUserId, User> loader) { // 如何获取tenantId? int tenantId = ???; // TenantAndUserId用于存储tenantId和userId var key = new TenantAndUserId(tenantId, subResource.getUserId()); return loader.load(key).thenApply(User::getName); }
请问该如何解决此问题?
方法1:通过DataFetchingEnvironment获取父节点数据
Spring GraphQL提供的DataFetchingEnvironment可以访问当前字段的父节点信息,直接从中提取上层Resource的tenantId即可:
@SchemaMapping public CompletableFuture<String> userName(SubResource subResource, DataLoader<TenantAndUserId, User> loader, DataFetchingEnvironment env) { // 从父节点Resource中获取tenantId Resource parentResource = env.getSource(); int tenantId = parentResource.getTenantId(); var key = new TenantAndUserId(tenantId, subResource.getUserId()); return loader.load(key).thenApply(User::getName); }
注意:如果subResources存在多层嵌套,可能需要用env.getParent()逐层向上查找,但这里因为SubResource是Resource的直接子节点,所以getSource()可以直接拿到父级Resource对象。
方法2:扩展SubResource类,携带tenantId
如果不想依赖GraphQL上下文,可在组装SubResource列表时,直接将上层Resource的tenantId注入到每个SubResource实例中:
// 修改SubResource类,新增tenantId字段 class SubResource { String name; int userId; int tenantId; } // 在获取Resource数据时,给每个SubResource设置tenantId Resource resource = getResourceFromDB(); resource.getSubResources().forEach(sub -> sub.setTenantId(resource.getTenantId()));
之后在SchemaMapping方法中直接使用该字段:
@SchemaMapping public CompletableFuture<String> userName(SubResource subResource, DataLoader<TenantAndUserId, User> loader) { int tenantId = subResource.getTenantId(); var key = new TenantAndUserId(tenantId, subResource.getUserId()); return loader.load(key).thenApply(User::getName); }
这种方法更直观,不依赖GraphQL上下文,适合需要在多个业务场景中使用tenantId的情况。
方法3:使用@ContextValue传递租户信息
如果tenantId属于全局上下文数据(比如从请求头、认证信息中获取),可以通过@ContextValue直接注入到方法中:
@SchemaMapping public CompletableFuture<String> userName(SubResource subResource, DataLoader<TenantAndUserId, User> loader, @ContextValue int tenantId) { var key = new TenantAndUserId(tenantId, subResource.getUserId()); return loader.load(key).thenApply(User::getName); }
前提是你已经通过全局配置(比如GraphQlSourceBuilderCustomizer或拦截器)将tenantId放入了GraphQL上下文。
内容的提问来源于stack exchange,提问作者Claus Nielsen

