Data Integration
Spring for GraphQL 让你能够利用现有的 Spring 技术,按照常见的编程模型通过 GraphQL 公开底层数据源。
Spring for GraphQL lets you leverage existing Spring technology, following common programming models to expose underlying data sources through GraphQL.
本节讨论 Spring Data 的集成层,它提供了一种简单的方法来改编 Querydsl 或 Query by Example 存储库为 DataFetcher
,包括自动检测和 GraphQL 查询注册带有 @GraphQlRepository
的存储库。
This section discusses an integration layer for Spring Data that provides an easy way to
adapt a Querydsl or a Query by Example repository to a DataFetcher
, including the
option for automated detection and GraphQL Query registration for repositories marked
with @GraphQlRepository
.
Querydsl
Spring for GraphQL 支持使用 Querydsl通过 Spring Data Querydsl extension 提取数据。Querydsl 提供了一种灵活但类型安全的方法,可使用注释处理器生成元模型,由此表达查询谓词。
Spring for GraphQL supports use of Querydsl to fetch data through the Spring Data Querydsl extension. Querydsl provides a flexible yet typesafe approach to express query predicates by generating a meta-model using annotation processors.
例如,将存储库声明为 QuerydslPredicateExecutor
:
For example, declare a repository as QuerydslPredicateExecutor
:
public interface AccountRepository extends Repository<Account, Long>,
QuerydslPredicateExecutor<Account> {
}
然后用它创建一个 DataFetcher
:
Then use it to create a 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();
你现在可以通过一个 xref:request-execution.adoc#execution.graphqlsource.runtimewiring-configurer[RuntimeWiringConfigurer
)来注册以上的 DataFetcher
。
You can now register the above DataFetcher
through a
RuntimeWiringConfigurer
.
DataFetcher
从 GraphQL 参数构建 Querydsl Predicate
,并用它来获取数据。Spring Data 支持 JPA、MongoDB、Neo4j 和 LDAP 的 QuerydslPredicateExecutor
。
The DataFetcher
builds a Querydsl Predicate
from GraphQL arguments, and uses it to
fetch data. Spring Data supports QuerydslPredicateExecutor
for JPA, MongoDB, Neo4j, and LDAP.
对于一个作为 GraphQL 输入类型的单个参数, |
For a single argument that is a GraphQL input type, |
如果存储库是 ReactiveQuerydslPredicateExecutor
,则生成器将返回 DataFetcher<Mono<Account>>
或 DataFetcher<Flux<Account>>
。Spring Data 支持 MongoDB 和 Neo4j 的此变体。
If the repository is ReactiveQuerydslPredicateExecutor
, the builder returns
DataFetcher<Mono<Account>>
or DataFetcher<Flux<Account>>
. Spring Data supports this
variant for MongoDB and Neo4j.
Build Setup
要在你的构建中配置 Querydsl,请按照 official reference documentation操作:
To configure Querydsl in your build, follow the official reference documentation:
例如:
For example:
-
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 示例对`artifactRepositories`使用 Querydsl。
The webmvc-http sample uses Querydsl for
artifactRepositories
.
Customizations
QuerydslDataFetcher
支持自定义如何将 GraphQL 参数绑定到属性以创建 Querydsl Predicate
。默认情况下,参数绑定为 “等于” 对每个可用的属性。要自定义它,你可以使用 QuerydslDataFetcher
生成器方法来提供一个 QuerydslBinderCustomizer
。
QuerydslDataFetcher
supports customizing how GraphQL arguments are bound onto properties
to create a Querydsl Predicate
. By default, arguments are bound as "is equal to" for
each available property. To customize that, you can use QuerydslDataFetcher
builder
methods to provide a QuerydslBinderCustomizer
.
一个资源库本身可以是 QuerydslBinderCustomizer
的一个实例。这是在 Auto-Registration
期间自动检测和透明应用的。但是,在手动构建 QuerydslDataFetcher
时,你将需要使用构建器方法来应用它。
A repository may itself be an instance of QuerydslBinderCustomizer
. This is auto-detected
and transparently applied during Auto-Registration. However, when manually
building a QuerydslDataFetcher
you will need to use builder methods to apply it.
`QuerydslDataFetcher`支持接口和DTO投影,以便在为进一步的GraphQL处理返回查询结果之前对其进行转换。
QuerydslDataFetcher
supports interface and DTO projections to transform query results
before returning these for further GraphQL processing.
要了解投影是什么,请参考 Spring Data docs。要了解如何在 GraphQL 中使用投影,请参阅 Selection Set vs Projections。 |
To learn what projections are, please refer to the Spring Data docs. To understand how to use projections in GraphQL, please see Selection Set vs Projections. |
要将Spring Data投影与QueryDSL存储库一起使用,请创建投影接口或目标DTO类,并通过`projectAs`方法对其进行配置,以获取生成目标类型的`DataFetcher`:
To use Spring Data projections with Querydsl repositories, create either a projection interface
or a target DTO class and configure it through the projectAs
method to obtain a
DataFetcher
producing the target type:
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
,并且其返回类型与资源库领域类型匹配。这包括单值查询、多值查询和 paginated
查询。
If a repository is annotated with @GraphQlRepository
, it is automatically registered
for queries that do not already have a registered DataFetcher
and whose return type
matches that of the repository domain type. This includes single value queries, multi-value
queries, and paginated queries.
默认情况下,查询返回的GraphQL类型的名称必须与存储库域类型名称的简单名称匹配。如果需要,可以使用`@GraphQlRepository`的`typeName`属性来指定目标GraphQL类型名称。
By default, the name of the GraphQL type returned by the query must match the simple name
of the repository domain type. If needed, you can use the typeName
attribute of
@GraphQlRepository
to specify the target GraphQL type name.
对于分页查询,存储库域类型的简单名称必须与没有`Connection`结尾的`Connection`类型名称匹配(例如`Book匹配
*Books*Connection`)。对于自动注册,分页是基于偏移量的,一页有20个项目。
For paginated queries, the simple name of the repository domain type must match the
Connection
type name without the Connection
ending (e.g. Book
matches
*Books*Connection
). For auto-registration, pagination is offset-based with 20 items
per page.
自动注册会检测给定的存储库是否实现了`QuerydslBinderCustomizer`,并通过`QuerydslDataFetcher`构建器方法透明地应用该检测。
Auto-registration detects if a given repository implements QuerydslBinderCustomizer
and
transparently applies that through QuerydslDataFetcher
builder methods.
自动注册通过内置 RuntimeWiringConfigurer
执行,它可以从 QuerydslDataFetcher
中获取。Boot Starter 自动检测 @GraphQlRepository
bean,并使用它们对`RuntimeWiringConfigurer` 进行初始化。
Auto-registration is performed through a built-in RuntimeWiringConfigurer
that can be
obtained from QuerydslDataFetcher
. The Boot Starter automatically
detects @GraphQlRepository
beans and uses them to initialize the
RuntimeWiringConfigurer
with.
如果你的资源库分别实现了 QuerydslBuilderCustomizer
或 ReactiveQuerydslBuilderCustomizer
,自动注册会通过在资源库实例上调用 customize(Builder)
来应用 customizations
。
Auto-registration applies customizations
by calling customize(Builder)
on the repository instance if your repository
implements QuerydslBuilderCustomizer
or ReactiveQuerydslBuilderCustomizer
respectively.
Query by Example
Spring Data 支持使用 Query by Example 提取数据。按示例查询 (QBE) 是一种简单的查询技术,不需要使用特定于存储的查询语言来编写查询。
Spring Data supports the use of Query by Example to fetch data. Query by Example (QBE) is a simple querying technique that does not require you to write queries through store-specific query languages.
首先声明一个`QueryByExampleExecutor`存储库:
Start by declaring a repository that is QueryByExampleExecutor
:
public interface AccountRepository extends Repository<Account, Long>,
QueryByExampleExecutor<Account> {
}
使用`QueryByExampleDataFetcher`将存储库转换为`DataFetcher`:
Use QueryByExampleDataFetcher
to turn the repository into a 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();
你现在可以通过一个 xref:request-execution.adoc#execution.graphqlsource.runtimewiring-configurer[RuntimeWiringConfigurer
)来注册以上的 DataFetcher
。
You can now register the above DataFetcher
through a
RuntimeWiringConfigurer
.
DataFetcher`使用GraphQL参数映射来创建存储库的域类型,并使用该域类型作为要提取数据的示例对象。Spring Data支持JPA、MongoDB、Neo4j和Redis的`QueryByExampleDataFetcher
。
The DataFetcher
uses the GraphQL arguments map to create the domain type of the
repository and use that as the example object to fetch data with. Spring Data supports
QueryByExampleDataFetcher
for JPA, MongoDB, Neo4j, and Redis.
对于一个作为 GraphQL 输入类型的单个参数, |
For a single argument that is a GraphQL input type, |
如果存储库是`ReactiveQueryByExampleExecutor`,则构建器会返回`DataFetcher<Mono<Account>>`或`DataFetcher<Flux<Account>>`。Spring Data支持MongoDB、Neo4j、Redis和R2dbc的此变体。
If the repository is ReactiveQueryByExampleExecutor
, the builder returns
DataFetcher<Mono<Account>>
or DataFetcher<Flux<Account>>
. Spring Data supports this
variant for MongoDB, Neo4j, Redis, and R2dbc.
Build Setup
按实例查询已包含在Spring Data模块中,适用于支持该查询的数据存储,因此无需额外的设置即可启用它。
Query by Example is already included in the Spring Data modules for the data stores where it is supported, so no extra setup is required to enable it.
Customizations
`QueryByExampleDataFetcher`支持接口和DTO投影,以便在为进一步的GraphQL处理返回查询结果之前对其进行转换。
QueryByExampleDataFetcher
supports interface and DTO projections to transform query
results before returning these for further GraphQL processing.
要了解投影是什么,请参考 Spring Data documentation。要了解投影在 GraphQL 中的作用,请参阅 Selection Set vs Projections。 |
To learn what projections are, please refer to the Spring Data documentation. To understand the role of projections in GraphQL, please see Selection Set vs Projections. |
要将Spring Data投影与按实例查询存储库一起使用,请创建投影接口或目标DTO类,并通过`projectAs`方法对其进行配置,以获取生成目标类型的`DataFetcher`:
To use Spring Data projections with Query by Example repositories, create either a projection interface
or a target DTO class and configure it through the projectAs
method to obtain a
DataFetcher
producing the target type:
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
,并且其返回类型与资源库领域类型匹配。这包括单值查询、多值查询和 paginated
查询。
If a repository is annotated with @GraphQlRepository
, it is automatically registered
for queries that do not already have a registered DataFetcher
and whose return type
matches that of the repository domain type. This includes single value queries, multi-value
queries, and paginated queries.
默认情况下,查询返回的GraphQL类型的名称必须与存储库域类型名称的简单名称匹配。如果需要,可以使用`@GraphQlRepository`的`typeName`属性来指定目标GraphQL类型名称。
By default, the name of the GraphQL type returned by the query must match the simple name
of the repository domain type. If needed, you can use the typeName
attribute of
@GraphQlRepository
to specify the target GraphQL type name.
对于分页查询,存储库域类型的简单名称必须与没有`Connection`结尾的`Connection`类型名称匹配(例如`Book匹配
*Books*Connection`)。对于自动注册,分页是基于偏移量的,一页有20个项目。
For paginated queries, the simple name of the repository domain type must match the
Connection
type name without the Connection
ending (e.g. Book
matches
*Books*Connection
). For auto-registration, pagination is offset-based with 20 items
per page.
自动注册通过内置 RuntimeWiringConfigurer
执行,它可以从 QueryByExampleDataFetcher
中获取。Boot Starter 自动检测 @GraphQlRepository
bean,并使用它们对`RuntimeWiringConfigurer` 进行初始化。
Auto-registration is performed through a built-in RuntimeWiringConfigurer
that can be
obtained from QueryByExampleDataFetcher
. The Boot Starter automatically
detects @GraphQlRepository
beans and uses them to initialize the
RuntimeWiringConfigurer
with.
如果你的资源库分别实现了 QueryByExampleBuilderCustomizer
或 ReactiveQueryByExampleBuilderCustomizer
,自动注册会通过在资源库实例上调用 customize(Builder)
来应用 customizations
。
Auto-registration applies customizations
by calling customize(Builder)
on the repository instance if your repository
implements QueryByExampleBuilderCustomizer
or
ReactiveQueryByExampleBuilderCustomizer
respectively.
Selection Set vs Projections
一个常见问题是,GraphQL 选择集与 Spring Data projections有何比较,以及各自扮演什么角色?
A common question that arises is, how GraphQL selection sets compare to Spring Data projections and what role does each play?
简短的回答是 Spring for GraphQL 并不是一个直接将 GraphQL 查询转换为 SQL 或 JSON 查询的数据网关。相反,它让你能够利用现有的 Spring 技术,并且它并不想当然地认为 GraphQL 架构和底层数据模型之间是一对一的映射。这就是为什么客户端驱动的选择和服务器端数据模型的转换能够起到互补作用。
The short answer is that Spring for GraphQL is not a data gateway that translates GraphQL queries directly into SQL or JSON queries. Instead, it lets you leverage existing Spring technology and does not assume a one for one mapping between the GraphQL schema and the underlying data model. That is why client-driven selection and server-side transformation of the data model can play complementary roles.
为了更好地理解这一点,考虑一下 Spring Data 推崇基于领域(DDD)的设计作为在数据层管理复杂性的推荐方法。在 DDD 中,遵守聚合的约束非常重要。在定义上,聚合只有在加载全体时才有效,因为部分加载的聚合可能会限制聚合的功能。
To better understand, consider that Spring Data promotes domain-driven (DDD) design as the recommended approach to manage complexity in the data layer. In DDD, it is important to adhere to the constraints of an aggregate. By definition an aggregate is valid only if loaded in its entirety, since a partially loaded aggregate may impose limitations on aggregate functionality.
在 Spring Data 中,你可以选择是否希望按原样公开聚合,或者在将聚合作为 GraphQL 结果返回之前,是否将转换应用于数据模型。有时执行前者就足够了,默认情况下,Querydsl
和 Query by Example
集成将 GraphQL 选择集转换为属性路径提示,底层的 Spring Data 模块使用这些提示来限制选择。
In Spring Data you can choose whether you want your aggregate be exposed as is, or whether to apply transformations to the data model before returning it as a GraphQL result. Sometimes it’s enough to do the former, and by default the Querydsl and the Query by Example integrations turn the GraphQL selection set into property path hints that the underlying Spring Data module uses to limit the selection.
在其他情况下,为了适应 GraphQL 架构,缩减甚至转换底层数据模型很有用。Spring Data 通过接口和 DTO 投影支持这一点。
In other cases, it’s useful to reduce or even transform the underlying data model in order to adapt to the GraphQL schema. Spring Data supports this through Interface and DTO Projections.
接口投影定义了一组固定的属性来公开,这些属性可能为 null
或不为 null
,具体取决于数据存储查询结果。有两种接口投影,两者都决定了从底层数据源加载哪些属性:
Interface projections define a fixed set of properties to expose where properties may or
may not be null
, depending on the data store query result. There are two kinds of
interface projections both of which determine what properties to load from the underlying
data source:
-
Closed interface projections are helpful if you cannot partially materialize the aggregate object, but you still want to expose a subset of properties.
-
Open interface projections leverage Spring’s
@Value
annotation and SpEL expressions to apply lightweight data transformations, such as concatenations, computations, or applying static functions to a property.
DTO 投影提供了更高级别的自定义,因为你可以在构造函数或 getter 方法中放置转换代码。
DTO projections offer a higher level of customization as you can place transformation code either in the constructor or in getter methods.
DTO 投影从查询中具象化,其中各个属性由投影本身决定。DTO 投影通常与全参数构造函数(例如 Java 记录)一起使用,因此它们只能在所有必需字段(或列)都是数据库查询结果的一部分时才能被构建。
DTO projections materialize from a query where the individual properties are determined by the projection itself. DTO projections are commonly used with full-args constructors (e.g. Java records), and therefore they can only be constructed if all required fields (or columns) are part of the database query result.
Scroll
正如 Pagination
中所解释的,GraphQL Cursor Connection 规范定义了一种使用 Connection
、Edge
和 PageInfo
架构类型的分页机制,而 GraphQL Java 提供了等效的 Java 类型表示。
As explained in Pagination, the GraphQL Cursor Connection spec defines a
mechanism for pagination with Connection
, Edge
, and PageInfo
schema types, while
GraphQL Java provides the equivalent Java type representations.
Spring for GraphQL 提供了内置的 ConnectionAdapter
实现,以透明地调整 Spring Data 分页类型 Window
和 Slice
。你可以按照如下方式配置:
Spring for GraphQL provides built-in ConnectionAdapter
implementations to adapt the
Spring Data pagination types Window
and Slice
transparently. You can configure that
as follows:
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 | Create strategy to convert ScrollPosition to a Base64 encoded cursor. |
2 | Create type visitor to adapt Window and Slice returned from `DataFetcher`s. |
3 | Register the type visitor. |
在请求端,控制器方法可以声明一个 ScrollSubrange
方法参数,以向前或向后分页。为此,你必须声明一个支持 ScrollPosition
的 `CursorStrategy
` 作为 bean。
On the request side, a controller method can declare a
ScrollSubrange method argument to paginate forward
or backward. For this to work, you must declare a CursorStrategy
supports ScrollPosition
as a bean.
如果 Spring Data 在类路径中,则 Boot Starter 声明一个 CursorStrategy<ScrollPosition>
bean,并注册上文所示的`ConnectionFieldTypeVisitor`。
The Boot Starter declares a CursorStrategy<ScrollPosition>
bean, and registers the
ConnectionFieldTypeVisitor
as shown above if Spring Data is on the classpath.
Keyset Position
对于 KeysetScrollPosition
,游标需要从键集创建,键集本质上是一个键值对的 Map
。为了决定如何从键集创建游标,你可以使用 CursorStrategy<Map<String, Object>>
配置 ScrollPositionCursorStrategy
。默认情况下,JsonKeysetCursorStrategy
将键集 Map
写入 JSON。对于字符串、布尔值、整数和双精度这样的简单值,该方法有效,但其他值不能在没有目标类型信息的情况下恢复为相同类型。Jackson 库有一个默认的类型化特性,它可以将类型信息包含在 JSON 中。要安全地使用它,你必须指定一个允许的类型列表。例如:
For KeysetScrollPosition
, the cursor needs to be created from a keyset, which is
essentially a Map
of key-value pairs. To decide how to create a cursor from a keyset,
you can configure ScrollPositionCursorStrategy
with CursorStrategy<Map<String, Object>>
.
By default, JsonKeysetCursorStrategy
writes the keyset Map
to JSON. That works for
simple like String, Boolean, Integer, and Double, but others cannot be restored back to the
same type without target type information. The Jackson library has a default typing feature
that can include type information in the JSON. To use it safely you must specify a list of
allowed types. For example:
PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Map.class)
.allowIfSubType(ZonedDateTime.class)
.build();
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL);
然后你可以创建 JsonKeysetCursorStrategy
:
You can then create JsonKeysetCursorStrategy
:
ObjectMapper mapper = ... ;
CodecConfigurer configurer = ServerCodecConfigurer.create();
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper));
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper));
JsonKeysetCursorStrategy strategy = new JsonKeysetCursorStrategy(configurer);
默认情况下,如果在没有 CodecConfigurer
的情况下创建了 JsonKeysetCursorStrategy
,并且 Jackson 库在类路径上,将会为 Date
、Calendar
和 java.time
中的任何类型应用类似于上面的自定义。
By default, if JsonKeysetCursorStrategy
is created without a CodecConfigurer
and the
Jackson library is on the classpath, customizations like the above are applied for
Date
, Calendar
, and any type from java.time
.
Sort
Spring for GraphQL 定义了一个 SortStrategy
,用于从 GraphQL 参数创建 Sort
。AbstractSortStrategy
使用抽象方法实现了该契约,以提取排序方向和属性。为了将 Sort
支持启用为控制器方法参数,你需要声明一个 SortStrategy
bean。
Spring for GraphQL defines a SortStrategy
to create Sort
from GraphQL arguments.
AbstractSortStrategy
implements the contract with abstract methods to extract the sort
direction and properties. To enable support for Sort
as a controller method argument,
you need to declare a SortStrategy
bean.