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/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容