zoukankan      html  css  js  c++  java
  • 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/

  • 相关阅读:
    bzoj-2748 2748: [HAOI2012]音量调节(dp)
    bzoj-2338 2338: [HNOI2011]数矩形(计算几何)
    bzoj-3444 3444: 最后的晚餐(组合数学)
    codeforces 709E E. Centroids(树形dp)
    codeforces 709D D. Recover the String(构造)
    codeforces 709C C. Letters Cyclic Shift(贪心)
    codeforces 709B B. Checkpoints(水题)
    codeforces 709A A. Juicer(水题)
    Repeat Number
    hdu 1003 Max Sum (动态规划)
  • 原文地址:https://www.cnblogs.com/coderxiaohei/p/14204840.html
Copyright © 2011-2022 走看看