对于最新的稳定版本,请使用Spring GraphQL 2.0.2spring-doc.cadn.net.cn

数据集成

Spring for GraphQL 让您能够利用现有的 Spring 技术,遵循常见的编程模式,通过 GraphQL 暴露底层数据源。spring-doc.cadn.net.cn

本节讨论了Spring Data的一个整合层,它提供了一种简单的方法来将Querydsl或示例查询仓库适配到DataFetcher中,包括为标记有@GraphQlRepository的仓库自动检测和注册GraphQL查询选项。spring-doc.cadn.net.cn

Querydsl

Spring for GraphQL 支持使用 Querydsl 通过 Spring Data Querydsl 扩展 获取数据。 Querydsl 提供了一种灵活且类型安全的方式来表达查询谓词,通过注解处理器生成元模型。spring-doc.cadn.net.cn

例如,声明一个仓库(repository)为 QuerydslPredicateExecutor:spring-doc.cadn.net.cn

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

然后使用它来创建一个DataFetcherspring-doc.cadn.net.cn

// 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注册上述DataFetcherspring-doc.cadn.net.cn

The DataFetcher 从 GraphQL 参数构建一个 Querydsl Predicate,并使用它来获取数据。Spring Data 支持 QuerydslPredicateExecutor 对于 JPA、MongoDB、Neo4j 和 LDAP。spring-doc.cadn.net.cn

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

如果仓库是ReactiveQuerydslPredicateExecutor,构建器返回DataFetcher<Mono<Account>>DataFetcher<Flux<Account>>。Spring Data 为此变体支持 MongoDB 和 Neo4j。spring-doc.cadn.net.cn

构建设置

要配置Querydsl,请参阅 官方参考文档spring-doc.cadn.net.cn

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>

The webmvc-http 样例使用了 Querydsl 进行 artifactRepositories.spring-doc.cadn.net.cn

自定义配置

QuerydslDataFetcher 支持自定义如何将 GraphQL 参数绑定到属性以创建 Querydsl Predicate。默认情况下,参数会针对每个可用的属性进行“等于”绑定。要自定义这一点,您可以使用 QuerydslDataFetcher 构建器方法来提供一个 QuerydslBinderCustomizerspring-doc.cadn.net.cn

一个仓库本身可以是QuerydslBinderCustomizer的一个实例。这会自动检测并在自动注册过程中透明地应用。然而,当你手动构建一个QuerydslDataFetcher时,你需要使用构建器方法来应用它。spring-doc.cadn.net.cn

QuerydslDataFetcher 支持接口和DTO投影,用于在返回这些结果进行进一步的GraphQL处理之前转换查询结果。spring-doc.cadn.net.cn

要了解投影的相关内容,请参阅 Spring Data 文档。 要理解如何在 GraphQL 中使用投影,请参见 选择集与投影

要使用 Spring Data 投射与 Querydsl 仓库一起工作,请创建一个投射接口或目标 DTO 类,并通过 projectAs 方法进行配置以获取生成目标类型的 DataFetcher:spring-doc.cadn.net.cn

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();

Auto-Registration

如果一个仓库注解了@GraphQlRepository,它将自动注册用于查询那些尚未有注册的DataFetcher并且其返回类型与仓库领域类型匹配的查询。这包括单值查询、多值查询以及分页查询。spring-doc.cadn.net.cn

默认情况下,查询返回的GraphQL类型名称必须与仓库领域类型的简单名称匹配。如果需要,可以使用typeName属性来指定目标GraphQL类型名称。spring-doc.cadn.net.cn

对于分页查询,仓库领域类型的简单名称必须与没有 Connection 结尾的 Connection 类型名称匹配(例如,Book 匹配 BooksConnection)。对于自动注册,分页基于偏移量,每页包含 20 项。spring-doc.cadn.net.cn

自动注册会检测给定的仓库是否实现了QuerydslBinderCustomizer,并通过QuerydslDataFetcher的构建器方法透明地应用这一实现。spring-doc.cadn.net.cn

自动注册是通过内置的RuntimeWiringConfigurer完成的,该RuntimeWiringConfigurer可以从QuerydslDataFetcher中获得。Boot Starter会自动检测@GraphQlRepository bean并使用它们初始化RuntimeWiringConfigurerspring-doc.cadn.net.cn

按示例查询

Spring Data 支持使用 查询示例(Query by Example) 来获取数据。查询示例(QBE) 是一种简单的查询技术,无需您通过特定于存储的查询语言编写查询。spring-doc.cadn.net.cn

使用声明一个仓库,该仓库是QueryByExampleExecutor开始的:spring-doc.cadn.net.cn

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

使用QueryByExampleDataFetcher将仓库转换为DataFetcher:spring-doc.cadn.net.cn

// 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注册上述DataFetcherspring-doc.cadn.net.cn

The DataFetcher 使用 GraphQL 参数映射来创建仓库的域类型,并使用该类型作为查询数据的示例对象。Spring Data 支持QueryByExampleDataFetcher用于 JPA、MongoDB、Neo4j 和 Redis。spring-doc.cadn.net.cn

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

如果仓库是ReactiveQueryByExampleExecutor,构建器返回DataFetcher<Mono<Account>>DataFetcher<Flux<Account>>。Spring Data 支持这种变体用于 MongoDB、Neo4j、Redis 和 R2dbc。spring-doc.cadn.net.cn

构建设置

查询示例已经在支持它的数据存储的Spring Data模块中包含,因此无需额外设置即可启用。spring-doc.cadn.net.cn

自定义配置

QueryByExampleDataFetcher 支持接口和DTO投影,在返回这些结果进行进一步的GraphQL处理之前,将查询结果进行转换。spring-doc.cadn.net.cn

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

要使用 Spring Data 投影与 Query by Example 仓库一起工作,可以创建一个投影接口或目标 DTO 类,并通过 projectAs 方法进行配置以获得生成目标类型的 DataFetcher:spring-doc.cadn.net.cn

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();

Auto-Registration

如果一个仓库注解了@GraphQlRepository,它将自动注册用于查询那些尚未有注册的DataFetcher并且其返回类型与仓库领域类型匹配的查询。这包括单值查询、多值查询以及分页查询。spring-doc.cadn.net.cn

默认情况下,查询返回的GraphQL类型名称必须与仓库领域类型的简单名称匹配。如果需要,可以使用typeName属性来指定目标GraphQL类型名称。spring-doc.cadn.net.cn

对于分页查询,仓库领域类型的简单名称必须与没有 Connection 结尾的 Connection 类型名称匹配(例如,Book 匹配 BooksConnection)。对于自动注册,分页基于偏移量,每页包含 20 项。spring-doc.cadn.net.cn

自动注册是通过内置的RuntimeWiringConfigurer完成的,该RuntimeWiringConfigurer可以从QueryByExampleDataFetcher中获得。Boot Starter会自动检测@GraphQlRepository bean并使用它们初始化RuntimeWiringConfigurerspring-doc.cadn.net.cn

自动注册会应用自定义设置,通过在仓库实例上调用customize(Builder)来实现,如果你的仓库实现了QueryByExampleBuilderCustomizerReactiveQueryByExampleBuilderCustomizer。 其中:0对应原文中的spring-doc.cadn.net.cn

选择集与投影

一个常见问题是,GraphQL 选择集如何与 Spring Data 投影 比较,并且每种方式各自扮演什么角色?spring-doc.cadn.net.cn

短答案是,Spring for GraphQL 并不是一个数据网关,它不会直接将GraphQL 查询转换为SQL或JSON查询。相反,它允许你利用现有的Spring技术,并且不假设GraphQL模式与底层数据模型之间有一对一的映射关系。这就是为什么客户端驱动的数据选择和服务器端的数据模型转换可以相互补充。spring-doc.cadn.net.cn

要更好地理解,请考虑Spring Data推崇领域驱动设计(DDD)作为管理数据层复杂性的推荐方法。在DDD中,遵循聚合的约束非常重要。根据定义,只有当聚合完全加载时才是有效的,因为部分加载的聚合可能会限制聚合的功能。spring-doc.cadn.net.cn

在Spring Data中,您可以选择是直接暴露聚合结果,还是在将其作为GraphQL结果返回之前对数据模型应用转换。有时只需要前者即可,默认情况下,Querydsl查询示例 整合将GraphQL的选择集转换为Spring Data模块使用的属性路径提示,用于限制选择。spring-doc.cadn.net.cn

在其他情况下,为了适应GraphQL模式,减少或甚至转换底层数据模型是有用的。Spring Data 通过接口和DTO投影支持这一点。spring-doc.cadn.net.cn

接口投影定义了一组固定的属性,用于暴露数据,其中的属性可能会或可能不会为null,这取决于数据存储查询结果。有两种类型的接口投影,它们都决定了从底层数据源加载哪些属性。spring-doc.cadn.net.cn

DTO投影提供了一种更高的自定义级别,因为您可以将转换代码放在构造函数中或getter方法中。spring-doc.cadn.net.cn

DTO投影是从查询中产生的,其中个别属性由投影本身确定。DTO投影通常与全参数构造函数一起使用(例如Java记录),因此只有当数据库查询结果包含所有必需字段(或列)时,才能构建这些对象。spring-doc.cadn.net.cn

滚动

分页说明所述,GraphQL Cursor Connection 规范定义了使用 ConnectionEdgePageInfo 架构类型进行分页的机制,而 GraphQL Java 提供了相应的 Java 类型表示。spring-doc.cadn.net.cn

Spring for GraphQL 提供内置的 ConnectionAdapter 实现,可以透明地适配 Spring Data 分页类型 WindowSlice。你可以按照以下方式进行配置:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

The Boot Starter 声明了一个 CursorStrategy<ScrollPosition> bean,并且如果类路径中包含 Spring Data,则会注册 ConnectionFieldTypeVisitorspring-doc.cadn.net.cn

键集位置

对于KeysetScrollPosition,游标需要从一个键集创建,这实际上是一系列Map的键值对。要决定如何从键集创建游标,可以使用ScrollPositionCursorStrategy配置CursorStrategy<Map<String, Object>>。 默认情况下,JsonKeysetCursorStrategy将键集Map写入JSON。这适用于简单的类型如String、Boolean、Integer和Double,但其他类型的值在没有目标类型信息的情况下无法恢复为相同类型。Jackson库具有默认的类型功能,可以在JSON中包含类型信息。为了安全使用它,必须指定允许的类型列表。例如:spring-doc.cadn.net.cn

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

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

然后您可以创建 JsonKeysetCursorStrategy:spring-doc.cadn.net.cn

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库,则上述自定义应用于DateCalendar以及从java.time派生的任何类型。spring-doc.cadn.net.cn

排序

Spring for GraphQL 定义了一个 SortStrategy 来从 GraphQL 参数创建 SortAbstractSortStrategy 实现了该合约并通过抽象方法来提取排序方向和属性。为了在控制器方法参数中启用对 Sort 的支持,您需要声明一个 SortStrategy 颗粒。spring-doc.cadn.net.cn