数据集成

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 中使用投影,请参阅选择集与投影

要将 Spring Data 投影与 Querydsl 仓库一起使用,请创建一个投影接口或目标 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 获取。 启动器 自动检测 @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 投影与 Query by Example 存储库一起使用,请创建投影接口或目标 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,也可能不为null,具体取决于数据存储查询结果。接口投影有两种类型,它们都决定了从底层数据源加载哪些属性。

  • 封闭接口投影 在你无法部分实例化聚合对象,但仍然希望暴露属性子集的情况下很有用。

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

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

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

滚动

分页 中所述,GraphQL Cursor Connection 规范定义了一种使用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。

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

键集位置

对于KeysetScrollPosition,游标需要从键集创建,键集本质上是一个Map,包含键值对。为了决定如何从键集创建游标,你可以使用CursorStrategy<Map<String, Object>> 配置ScrollPositionCursorStrategy。默认情况下,JsonKeysetCursorStrategy 将键集Map 写入 JSON。这对于简单的类型(如 String、Boolean、Integer 和 Double)有效,但其他类型在没有目标类型信息的情况下无法恢复到相同的类型。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。