源码地址:https://github.com/windhan2100/graphql
一、使用例子
1.联合类型
Union可以当成枚举的类型,常用于错误处理
《1.》建立一张表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` smallint(6) DEFAULT NULL,
`balance` bigint(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`pwd` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
《2.》添加schema
在schema.graphqls中加入下面内容
######## user 相关 ############
type User {
id: Long!
name: String!
age: Int
email: String!
pwd: String!
}
input AuthData {
pwd: String!
email: String!
}
union CreateUserResult = CreatedUser | ErrorContainer
type CreatedUser {
user: User!
}
type ErrorContainer {
messages:[String!]!
}
在root.graphqls中Mutation中添加
#### user相关 ####
createUser(name: String!, authData:AuthData!): CreateUserResult!
《3.》添加类
User.java
package com.hanchao.graphql.graphql.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* @author hanliwei
* @create 2019-03-02 11:03
*/
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(columnDefinition = "bigint", nullable = false)
private Long id;
private String name;
private Integer age;
private String email;
private String pwd;
}
AuthData.java ,Input类型
package com.hanchao.graphql.graphql.model;
import lombok.Data;
/**
* AuthData.java
*
* @author hanliwei
* @create 2019-03-02 11:16
*/
@Data
public class AuthData {
private String pwd;
private String email;
}
CreateUserResult.java 接口类型
package com.hanchao.graphql.graphql.model;
/**
* union CreateUserResult
*
* @author hanliwei
* @create 2019-03-02 11:13
*/
public interface CreateUserResult {
}
CreatedUser.java 实现了CreateUserReSult接口
package com.hanchao.graphql.graphql.model;
import com.hanchao.graphql.graphql.entity.User;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* CreatedUser.java
*
* @author hanliwei
* @create 2019-03-02 11:14
*/
@Data
@AllArgsConstructor
public class CreatedUser implements CreateUserResult {
private User user;
}
ErrorContainer.java 实现了CreateUserResult接口
package com.hanchao.graphql.graphql.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* ErrorContainer.java
*
* @author hanliwei
* @create 2019-03-02 11:15
*/
@Data
@AllArgsConstructor
public class ErrorContainer implements CreateUserResult {
private List<String> messages;
}
UserRepo.java : JPA接口
package com.hanchao.graphql.graphql.repo;
import com.hanchao.graphql.graphql.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author hanliwei
* @create 2019-03-02 11:18
*/
public interface UserRepo extends JpaRepository<User,Long> {
/**
* @descript: 通过用户名查找用户
* @auther: hanliwei
* @date: 2019/3/2 11:29
* @param name 用户名
* @return User
*/
User findUserByName(String name);
}
Mutation.java中添加root.graphqls中新加的方法定义
public CreateUserResult createUser(String name, AuthData authData) {
if (userRepo.findUserByName(name) != null) {
return new ErrorContainer(Stream.of("The user already exists.").collect(Collectors.toList()));
} else {
User user = new User();
user.setPwd(authData.getPwd());
user.setEmail(authData.getEmail());
user.setName(name);
return new CreatedUser(userRepo.save(user));
}
}
Application.java中添加:不然会抛出下面的异常:
Object type 'CreatedUser' is a member of a known union, but no class could be found for that type name.
Please pass a class for type 'CreatedUser' in the parser's dictionary.
package com.hanchao.graphql.graphql;
import com.coxautodev.graphql.tools.SchemaParserDictionary;
import com.hanchao.graphql.graphql.model.CreatedUser;
import com.hanchao.graphql.graphql.model.ErrorContainer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GraphqlApplication {
public static void main(String[] args) {
SpringApplication.run(GraphqlApplication.class, args);
}
@Bean
SchemaParserDictionary schemaParserDictionary() {
return new SchemaParserDictionary()
.add(CreatedUser.class)
.add(ErrorContainer.class);
}
}
《4.》测试一下
第一次请求:
第二次请求:
2.分页
分页我们可以自己定义,也可以使用graphQL提供的Relay(中继) ,下面我们以graphQL的Relay为例子:
参考:https://www.graphql-java-kickstart.com/tools/relay/
《1.》更新Pom依赖
之前的版本有点低,我们需要5.4.0以上
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.4.1</version>
</dependency>
《2.》修改schema
在root.graphqls中的Query中添加方法
#分页#
books(first: Int, after: String): BookConnection @connection(for: "Book")
在schema.graphqls中添加如下:
#### 分页 ####
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
}
type BookEdge {
cursor: String
node: Book
}
type BookConnection {
edges: [BookEdge]
pageInfo: PageInfo
}
《3.》Query.java中添加对应的方法
/**
* 分页测试
* @param first
* @param after
* @param env
* @return
*/
public Connection<Book> books(int first,String after,DataFetchingEnvironment env) {
return new SimpleListConnection<>(bookRepo.findAll()).get(env);
}
《4.》测试一下
3.接口|内联|外联
跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口。
例如:我们用Animal接口表示所有的动物接口,所有的动物都会有一个名字,但是,都会有不同的其他的属性,比如:鱼有尾部,狗有四条腿,鸟儿有翅膀
《1.》添加schema
1.在schema.graphqls中添加
### 接口演示 ####
interface Animal {
name: String!
}
type Fish implements Animal {
name: String!
tailColor: String!
}
type Dog implements Animal {
name: String!
legs: Int!
}
2.在root.graphqls中添加
### 测试接口 ####
getAnimal(name:String!): Animal!
animals: [Animal]
《2.》实体类
Animal.java
package com.hanchao.graphql.graphql.model;
/**
* 为演示GraphQL interface创建的接口
*
* @author hanliwei
* @create 2019-02-20 17:26
*/
public interface Animal {
String getName();
}
Dog.java
package com.hanchao.graphql.graphql.model;
import lombok.Data;
/**
* 为演示GraphQL interface创建的实现实体
*
* @author hanliwei
* @create 2019-02-20 17:27
*/
@Data
public class Dog implements Animal {
private String name;
private int legs;
}
Fish.java
package com.hanchao.graphql.graphql.model;
import lombok.Data;
/**
* 为演示GraphQL interface创建的实现实体
*
* @author hanliwei
* @create 2019-02-20 17:29
*/
@Data
public class Fish implements Animal {
private String tailColor;
private String name;
}
《3.》查询方法
/**
* @descript: 测试一下接口的使用
* @auther: hanliwei
* @date: 2019/3/3 18:31
* @param name
* @return
*/
public Animal getAnimal(String name) {
Dog dog = new Dog();
dog.setName(name);
dog.setLegs(4);
Fish fish = new Fish();
fish.setName(name);
fish.setTailColor("BlueAndRead");
if ("dog".equals(name)) {
return dog;
} else if ("fish".equals(name)) {
return fish;
}
return null;
}
/**
* @descript: 返回不同类型的数据
* @auther: hanliwei
* @date: 2019/3/3 20:42
* @return
*/
public List<Animal> animals() {
Dog dog = new Dog();
dog.setName("I am Dog");
dog.setLegs(4);
Fish fish = new Fish();
fish.setName("I am Fish");
fish.setTailColor("BlueAndRead");
List<Animal> list = new ArrayList<>();
list.add(dog);
list.add(fish);
return list;
}
《4.》添加接口类型
这一步很重要,否则会抛异常,具体异常见上面的例子!
package com.hanchao.graphql.graphql;
import com.coxautodev.graphql.tools.SchemaParserDictionary;
import com.hanchao.graphql.graphql.model.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GraphqlApplication {
public static void main(String[] args) {
SpringApplication.run(GraphqlApplication.class, args);
}
@Bean
SchemaParserDictionary schemaParserDictionary() {
return new SchemaParserDictionary()
.add(CreatedUser.class)
.add(ErrorContainer.class)
.add(LoginPayload.class)
.add(Dog.class) //Dog
.add(Fish.class); // Fish
}
}
《5.》测试一下
如果查询的字段返回的是接口或联合类型,那么可能需要返回内联片段来取出下层具体类型的数据:
下面的查询中,因为getAnimal返回的是Animal类型,取决于name参数,其可能是Fish或Dog。
在直接选择的情况下,只能请求Animal上存在的字段,比如:name
如果要请求具体类型上的字段,你需要使用一个类型条件内联片段。
因为第一个片段标注为 ... on Fish ,tailColor仅在getAnimal返回的Animal为Fish类型才执行。
同理适用于Dog类型的legs字段。
如果是Dog则查询legs字段,如果是Fish则查询tailColor字段,这种内联如果单个查询则比较方便,但多个查询则使用外联更简洁,如下:
4.字段加参数
《1.》schema
我们给前面的例子中的Book,加入一个字段money ,参数为枚举,不能为空,传入CHINA时,返回:¥:xxx ; 传入USA,返回:$:xxx 。
但是,Book实体类并没有该属性,可见,schema的对象类型,和实际的实体类型,并没有直接关系,属性值也不是一一对应的!!
GraphQL每个字段上都可以有零个或多个参数,所有的参数都是有名字和类型约束的,参数可能是必选或可选的,当一个参数是可选的,我们可以定义一个默认值!
《2.》自定义解析器
《3.》测试一下
注意事项:double和float不能进行精确计算,所以,上面的值为121.1999999999 !!
如果进行精确计算,参考:https://blog.51cto.com/hanchaohan/1323228
5.自定义标量类型
大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date
类型:
scalar Date
然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date
类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。
《1.》Schema
《2.》实现自定义标量类型
package com.hanchao.graphql.graphql.resolver;
import graphql.language.IntValue;
import graphql.schema.Coercing;
import graphql.schema.GraphQLScalarType;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 自定义标量类型-测试
*
* @author hanliwei
* @create 2019-03-08 22:34
*/
@Component
public class DateScalar extends GraphQLScalarType {
public DateScalar() {
super("Date", "Built-in Date as timestamp", new Coercing() {
@Override
public Long serialize(Object input) {
if (input instanceof java.util.Date) {
return ((java.util.Date) input).getTime();
}
return null;
}
@Override
public java.util.Date parseValue(Object input) {
if (input instanceof Long) {
return new java.util.Date((Long) input);
}
return null;
}
@Override
public java.util.Date parseLiteral(Object input) {
if (input instanceof IntValue) {
return new Date(((IntValue) input).getValue().longValue());
}
return null;
}
});
}
}
《3.》测试一下
可以看到不同了吗?!Book中时间使用的是自定义类型Date,而Author中是在解析器中重新定义了getCreatedTime方法
自定义标量类型参考:
https://www.graphql-java.com/documentation/v10/scalars/
https://www.apollographql.com/docs/graphql-tools/scalars.html