数据集成

Spring for GraphQL 允许您利用现有的 Spring 技术,遵循常见的编程模型,通过 GraphQL 公开底层数据源。

本节讨论 Spring Data 的一个集成层,它提供了一种简单的方法来将 Querydsl 或 Query by Example 存储库适配到 DataFetcher,包括为标记有 @GraphQlRepository 的存储库自动检测和 GraphQL 查询注册的选项。

Querydsl

Spring for GraphQL 支持使用 Querydsl 通过 Spring Data 的 Querydsl 扩展 获取数据。Querydsl 提供了一种灵活且类型安全的表达查询谓词的方法,通过使用注释处理器生成元模型。

例如,将存储库声明为 QuerydslPredicateExecutor

public interface AccountRepository extends Repository<Account, Long>,
			QuerydslPredicateExecutor<Account> {
}

然后使用它来创建一个 DataFetcher

// For single result queries
DataFetcher<Account> dataFetcher =
		QuerydslDataFetcher.builder(repository).single();

// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
		QuerydslDataFetcher.builder(repository).many();

// For paginated queries
DataFetcher<Iterable<Account>> dataFetcher =
		QuerydslDataFetcher.builder(repository).scrollable();

现在,您可以通过 RuntimeWiringConfigurer 注册上述 DataFetcher

DataFetcher 从 GraphQL 参数构建 Querydsl Predicate,并使用它来获取数据。Spring Data 支持 JPA、MongoDB、Neo4j 和 LDAP 的 QuerydslPredicateExecutor

对于作为 GraphQL 输入类型的单个参数,QuerydslDataFetcher 向下嵌套一层,并使用参数子映射中的值。

如果存储库是 ReactiveQuerydslPredicateExecutor,则构建器返回 DataFetcher<Mono<Account>>DataFetcher<Flux<Account>>。Spring Data 支持 MongoDB 和 Neo4j 的此变体。

构建设置

要在您的构建中配置 Querydsl,请遵循 官方参考文档

例如

  • Gradle

  • Maven

dependencies {
	//...

	annotationProcessor "com.querydsl:querydsl-apt:$querydslVersion:jpa",
			'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final',
			'javax.annotation:javax.annotation-api:1.3.2'
}

compileJava {
	 options.annotationProcessorPath = configurations.annotationProcessor
}
<dependencies>
	<!-- ... -->
	<dependency>
		<groupId>com.querydsl</groupId>
		<artifactId>querydsl-apt</artifactId>
		<version>${querydsl.version}</version>
		<classifier>jpa</classifier>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>org.hibernate.javax.persistence</groupId>
		<artifactId>hibernate-jpa-2.1-api</artifactId>
		<version>1.0.2.Final</version>
	</dependency>
	<dependency>
		<groupId>javax.annotation</groupId>
		<artifactId>javax.annotation-api</artifactId>
		<version>1.3.2</version>
	</dependency>
</dependencies>
<plugins>
	<!-- Annotation processor configuration -->
	<plugin>
		<groupId>com.mysema.maven</groupId>
		<artifactId>apt-maven-plugin</artifactId>
		<version>${apt-maven-plugin.version}</version>
		<executions>
			<execution>
				<goals>
					<goal>process</goal>
				</goals>
				<configuration>
					<outputDirectory>target/generated-sources/java</outputDirectory>
					<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
				</configuration>
			</execution>
		</executions>
	</plugin>
</plugins>

webmvc-http 示例使用 Querydsl 作为 artifactRepositories

自定义

QuerydslDataFetcher 支持自定义如何将 GraphQL 参数绑定到属性以创建 Querydsl Predicate。默认情况下,参数将绑定为每个可用属性的“等于”。要自定义它,您可以使用 QuerydslDataFetcher 构建器方法来提供 QuerydslBinderCustomizer

存储库本身可能就是 QuerydslBinderCustomizer 的实例。这会在 自动注册 期间自动检测并透明地应用。但是,当手动构建 QuerydslDataFetcher 时,您需要使用构建器方法来应用它。

QuerydslDataFetcher 支持接口和 DTO 投影,以便在返回这些投影以进行进一步的 GraphQL 处理之前转换查询结果。

要了解什么是投影,请参阅 Spring Data 文档。要了解如何在 GraphQL 中使用投影,请参阅 选择集与投影

要在 Querydsl 存储库中使用 Spring Data 投影,请创建一个投影接口或目标 DTO 类,并通过 projectAs 方法对其进行配置,以获取生成目标类型的 DataFetcher

class Account {

	String name, identifier, description;

	Person owner;
}

interface AccountProjection {

	String getName();

	String getIdentifier();
}

// For single result queries
DataFetcher<AccountProjection> dataFetcher =
		QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).single();

// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
		QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).many();

自动注册

如果存储库使用 @GraphQlRepository 进行注释,则会自动为尚未注册 DataFetcher 且返回类型与存储库域类型匹配的查询注册。这包括单值查询、多值查询和 分页 查询。

默认情况下,查询返回的 GraphQL 类型名称必须与存储库域类型的简单名称匹配。如果需要,您可以使用 @GraphQlRepositorytypeName 属性来指定目标 GraphQL 类型名称。

对于分页查询,存储库域类型的简单名称必须与 Connection 类型名称匹配,不包括 Connection 结尾(例如,Book 匹配 BooksConnection)。对于自动注册,分页是基于偏移量的,每页 20 个项目。

自动注册检测给定存储库是否实现了 QuerydslBinderCustomizer,并通过 QuerydslDataFetcher 构建器方法透明地应用它。

自动注册通过内置的 RuntimeWiringConfigurer 执行,该配置器可以从 QuerydslDataFetcher 获得。Boot 启动器 自动检测 @GraphQlRepository bean 并使用它们来初始化 RuntimeWiringConfigurer

自动注册通过在存储库实例上调用 customize(Builder) 来应用 自定义,前提是您的存储库分别实现了 QuerydslBuilderCustomizerReactiveQuerydslBuilderCustomizer

按示例查询

Spring Data 支持使用 按示例查询 来获取数据。按示例查询 (QBE) 是一种简单的查询技术,不需要您通过特定于存储的查询语言编写查询。

首先声明一个 QueryByExampleExecutor 存储库

public interface AccountRepository extends Repository<Account, Long>,
			QueryByExampleExecutor<Account> {
}

使用 QueryByExampleDataFetcher 将存储库转换为 DataFetcher

// For single result queries
DataFetcher<Account> dataFetcher =
		QueryByExampleDataFetcher.builder(repository).single();

// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
		QueryByExampleDataFetcher.builder(repository).many();

// For paginated queries
DataFetcher<Iterable<Account>> dataFetcher =
		QueryByExampleDataFetcher.builder(repository).scrollable();

现在,您可以通过 RuntimeWiringConfigurer 注册上述 DataFetcher

DataFetcher 使用 GraphQL 参数映射创建存储库的域类型,并将其用作示例对象来获取数据。Spring Data 支持 JPA、MongoDB、Neo4j 和 Redis 的 QueryByExampleDataFetcher

对于作为 GraphQL 输入类型的单个参数,QueryByExampleDataFetcher 向下嵌套一层,并使用参数子映射中的值进行绑定。

如果存储库是 ReactiveQueryByExampleExecutor,则构建器返回 DataFetcher<Mono<Account>>DataFetcher<Flux<Account>>。Spring Data 支持 MongoDB、Neo4j、Redis 和 R2dbc 的此变体。

构建设置

按示例查询已包含在支持它的数据存储的 Spring Data 模块中,因此无需额外的设置即可启用它。

自定义

QueryByExampleDataFetcher 支持接口和 DTO 投影,以便在返回这些投影以进行进一步的 GraphQL 处理之前转换查询结果。

要了解什么是投影,请参阅 Spring Data 文档。要了解投影在 GraphQL 中的作用,请参阅 选择集与投影

要在按示例查询存储库中使用 Spring Data 投影,请创建一个投影接口或目标 DTO 类,并通过 projectAs 方法对其进行配置,以获取生成目标类型的 DataFetcher

class Account {

	String name, identifier, description;

	Person owner;
}

interface AccountProjection {

	String getName();

	String getIdentifier();
}

// For single result queries
DataFetcher<AccountProjection> dataFetcher =
		QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).single();

// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
		QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).many();

自动注册

如果存储库使用 @GraphQlRepository 进行注释,则会自动为尚未注册 DataFetcher 且返回类型与存储库域类型匹配的查询注册。这包括单值查询、多值查询和 分页 查询。

默认情况下,查询返回的 GraphQL 类型名称必须与存储库域类型的简单名称匹配。如果需要,您可以使用 @GraphQlRepositorytypeName 属性来指定目标 GraphQL 类型名称。

对于分页查询,存储库域类型的简单名称必须与 Connection 类型名称匹配,不包括 Connection 结尾(例如,Book 匹配 BooksConnection)。对于自动注册,分页是基于偏移量的,每页 20 个项目。

自动注册是通过一个内置的RuntimeWiringConfigurer执行的,该配置器可以从QueryByExampleDataFetcher获取。该Boot Starter会自动检测@GraphQlRepository bean并使用它们来初始化RuntimeWiringConfigurer

自动注册通过在存储库实例上调用customize(Builder)来应用自定义,前提是您的存储库分别实现了QueryByExampleBuilderCustomizerReactiveQueryByExampleBuilderCustomizer

选择集与投影

一个常见的问题是,GraphQL 选择集如何与Spring Data 投影进行比较,以及每个角色的作用是什么?

简而言之,Spring for GraphQL 不是一个将 GraphQL 查询直接转换为 SQL 或 JSON 查询的数据网关。相反,它允许您利用现有的 Spring 技术,并且不假设 GraphQL 模式与底层数据模型之间存在一对一的映射。这就是为什么客户端驱动的选择和服务器端的数据模型转换可以发挥互补作用的原因。

为了更好地理解,请考虑 Spring Data 将领域驱动设计 (DDD) 作为管理数据层复杂性的推荐方法。在 DDD 中,遵守聚合的约束非常重要。根据定义,聚合只有在完整加载时才有效,因为部分加载的聚合可能会对聚合功能施加限制。

在 Spring Data 中,您可以选择是否希望您的聚合按原样公开,或者在将其作为 GraphQL 结果返回之前是否要对数据模型应用转换。有时仅执行前者就足够了,并且默认情况下,QuerydslQuery by Example 集成会将 GraphQL 选择集转换为属性路径提示,底层 Spring Data 模块使用这些提示来限制选择。

在其他情况下,减少甚至转换底层数据模型以适应 GraphQL 模式非常有用。Spring Data 通过接口和 DTO 投影支持这一点。

接口投影定义了一组固定的属性来公开,其中属性可能是也可能不是null,具体取决于数据存储查询结果。有两种接口投影,它们都决定了要从底层数据源加载哪些属性

  • 封闭接口投影 如果您无法部分实现聚合对象,但仍希望公开一部分属性,则很有帮助。

  • 开放接口投影 利用 Spring 的@Value注解和SpEL表达式来应用轻量级数据转换,例如连接、计算或将静态函数应用于属性。

DTO 投影提供了更高程度的自定义,因为您可以将转换代码放在构造函数或 getter 方法中。

DTO 投影从查询中实现,其中各个属性由投影本身确定。DTO 投影通常与全参数构造函数(例如 Java 记录)一起使用,因此,只有当所有必需字段(或列)都是数据库查询结果的一部分时,才能构造它们。

滚动

分页中所述,GraphQL 游标连接规范定义了一种使用ConnectionEdgePageInfo模式类型的分页机制,而 GraphQL Java 提供了等效的 Java 类型表示。

Spring for GraphQL 提供内置的ConnectionAdapter实现,以透明地适配 Spring Data 分页类型WindowSlice。您可以按如下方式配置:

CursorStrategy<ScrollPosition> strategy = CursorStrategy.withEncoder(
		new ScrollPositionCursorStrategy(),
		CursorEncoder.base64()); (1)

GraphQLTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(
		new WindowConnectionAdapter(strategy),
		new SliceConnectionAdapter(strategy))); (2)

GraphQlSource.schemaResourceBuilder()
		.schemaResources(..)
		.typeDefinitionConfigurer(..)
		.typeVisitors(List.of(visitor)); (3)
1 创建策略以将ScrollPosition转换为 Base64 编码的游标。
2 创建类型访问器以适配从DataFetcher返回的WindowSlice
3 注册类型访问器。

在请求端,控制器方法可以声明一个ScrollSubrange方法参数以向前或向后分页。为此,您必须声明一个CursorStrategy支持ScrollPosition作为 bean。

如果 Spring Data 位于类路径中,则Boot Starter会声明一个CursorStrategy<ScrollPosition> bean,并注册ConnectionFieldTypeVisitor,如上所示。

键集位置

对于KeysetScrollPosition,需要从键集创建游标,键集本质上是Map键值对。要确定如何从键集创建游标,您可以使用CursorStrategy<Map<String, Object>>配置ScrollPositionCursorStrategy。默认情况下,JsonKeysetCursorStrategy将键集Map写入 JSON。这适用于简单的字符串、布尔值、整数和双精度数,但其他类型在没有目标类型信息的情况下无法恢复为相同的类型。Jackson 库具有可以将类型信息包含在 JSON 中的默认类型功能。要安全地使用它,您必须指定允许的类型列表。例如

PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()
		.allowIfBaseType(Map.class)
		.allowIfSubType(ZonedDateTime.class)
		.build();

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL);

然后,您可以创建JsonKeysetCursorStrategy

ObjectMapper mapper = ... ;

CodecConfigurer configurer = ServerCodecConfigurer.create();
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));

JsonKeysetCursorStrategy strategy = new JsonKeysetCursorStrategy(configurer);

默认情况下,如果JsonKeysetCursorStrategy在没有CodecConfigurer的情况下创建并且 Jackson 库位于类路径中,则会对DateCalendarjava.time中的任何类型应用上述自定义。

排序

Spring for GraphQL 定义了一个SortStrategy,用于从 GraphQL 参数创建SortAbstractSortStrategy使用抽象方法实现契约以提取排序方向和属性。要启用对作为控制器方法参数的Sort的支持,您需要声明一个SortStrategy bean。