You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring for GraphQL:不修改Schema如何避免数据过度/不足获取?

问题描述

我正在使用Spring for GraphQL,希望让GraphQL解析器避免数据过度获取与不足获取,但不想修改Schema以匹配数据库的数据分组方式。我了解过N+1问题,但本次场景有所不同。

现有GraphQL Schema如下:

type Query {
    employee(name: String, companyName: String): Employee
}

type Employee {
    name: String
    companyName: String
    age: Int # 来源:Person表
    address: String # 来源:Person表
    jobTitle: String # 来源:Employee表
    startDate: String # 来源:Employee表
}

请问是否可以配置解析器,让age和address始终一起获取,jobTitle和startDate始终一起获取?

修改Schema将这些字段分组会导致与数据库紧耦合,比如:

type Employee {
    name: String
    companyName: String
    personalDetails : PersonalDetails # 来源:Person表
    jobDetails: JobDetails # 来源:Employee表
}

type PersonalDetails {
    age: Int
    address: String
}

type JobDetails {
    jobTitle: String
    startDate: String
}

目前示例代码使用四个解析器填充Employee数据,我希望仅使用两个:

@Controller
public class GraphqlController {
    record Employee(String name, String companyName, Integer age, String address, String jobTitle, String startDate) {
    }

    @QueryMapping
    public Employee employee(@Argument String name, @Argument String companyName) {
        return new Employee(name, companyName, null, null, null, null);
    }

    @SchemaMapping
    public Integer age(Employee employee) {
        return 35;
    }

    @SchemaMapping
    public String address(Employee employee) {
        return "US";
    }

    @SchemaMapping
    public String jobTitle(Employee employee) {
        return "Software";
    }

    @SchemaMapping
    public String startDate(Employee employee) {
        return "Monday";
    }
}

解决方案

完全可以实现你的需求,不需要修改现有Schema,只需要通过Spring GraphQL的解析器关联机制,将同属一个数据库表的字段绑定到同一个数据获取逻辑中。具体做法如下:

核心思路

创建两个辅助数据类(对应数据库的两个表分组),编写两个核心解析器方法分别获取这两组数据,再将原Schema中的单个字段映射到对应辅助类的属性上。这样无论客户端请求age还是address,都会触发一次完整的Person表查询;请求jobTitle或startDate时,触发一次完整的Employee表查询,避免重复查询或数据拆分获取。

代码实现

@Controller
public class GraphqlController {
    // 仅保留Schema顶层返回的核心字段,无需包含后续要填充的关联字段
    record Employee(String name, String companyName) {}
    
    // 对应Person表的分组数据,仅作为后端数据载体
    record PersonalDetails(Integer age, String address) {}
    // 对应Employee表的分组数据,仅作为后端数据载体
    record JobDetails(String jobTitle, String startDate) {}

    @QueryMapping
    public Employee employee(@Argument String name, @Argument String companyName) {
        // 仅返回基础字段,关联字段交给后续解析器处理
        return new Employee(name, companyName);
    }

    // 一次性获取Person表的age和address数据
    @SchemaMapping
    public PersonalDetails personalDetails(Employee employee) {
        // 此处替换为实际数据库查询逻辑,一次查询获取两个字段
        return new PersonalDetails(35, "US");
    }

    // 将age字段映射到PersonalDetails的age属性
    @SchemaMapping(field = "age")
    public Integer age(PersonalDetails personalDetails) {
        return personalDetails.age();
    }

    // 将address字段映射到PersonalDetails的address属性
    @SchemaMapping(field = "address")
    public String address(PersonalDetails personalDetails) {
        return personalDetails.address();
    }

    // 一次性获取Employee表的jobTitle和startDate数据
    @SchemaMapping
    public JobDetails jobDetails(Employee employee) {
        // 此处替换为实际数据库查询逻辑,一次查询获取两个字段
        return new JobDetails("Software", "Monday");
    }

    // 将jobTitle字段映射到JobDetails的jobTitle属性
    @SchemaMapping(field = "jobTitle")
    public String jobTitle(JobDetails jobDetails) {
        return jobDetails.jobTitle();
    }

    // 将startDate字段映射到JobDetails的startDate属性
    @SchemaMapping(field = "startDate")
    public String startDate(JobDetails jobDetails) {
        return jobDetails.startDate();
    }
}

原理说明

  1. Spring GraphQL会根据字段依赖关系自动解析:当客户端请求age或address时,会先调用personalDetails方法获取完整分组数据,再提取对应字段值,确保只执行一次数据库查询。
  2. 辅助类仅作为后端数据分组的载体,不会暴露到Schema中,完全避免了Schema与数据库结构的耦合。
  3. 最终仅用两个核心数据获取逻辑(personalDetails和jobDetails)处理所有关联字段,满足你的需求。

内容的提问来源于stack exchange,提问作者neets

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 15:10:33