• Spring Boot GraphQL 实战 02_增删改查和自定义标量


    hello,大叫好,我是小黑,又和大家见面啦~

    今天我们来继续学习 Spring Boot GraphQL 实战,我们使用的框架是 https://github.com/graphql-java-kickstart/graphql-spring-boot

    项目 github 地址:https://github.com/shenjianeng/graphql-spring-boot-example

    Query(查询)

    带参数的查询

    首先,在 classpath 下创建 graphqls 文件:

    type Book{
        id:ID!
        name:String!
    }
    
    type Query{
        # 根据 id 查询 book,参数名为 id,参数类型的 ID 类型,结果返回 book
        getBookById(id:ID!):Book
    }
    

    创建一个 Spring Bean,此处需要实现 GraphQLQueryResolver 接口,并在该类中自定义一个方法来映射 graphqls 文件中的查询。

    @Data
    public class Book {
        private int id;
        private String name;
    }
    
    @Component
    public class BookGraphQLQueryResolver implements GraphQLQueryResolver {
    
        public Book getBookById(int id) {
            Book book = new Book();
            book.setId(id);
            book.setName("这边书没有书名");
            return book;
        }
    }
    

    复合字段查询

    需求:每本书都有作者,在查询书本信息时,有时需要返回作者信息。

    # 定义 Author 数据类型结构
    type Author{
        id:ID!
        name:String!
    }
    
    type Book{
        id:ID!
        name:String!
        # 增加 author 字段,数据类型为 Author
        author:Author
    }
    
    
    type Query{
        # 根据 id 查询 book,参数名为 id,参数类型的 ID 类型,结果返回 book
        getBookById(id:ID!):Book
    }
    

    再看一下此时我们的 Java Bean:

    @Data
    public class Author {
        private UUID id;
        private String name;
    }
    
    @Data
    public class Book {
        private long id;
        private String name;
    }
    

    看仔细哦,Book 类中并没有 author 字段,Book 中 author 信息将由 graphql.kickstart.tools.GraphQLResolver 来提供。

    @Slf4j
    @Component
    public class BookGraphQLResolver implements GraphQLResolver<Book> {
    
        public Author author(Book book) {
            log.info("book id :{} query author info", book.getId());
            Author author = new Author();
            author.setId(UUID.randomUUID());
            author.setName(String.format("我是[%s]的作者", book.getName()));
            return author;
        }
    }
    

    ok,让我们启动服务,访问 http://localhost:8080/graphiql

    同时查询book和author

    而当客户端不需要 author 信息时,服务端就不会执行 BookGraphQLResolver#author,真正做到了使得客户端能够准确地获得它需要的数据,而且没有任何冗余

    (ps:如果你是服务端开发,你会怎么实现呢?是给客户端提供一个接口返回 book 和 author 信息,还是给客户端提供两个不同的接口呢?)

    只查询book时

    Mutation(变更)

    在 graphqls 文件中,使用 Query 来定义查询接口,使用 Mutation 可以定义变更数据的操作。

    type Mutation{
        createBook(id:ID!,name:String!):Book
    }
    

    上述 graphqls 文件中定义了一个 createBook 的方法,参数列表为 idname ,方法返回创建的 Book 对象。

    与之对应的 Java 代码如下:

    @Component
    public class BookGraphQLMutationResolver implements GraphQLMutationResolver {
    
        public Book createBook(int id, String name) {
            Book book = new Book();
            book.setId(id);
            book.setName(name);
            return book;
        }
    }
    

    BookGraphQLMutationResolver 实现了 graphql.kickstart.tools.GraphQLMutationResolver 接口,表明当前类中的方法用来映射 graphqls 文件中的 Mutation。

    mutation

    Input Types

    当 Mutation 中请求参数特别多时,我们可以使用 Input Types 来优化代码。

    type Mutation{
        createBook(id:ID!,name:String!):Book
        create(bookInput:BookInput!):Book
    }
    
    input BookInput{
        id:ID!
        name:String!
    }
    
    

    同理,我们也需求在 BookGraphQLMutationResolver 中添加对应的方法来映射。

    @Component
    public class BookGraphQLMutationResolver implements GraphQLMutationResolver {
      	// ...省略其他代码
      
        public Book create(BookInput input) {
            Book book = new Book();
            book.setId(input.getId());
            book.setName(input.getName());
            return book;
        }
    }
    

    客户端请求代码如下:

    mutation和input

    自定义标量类型

    在 GraphQL 中自带一些默认标量类型:

    • Int:有符号 32 位整数

    • Float:有符号双精度浮点值

    • String:UTF‐8 字符序列

    • Booleantrue 或者 false

    • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化

    使用 graphql-java-extended-scalars 库

    在 Java 这个生态中,我们可以引入下面这个库来帮助我们很方便的进行扩展:

    https://github.com/graphql-java/graphql-java-extended-scalars

      <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-java-extended-scalars</artifactId>
        <version>15.0.0</version>
      </dependency>
    

    graphql-java-extended-scalars 中具体扩展了哪些标量类型,我们都可以在 graphql.scalars.ExtendedScalars 类中找到。

    (ps:一个小技巧,s 结尾的类一般都是工具类)

    ExtendedScalars

    如何使用呢?

    1. 向 Spring 容器中注册自定义标量
    2. 在 graphqls 文件中声明要使用的自定义标量
    3. 直接使用即可

    相关示例代码如下:

    @Configuration
    public class CustomScalarTypeConfig {
    
        @Bean
        public GraphQLScalarType graphQLLong() {
            return ExtendedScalars.GraphQLLong;
        }
    }
    
    scalar Long
    
    type Book{
        id:ID!
        name:String!
        # 增加 author 字段,数据类型为 Author
        author:Author
        totalPageSize:Long
    }
    

    使用 GraphQLScalarType 自定义标量类型

    我们可以参考 graphql.scalars.java.JavaPrimitives#GraphQLLong 的实现来自定标量类型。

    @Bean
    public GraphQLScalarType graphQLDate() {
        return GraphQLScalarType
                .newScalar()
                .name("Date")
                .description("Date 类型")
                .coercing(new Coercing<Date, String>() {
                    @Override
                    public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
                        return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).format((Date) dataFetcherResult);
                    }
    
                    @Override
                    public Date parseValue(Object input) throws CoercingParseValueException {
                        if (input instanceof String) {
                            try {
                                return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).parse((String) input);
                            } catch (ParseException e) {
                                throw new CoercingParseValueException(e);
                            }
                        }
                        throw new CoercingParseValueException(
                                "Expected a 'String' but was '" + Kit.typeName(input) + "'."
                        );
                    }
    
                    @Override
                    public Date parseLiteral(Object input) throws CoercingParseLiteralException {
                        if (!(input instanceof StringValue)) {
                            throw new CoercingParseLiteralException(
                                    "Expected AST type 'StringValue' but was '" + typeName(input) + "'."
                            );
                        }
                        try {
                            return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).parse(((StringValue) input).getValue());
                        } catch (ParseException e) {
                            throw new CoercingParseValueException(e);
                        }
                    }
                })
                .build();
    }
    

    DataFetcherResult

    在 Resolver 中,我们可以使用 graphql.execution.DataFetcherResult 来包装返回的结果,示例代码如下:

    @Component
    public class BookGraphQLQueryResolver implements GraphQLQueryResolver {
    
        public DataFetcherResult<Book> getBookById(int id) {
            if (id <= 0) {
                return DataFetcherResult
                        .<Book>newResult()
                        .error(new GenericGraphQLError("id 不能为负数"))
                        .build();
            }
    
            Book book = new Book();
            book.setId(id);
            book.setName("这边书没有书名");
            return DataFetcherResult
                    .<Book>newResult()
                    .data(book)
                    .build();
        }
    }
    
    

    下期预告

    下期我们将使用 graphQL 来实现分页,并介绍一些高级特性,例如:异步加载、全局异常处理等。感谢大家的关注和阅读~~

    更多学习参考资料:

    https://www.graphql-java-kickstart.com/tools/schema-definition/#resolvers-and-data-classes

    https://graphql.org/learn/schema/

  • 相关阅读:
    Linux基础-4磁盘管理
    Linux基础-3文本处理
    Linux基础-2文件及目录管理
    Linux基础-1使用命令帮助
    解决Eclipse 添加 Tomcat 中的一些问题
    非关系型数据库 -- redis
    Java 学习笔记 五 -- Jedis
    Java 学习笔记 四 -- DBUtils的使用
    Java 学习笔记 三 -- 数据库连接池 Druid
    Java 学习笔记 二 -- JDBC事务
  • 原文地址:https://www.cnblogs.com/coderxiaohei/p/14204840.html
Copyright © 2020-2023  润新知