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

Spring中如何通过REST URI执行GraphQL查询?报错排查

问题

需求说明

我需要实现一个REST URI(如/books),使其仅执行与书籍相关的GraphQL查询,查询逻辑依赖?id、?name等请求参数。

遇到的错误

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Circular view path [books]: would dispatch back to the current handler URL [/books] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)] with root cause

jakarta.servlet.ServletException: Circular view path [books]: would dispatch back to the current handler URL [/books] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

请问该需求是否可以实现?若可以,我在代码中哪里出现了错误?

相关代码

控制器代码

package com.example.graphqlserver;

import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
// import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    @GetMapping("/books")
    public Book getBook(@RequestParam(name = "id", defaultValue = "") String id) {
        return this.bookById(id);
    }

    @QueryMapping
    public Book bookById(@Argument String id) {
        return Book.getById(id);
    }

    @SchemaMapping
    public Author author(Book book) {
        return Author.getById(book.authorId());
    }
}

Book类代码

package com.example.graphqlserver;

import java.util.Arrays;
import java.util.List;

public record Book(String id, String name, int pageCount, String authorId) {

    private static List<Book> books = Arrays.asList(
            new Book("book-1", "Effective Java", 416, "author-1"),
            new Book("book-2", "Hitchhiker's Guide to the Galaxy", 208, "author-2"),
            new Book("book-3", "Down Under", 436, "author-3"));

    public static Book getById(String id) {
        return books.stream()
                .filter(book -> book.id().equals(id))
                .findFirst()
                .orElse(null);
    }
}

Author类代码

package com.example.graphqlserver;

import java.util.Arrays;
import java.util.List;

public record Author(String id, String firstName, String lastName) {

    private static List<Author> authors = Arrays.asList(
            new Author("author-1", "Joshua", "Bloch"),
            new Author("author-2", "Douglas", "Adams"),
            new Author("author-3", "Bill", "Bryson"));

    public static Author getById(String id) {
        return authors.stream()
                .filter(author -> author.id().equals(id))
                .findFirst()
                .orElse(null);
    }
}

GraphQL Schema

type Query {
    bookById(id: ID): Book
}

type Book {
    id: ID
    name: String
    pageCount: Int
    author: Author
}

type Author {
    id: ID
    firstName: String
    lastName: String
}

回答

该需求完全可以实现,你代码中的问题出在**@RestController注解的返回值处理逻辑**上,具体原因及修复方案如下:

错误原因

虽然你使用了@RestController(本质是@Controller + @ResponseBody),正常情况下会将返回对象序列化为JSON,但当Book.getById()返回null时(比如传入不存在的id,或未传id时使用默认空字符串查询),Spring MVC会认为没有指定视图,自动生成与请求路径同名的视图名books,随后试图解析这个视图时,又匹配到了@GetMapping("/books")的处理器,最终导致循环调用,抛出错误。

修复方案

方案1:用ResponseEntity控制响应,处理null情况

修改getBook方法,通过ResponseEntity灵活控制HTTP状态码和响应体,避免返回null:

@GetMapping("/books")
public ResponseEntity<Book> getBook(@RequestParam(name = "id", defaultValue = "") String id) {
    Book book = this.bookById(id);
    if (book == null) {
        return ResponseEntity.notFound().build(); // 查询不到时返回404
    }
    return ResponseEntity.ok(book);
}

方案2:确保查询结果非空

修改Book.getById方法,当查询不到书籍时返回一个默认空对象(语义上不如返回404清晰,按需选择):

public static Book getById(String id) {
    return books.stream()
            .filter(book -> book.id().equals(id))
            .findFirst()
            .orElse(new Book("", "", 0, ""));
}

额外优化:支持name参数查询

根据需求扩展getBook方法,增加对name参数的处理:

@GetMapping("/books")
public ResponseEntity<List<Book>> getBooks(
        @RequestParam(name = "id", required = false) String id,
        @RequestParam(name = "name", required = false) String name) {
    List<Book> result = new ArrayList<>();
    if (id != null && !id.isEmpty()) {
        Book book = Book.getById(id);
        if (book != null) {
            result.add(book);
        }
    } else if (name != null && !name.isEmpty()) {
        result = Book.books.stream()
                .filter(book -> book.name().contains(name))
                .collect(Collectors.toList());
    } else {
        // 无参数时返回所有书籍,或根据需求返回400
        result = Book.books;
    }
    return result.isEmpty() ? ResponseEntity.notFound().build() : ResponseEntity.ok(result);
}

注意需要将Book类中的books列表改为public,或添加对应的公共查询方法。

补充说明

你代码中的@QueryMapping和@SchemaMapping是用于GraphQL接口的,与这个REST接口互不冲突:GraphQL默认通过/graphql端点访问,你的REST接口是/books,两者可以共存。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 06:53:08