跳到主要内容

GPA(GraphQL Persistence API)

定义 GraphQL 持久化接口

GraphQL Persistence API 基于 Java 接口和注解定义, 将接口方法映射为 GraphQL 查询或变更, 提供代码级的数据访问及操作能力, Graphoenix 编译器会针对不同数据库和存储方案提供不同的实现. 如果你使用过 JPA(Java Persistence API), GPA 与 Repository 有一定的相似性

生成 GPA 注解

使用 Gradle 插件生成定义 GPA 接口需要的注解和 Java Entitis, 插件会根据 GraphQL 定义生成 @Query@Mutation 注解, 注解中包含所有 GraphQL 接口定义

请注意此处的 @Query@Mutation 注解并不是 org.eclipse.microprofile.graphql 包下的 @Query@Mutation

|-- order-package                             订单包
|-- build.gradle
|-- src
|-- main
| |-- java
| |-- demo.gp.order
| |-- api
| |-- SystemApi.java 系统API
| |-- dto
| |-- repository
| | |-- annotation GPA注解
| | |-- directive 指令注解
| | |-- enumType 枚举类型
| | |-- inputObjectType Input类型
| | |-- objectType Object类型

定义 GPA 接口

  1. 新建 GPA 接口
|-- order-package                             订单包
|-- build.gradle
|-- src
|-- main
| |-- java
| |-- demo.gp.order
| |-- dto
| | |-- annotation GPA注解
| | |-- directive 指令注解
| | |-- enumType 枚举类型
| | |-- inputObjectType Input类型
| | |-- objectType Object类型
| |-- repository
| |-- UserRepository.java 用户Repository
  1. 定义接口类

使用 @GraphQLOperation 注解来定义 GPA 接口

package demo.gp.order.repository;

import io.graphoenix.spi.annotation.GraphQLOperation;

@GraphQLOperation // 使用@GraphQLOperation 注解标记接口所在 CDI Bean
public interface UserRepository {

// 定义接口
}

查询接口

使用 @Query 注解来定义查询接口, 接口会添加到查询类型中

默认使用 Mono 类型来操作返回结果

普通查询

例: 查询所有 VIP 用户

定义 queryVIPUserList 方法, 使用 @Query 注解标记接口方法, 请注意此处的 @Query 注解是在之前生成的的GPA 注解, 并非 org.eclipse.microprofile.graphql.Query

package demo.gp.order.repository;

import demo.gp.order.dto.annotation.Query;
import demo.gp.order.dto.annotation.UserListQueryArguments;
import demo.gp.order.dto.annotation.UserTypeExpression;
import demo.gp.order.dto.enumType.UserType;
import demo.gp.order.dto.objectType.User;
import io.graphoenix.core.dto.enumType.Operator;
import io.graphoenix.spi.annotation.GraphQLOperation;

import java.util.List;

@GraphQLOperation // 使用@GraphQLOperation 注解标记接口所在 CDI Bean
public interface UserRepository {

// 查询所有用户类型=VIP的User
@Query(userList = @UserListQueryArguments(userType = @UserTypeExpression(opr = Operator.EQ, val = UserType.VIP)))
Mono<List<User>> queryVIPUserList();
}

接口等同于如下 GraphQL 查询

query queryVIPUserList {
userList(userType: { opr: EQ, val: VIP }) {
id
name
email
userType
# ...
}
}

测试每一个 User 的 userType

package demo.gp.order.test;

import demo.gp.order.dto.enumType.UserType;
import demo.gp.order.dto.objectType.User;
import demo.gp.order.repository.UserRepository;
import io.nozdormu.spi.context.BeanContext;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void queryVIPUserListTest() {
List<User> userLit = userRepository.queryVIPUserList().block();
assertAll(
userLit.stream().map((item) -> () -> assertEquals(item.getUserType(), UserType.VIP))
);
}
}

查询变量

注解中以 $ 开头的参数可以指定方法中的参数作为变量, 等同于 GraphQL 中的 Variables

例: 根据用户类型变量查询用户

package demo.gp.order.repository;

import demo.gp.order.dto.annotation.Query;
import demo.gp.order.dto.annotation.UserListQueryArguments;
import demo.gp.order.dto.annotation.UserTypeExpression;
import demo.gp.order.dto.enumType.UserType;
import demo.gp.order.dto.objectType.User;
import io.graphoenix.core.dto.enumType.Operator;
import io.graphoenix.spi.annotation.GraphQLOperation;

import java.util.List;

@GraphQLOperation
public interface UserRepository {

// 查询所有用户类型=userType参数的User
@Query(userList = @UserListQueryArguments(userType = @UserTypeExpression(opr = Operator.EQ, $val = "userType")))
Mono<List<User>> queryUserListByUserType(UserType userType);
}

接口等同于如下 GraphQL 查询

query queryUserListByUserType($userType: UserType) {
userList(userType: { opr: EQ, val: $userType }) {
id
name
email
userType
# ...
}
}

测试每一个 User 的 userType

@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void queryUserListByUserTypeTest() {
List<User> userLit = userRepository.queryUserListByUserType(UserType.REGULAR).block();
assertAll(
userLit.stream().map((item) -> () -> assertEquals(item.getUserType(), UserType.REGULAR))
);
}
}

查询字段

GPA 接口在默认情况下只会查询所有 Scalar 和 Enum 字段, 可以使用 @SelectionSet 注解自定义查询字段

  1. 例: 定义用户查询, 只查询 name 字段
@GraphQLOperation
public interface UserRepository {

@Query(userList = @UserListQueryArguments(userType = @UserTypeExpression(opr = Operator.EQ, $val = "userType")))
// 查询name字段
@SelectionSet("{ name }")
Mono<List<User>> queryUserNameListByUserType(UserType userType);
}

接口等同于如下 GraphQL 查询

query queryUserNameListByUserType($userType: UserType) {
userList(userType: { opr: EQ, val: $userType }) {
name
}
}

测试每一个 User 的 返回字段, 除 name 字段外全部为 null

@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void queryUserNameListByUserTypeTest() {
List<User> userLit = userRepository.queryUserNameListByUserType(UserType.REGULAR).block();
assertAll(
userLit.stream().map((item) ->
() -> assertAll(
() -> assertNotNull(item.getName()),
() -> assertNull(item.getId()),
() -> assertNull(item.getUserType()),
() -> assertNull(item.getEmail()),
() -> assertNull(item.getPhoneNumbers())
)
)
);
}
}
  1. 例: 查询 Alice 的订单

使用 name 参数作为用户名查询变量, 查询用户的订单信息

@GraphQLOperation
public interface UserRepository {

@Query(user = @UserQueryArguments(name = @StringExpression(opr = Operator.EQ, $val = "name")))
@SelectionSet("{ name orders { items { product { name } quantity } } }")
Mono<User> queryUserOrdersListByName(String name);
}

接口等同于如下 GraphQL 查询

query queryUserOrdersListByName($name: String) {
userList(userType: { opr: EQ, val: $name }) {
name
orders {
items {
product {
name
}
quantity
}
}
}
}

测试每一个 User 的 订单信息

@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void queryUserOrdersListByNameTest() {
User user = userRepository.queryUserOrdersListByName("Alice").block();
assertAll(
() -> assertEquals(user.getOrders().size(), 1),
() -> assertEquals(new ArrayList<>(user.getOrders()).get(0).getItems().size(), 2),
() -> assertEquals(new ArrayList<>(new ArrayList<>(user.getOrders()).get(0).getItems()).get(0).getProduct().getName(), "Laptop"),
() -> assertEquals(new ArrayList<>(new ArrayList<>(user.getOrders()).get(0).getItems()).get(0).getQuantity(), 1),
() -> assertEquals(new ArrayList<>(new ArrayList<>(user.getOrders()).get(0).getItems()).get(1).getProduct().getName(), "Tablet"),
() -> assertEquals(new ArrayList<>(new ArrayList<>(user.getOrders()).get(0).getItems()).get(1).getQuantity(), 2)
);
}
}

统计查询

GPA 接口同样支持统计查询

  1. 例: 分组查询普通用户和会员用户的数量
@GraphQLOperation
public interface UserRepository {

@Query(userList = @UserListQueryArguments(groupBy = {"userType"}))
@SelectionSet("{ userType idCount }")
Mono<List<User>> queryUserCountByUserType();
}

接口等同于如下 GraphQL 查询

query queryUserCountByUserType {
userList(groupBy: ["userType"]) {
userType
idCount
}
}
@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void queryUserCountByUserTypeTest() {
List<User> userList = userRepository.queryUserCountByUserType().block();
assertAll(
() -> assertEquals(userList.size(), 2),
() -> assertEquals(new ArrayList<>(userList).get(0).getUserType(), UserType.VIP),
() -> assertEquals(new ArrayList<>(userList).get(0).getIdCount(), 12),
() -> assertEquals(new ArrayList<>(userList).get(1).getUserType(), UserType.REGULAR),
() -> assertEquals(new ArrayList<>(userList).get(1).getIdCount(), 12)
);
}
}
  1. 例: 查询价格在 300 以内, 价格最高的产品
@GraphQLOperation
public interface ProductRepository {

@Query(product = @ProductQueryArguments(price = @FloatExpression(opr = Operator.LTE, $val = "price")))
@SelectionSet("{ name priceMax }")
Mono<Product> queryPriceMaxLessThan(Float price);
}

接口等同于如下 GraphQL 查询

query queryPriceMaxLessThan($price: Float) {
product(opr: LTE, val: $price) {
name
priceMax
}
}
@ExtendWith(TestResultLoggerExtension.class)
public class ProductRepositoryTest {

private final ProductRepository productRepository = BeanContext.get(ProductRepository.class);

@Test
void queryPriceMaxLessThanTest() {
Product product = productRepository.queryPriceMaxLessThan(300.00f).block();
assertAll(
() -> assertEquals(product.getName(), "Tablet"),
() -> assertEquals(product.getPriceMax(), 299.99f)
);
}
}

变更接口

使用 @Mutation 注解标记接口方法, 请注意此处的 @Mutation 注解是在之前生成的的GPA 注解, 并非 org.eclipse.microprofile.graphql.Mutation

新增

例: 新增用户 Yara

@GraphQLOperation
public interface UserRepository {

@Mutation(user = @UserMutationArguments($input = "userInput"))
@SelectionSet("{ id name email userType }")
Mono<User> mutationUser(UserInput userInput);
}

接口等同于如下 GraphQL 变更

mutation mutationUser($userInput: UserInput) {
user(input: $userInput) {
id
name
email
userType
}
}
@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void mutationUserTest() {
UserInput userInput = new UserInput();
userInput.setName("Yara");
userInput.setEmail("yara@example.com");
userInput.setUserType(UserType.VIP);
User user = userRepository.mutationUser(userInput).block();
assertAll(
() -> assertNotNull(user.getId()),
() -> assertEquals(user.getName(), "Yara"),
() -> assertEquals(user.getEmail(), "yara@example.com"),
() -> assertEquals(user.getUserType(), UserType.VIP)
);
}
}

更新

使用 where 字段可以指定更新条件

例: 通过用户名更新用户类型

@GraphQLOperation
public interface UserRepository {

@Mutation(user = @UserMutationArguments($userType = "userType", where = @UserExpression(name = @StringExpression(opr = Operator.EQ, $val = "name"))))
@SelectionSet("{ id name userType }")
Mono<User> updateUserTypeByName(UserType userType, String name);
}

接口等同于如下 GraphQL 变更

mutation updateUserTypeByName($userType: UserType, $name: String) {
user(userType: $userType, where: { opr: EQ, val: $name }) {
id
name
userType
}
}
@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void updateUserTypeByNameTest() {
User user = userRepository.updateUserTypeByName(UserType.REGULAR, "Yara").block();
assertAll(
() -> assertEquals(user.getName(), "Yara"),
() -> assertEquals(user.getUserType(), UserType.REGULAR)
);
}
}

删除

通过设置 isDeprecated = true 来删除对象

例: 通过用户名删除用户

@GraphQLOperation
public interface UserRepository {

@Mutation(user = @UserMutationArguments(isDeprecated = true, where = @UserExpression(name = @StringExpression(opr = Operator.EQ, $val = "name"))))
@SelectionSet("{ id }")
Mono<User> removeUserByName(String name);
}

接口等同于如下 GraphQL 变更

mutation removeUserByName($name: String) {
user(isDeprecated: true, where: { opr: EQ, val: $name }) {
id
}
}
@ExtendWith(TestResultLoggerExtension.class)
public class UserRepositoryTest {

private final UserRepository userRepository = BeanContext.get(UserRepository.class);

@Test
void removeUserByNameTest() {
User user = userRepository.removeUserByName("Yara").block();
assertNull(user);
}
}

注解说明

接口注解

注解说明示例GraphQL 操作
@GraphQLOperation定义 GPA 接口
@Query定义查询接口@Query(userList = @UserListQueryArguments(userType = @UserTypeExpression(opr = Operator.EQ, val = UserType.VIP)))
Mono<List<User>> queryVIPUserList();
query queryVIPUserList {
 userList(userType: { opr: EQ, val: VIP }) {
  id
  name
  userType
 }
}
@Mutation定义变更接口@Mutation(user = @UserMutationArguments($input = "userInput"))
Mono<User> mutationUser(UserInput userInput);
mutation mutationUser($userInput: UserInput) {
 user(input: $userInput) {
  id
  name
  userType
 }
}
@SelectionSet定义查询字段@Query(userList = @UserListQueryArguments(userType = @UserTypeExpression(opr = Operator.EQ, val = UserType.VIP)))
@SelectionSet("{ name }")
Mono<List<User>> queryVIPUserList();
query queryVIPUserList {
 userList(userType: { opr: EQ, val: VIP }) {
  name
 }
}

返回值说明

方法返回类型适用范围说明示例(Type=User)
Mono<(Type)>所有类默认异步返回结果, 通过 Mono 类型操作返回结果@Mutation(user = @UserMutationArguments($input = "userInput"))
Mono<User> mutationUser(UserInput userInput);
(Type)GraphQL API类中在 GPI 类中编译器可以同步返回结果, 原理参考同步与异步中的内容@Mutation(user = @UserMutationArguments($input = "userInput"))
User mutationUser(UserInput userInput);

本节示例

https://github.com/doukai/order/tree/main/order-app/src/test/java/demo/gp/order/test