spring-graphql

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring for GraphQL - Quick Reference

Spring for GraphQL - 快速参考

Full Reference: See advanced.md for DataLoader configuration, custom scalars, pagination implementation, GraphQL testing patterns, and subscription controllers.
Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
spring-graphql
for comprehensive documentation.
完整参考:查看advanced.md获取DataLoader配置、自定义标量、分页实现、GraphQL测试模式以及订阅控制器相关内容。
深度知识:使用
mcp__documentation__fetch_docs
并指定technology参数为
spring-graphql
,即可获取完整文档。

Dependencies

依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Configuration

配置

yaml
spring:
  graphql:
    graphiql:
      enabled: true
      path: /graphiql
    schema:
      locations: classpath:graphql/**/
    path: /graphql
    websocket:
      path: /graphql
yaml
spring:
  graphql:
    graphiql:
      enabled: true
      path: /graphiql
    schema:
      locations: classpath:graphql/**/
    path: /graphql
    websocket:
      path: /graphql

Schema Definition

Schema定义

graphql
type Query {
    bookById(id: ID!): Book
    allBooks: [Book!]!
}

type Mutation {
    createBook(input: CreateBookInput!): Book!
}

type Book {
    id: ID!
    title: String!
    author: Author!
}

input CreateBookInput {
    title: String!
    authorId: ID!
}
graphql
type Query {
    bookById(id: ID!): Book
    allBooks: [Book!]!
}

type Mutation {
    createBook(input: CreateBookInput!): Book!
}

type Book {
    id: ID!
    title: String!
    author: Author!
}

input CreateBookInput {
    title: String!
    authorId: ID!
}

Query Controller

查询控制器

java
@Controller
public class BookController {

    @QueryMapping
    public Book bookById(@Argument String id) {
        return bookRepository.findById(id).orElse(null);
    }

    @QueryMapping
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @SchemaMapping(typeName = "Book", field = "author")
    public Author author(Book book) {
        return authorRepository.findById(book.getAuthorId()).orElse(null);
    }
}
java
@Controller
public class BookController {

    @QueryMapping
    public Book bookById(@Argument String id) {
        return bookRepository.findById(id).orElse(null);
    }

    @QueryMapping
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @SchemaMapping(typeName = "Book", field = "author")
    public Author author(Book book) {
        return authorRepository.findById(book.getAuthorId()).orElse(null);
    }
}

Mutation Controller

变更控制器

java
@Controller
public class BookMutationController {

    @MutationMapping
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}
java
@Controller
public class BookMutationController {

    @MutationMapping
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}

BatchMapping (Solve N+1)

BatchMapping(解决N+1问题)

java
@Controller
public class OptimizedBookController {

    @BatchMapping
    public Map<Book, Author> author(List<Book> books) {
        List<String> authorIds = books.stream()
            .map(Book::getAuthorId)
            .distinct()
            .toList();

        Map<String, Author> authorsById = authorRepository.findAllById(authorIds)
            .stream()
            .collect(Collectors.toMap(Author::getId, a -> a));

        return books.stream()
            .collect(Collectors.toMap(
                book -> book,
                book -> authorsById.get(book.getAuthorId())
            ));
    }
}
java
@Controller
public class OptimizedBookController {

    @BatchMapping
    public Map<Book, Author> author(List<Book> books) {
        List<String> authorIds = books.stream()
            .map(Book::getAuthorId)
            .distinct()
            .toList();

        Map<String, Author> authorsById = authorRepository.findAllById(authorIds)
            .stream()
            .collect(Collectors.toMap(Author::getId, a -> a));

        return books.stream()
            .collect(Collectors.toMap(
                book -> book,
                book -> authorsById.get(book.getAuthorId())
            ));
    }
}

Input Validation

输入校验

java
@MutationMapping
public Book createBook(@Argument @Valid CreateBookInput input) {
    return bookService.create(input);
}

public record CreateBookInput(
    @NotBlank @Size(min = 1, max = 200) String title,
    @NotNull String authorId
) {}
java
@MutationMapping
public Book createBook(@Argument @Valid CreateBookInput input) {
    return bookService.create(input);
}

public record CreateBookInput(
    @NotBlank @Size(min = 1, max = 200) String title,
    @NotNull String authorId
) {}

Error Handling

错误处理

java
@Component
public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter {

    @Override
    protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
        if (ex instanceof BookNotFoundException) {
            return GraphqlErrorBuilder.newError(env)
                .errorType(ErrorType.NOT_FOUND)
                .message(ex.getMessage())
                .build();
        }
        return null;
    }
}
java
@Component
public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter {

    @Override
    protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
        if (ex instanceof BookNotFoundException) {
            return GraphqlErrorBuilder.newError(env)
                .errorType(ErrorType.NOT_FOUND)
                .message(ex.getMessage())
                .build();
        }
        return null;
    }
}

Security

安全

java
@Controller
public class SecuredBookController {

    @QueryMapping
    @PreAuthorize("hasRole('USER')")
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @MutationMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}
java
@Controller
public class SecuredBookController {

    @QueryMapping
    @PreAuthorize("hasRole('USER')")
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @MutationMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}

When NOT to Use This Skill

何时不应使用本技能

  • REST APIs - Use standard Spring MVC controllers
  • Standalone GraphQL - Use graphql-java directly
  • Simple CRUD - May be overkill, consider REST
  • File uploads - GraphQL isn't optimized for large binary data
  • REST API - 请使用标准Spring MVC控制器
  • 独立GraphQL服务 - 直接使用graphql-java
  • 简单CRUD场景 - 可能大材小用,建议考虑REST
  • 文件上传场景 - GraphQL不适合处理大型二进制数据

Anti-Patterns

反模式

Anti-PatternProblemSolution
No @BatchMappingN+1 queries on nested fieldsUse @BatchMapping or DataLoader
Unbounded listsMemory exhaustionImplement pagination
Exposing entitiesSchema tightly coupled to DBUse DTOs/projections
No error handlingStack traces exposedCustom ExceptionResolver
GraphiQL in prodSecurity riskDisable in production
反模式问题解决方案
未使用@BatchMapping嵌套字段出现N+1查询问题使用@BatchMapping或DataLoader
无限制返回列表内存耗尽实现分页
直接暴露实体Schema与数据库强耦合使用DTO/投影
未做错误处理栈跟踪信息泄露自定义ExceptionResolver
生产环境启用GraphiQL安全风险生产环境关闭该功能

Quick Troubleshooting

快速故障排查

ProblemDiagnosticFix
N+1 queriesCheck SQL logsAdd @BatchMapping
Field not resolvedCheck method nameVerify @SchemaMapping matches schema
Subscription not workingCheck WebSocket configEnable WebSocket support
Validation not appliedCheck @ValidAdd @Validated to controller
Auth not workingCheck security configAdd @PreAuthorize annotations
问题排查方式修复方案
N+1查询检查SQL日志添加@BatchMapping
字段无法解析检查方法名验证@SchemaMapping与Schema定义匹配
订阅不工作检查WebSocket配置开启WebSocket支持
校验未生效检查@Valid注解是否添加给控制器添加@Validated注解
鉴权不生效检查安全配置添加@PreAuthorize注解

Best Practices

最佳实践

DoDon't
Use @BatchMapping for N+1Fetch nested data individually
Define clear schema contractsOver-expose internal models
Implement paginationReturn unbounded lists
Use input types for mutationsUse many scalar arguments
Add proper error handlingExpose stack traces
正确做法错误做法
针对N+1问题使用@BatchMapping单独获取嵌套数据
定义清晰的Schema契约过度暴露内部模型
实现分页返回无限制列表
变更操作使用输入类型使用多个标量参数
添加完善的错误处理暴露栈跟踪信息

Production Checklist

生产环境检查清单

  • Schema well defined
  • N+1 solved with BatchMapping
  • Input validation enabled
  • Error handling configured
  • Security annotations applied
  • Pagination implemented
  • GraphiQL disabled in prod
  • Query complexity limits
  • Introspection controlled
  • Schema定义清晰
  • 已通过BatchMapping解决N+1问题
  • 已启用输入校验
  • 已配置错误处理
  • 已应用安全注解
  • 已实现分页
  • 生产环境已关闭GraphiQL
  • 已设置查询复杂度限制
  • 内省操作已受控

Reference Documentation

参考文档