如何为Spring GraphQL查询编写Resolver并按实例名称过滤客户
Spring GraphQL Resolver 实现:按实例名称过滤客户
核心思路
要解决跨API数据组合及避免N+1调用问题,核心是提前按过滤条件串联查询:先通过实例名称找到对应Instance,再查询该实例下的所有Tenant,最后获取关联的Customer,一次性完成数据组装,避免GraphQL自动触发子字段的重复API调用。
步骤1:定义数据模型
对应GraphQL Schema,创建Java实体类(示例用Lombok简化代码):
// Instance.java @Data public class Instance { private String instanceId; private String instanceName; } // Tenant.java @Data public class Tenant { private String tenantId; private Instance instance; } // Customer.java @Data public class Customer { private String customerName; private List<Tenant> tenants; } // 输入参数类 @Data public class CustomerFilterInput { private String customerName; private TenantInput tenant; } @Data public class TenantInput { private String tenantId; private InstanceInput instance; } @Data public class InstanceInput { private String instanceName; private String instanceId; }
步骤2:封装外部API调用
模拟不同API的客户端(实际项目中可替换为RestTemplate、WebClient或Feign接口):
@Component public class InstanceApiClient { // 调用外部Instance API,根据名称查询实例 public Instance getInstanceByName(String instanceName) { Instance instance = new Instance(); instance.setInstanceId("INST1"); instance.setInstanceName(instanceName); return instance; } } @Component public class TenantApiClient { // 调用外部Tenant API,根据实例ID查询租户列表 public List<Tenant> getTenantsByInstanceId(String instanceId) { Instance instance = new Instance(); instance.setInstanceId(instanceId); instance.setInstanceName("ABC"); Tenant tenant1 = new Tenant(); tenant1.setTenantId("TEN1"); tenant1.setInstance(instance); Tenant tenant2 = new Tenant(); tenant2.setTenantId("TEN2"); tenant2.setInstance(instance); return Arrays.asList(tenant1, tenant2); } } @Component public class CustomerApiClient { // 调用外部Customer API,根据租户ID列表查询客户 public List<Customer> getCustomersByTenantIds(List<String> tenantIds) { Customer customer1 = new Customer(); customer1.setCustomerName("Customer A"); customer1.setTenants(new ArrayList<>()); Customer customer2 = new Customer(); customer2.setCustomerName("Customer B"); customer2.setTenants(new ArrayList<>()); return Arrays.asList(customer1, customer2); } }
步骤3:编写Query Resolver
实现GraphQLQueryResolver,在顶层查询中完成所有数据的过滤与组装:
@Component public class CustomerQueryResolver implements GraphQLQueryResolver { private final InstanceApiClient instanceApiClient; private final TenantApiClient tenantApiClient; private final CustomerApiClient customerApiClient; // 构造方法注入依赖 public CustomerQueryResolver(InstanceApiClient instanceApiClient, TenantApiClient tenantApiClient, CustomerApiClient customerApiClient) { this.instanceApiClient = instanceApiClient; this.tenantApiClient = tenantApiClient; this.customerApiClient = customerApiClient; } public List<Customer> customers(CustomerFilterInput filter) { // 校验过滤条件是否存在 if (filter == null || filter.getTenant() == null || filter.getTenant().getInstance() == null) { return Collections.emptyList(); } String targetInstanceName = filter.getTenant().getInstance().getInstanceName(); // 1. 查询目标实例 Instance targetInstance = instanceApiClient.getInstanceByName(targetInstanceName); if (targetInstance == null) { return Collections.emptyList(); } // 2. 查询该实例下的所有租户 List<Tenant> matchedTenants = tenantApiClient.getTenantsByInstanceId(targetInstance.getInstanceId()); if (matchedTenants.isEmpty()) { return Collections.emptyList(); } // 提取租户ID列表 List<String> tenantIds = matchedTenants.stream() .map(Tenant::getTenantId) .collect(Collectors.toList()); // 3. 查询关联的客户列表 List<Customer> matchedCustomers = customerApiClient.getCustomersByTenantIds(tenantIds); // 4. 组装客户与租户的关联关系 // 注:此处逻辑需根据实际业务调整,示例为每个客户关联所有匹配的租户 matchedCustomers.forEach(customer -> customer.setTenants(matchedTenants)); // 若需仅保留客户中属于目标实例的租户,可替换为以下逻辑: // matchedCustomers.forEach(customer -> { // List<Tenant> filteredTenants = customer.getTenants().stream() // .filter(t -> t.getInstance().getInstanceId().equals(targetInstance.getInstanceId())) // .collect(Collectors.toList()); // customer.setTenants(filteredTenants); // }); return matchedCustomers; } }
关键注意事项
- 避免N+1调用:通过串联查询提前获取所有关联数据,而非先查客户再逐个拉取租户和实例,彻底杜绝GraphQL自动触发的子字段API调用。
- 过滤逻辑灵活调整:可根据业务需求,选择返回包含目标实例租户的客户(保留所有租户),或仅返回客户中属于目标实例的租户。
- 依赖注入:通过Spring的依赖注入管理API客户端,确保代码可维护性与扩展性。
内容的提问来源于stack exchange,提问作者Ajay Sharma
相关产品推荐
相关产品推荐

