GraphQL 和数据加载器使用 graphql-java-kickstart 库
GraphQL and Data Loader Using the graphql-java-kickstart library
我正在尝试使用 graphql-java-kickstart 库中的 DataLoader 功能:
https://github.com/graphql-java-kickstart
我的应用程序是 Spring 使用 2.3.0.RELEASE 的启动应用程序。我使用 graphql-spring-boot-starter 库的 7.0.1 版。
该库非常易于使用,当我不使用数据加载器时它也能正常工作。然而,我被 N+1 SQL 问题所困扰,因此需要使用数据加载器来帮助缓解这个问题。当我执行一个请求时,我最终得到这个:
Can't resolve value (/findAccountById[0]/customers) : type mismatch error, expected type LIST got class com.daluga.api.account.domain.Customer
我确定我在配置中遗漏了一些东西,但我真的不知道那是什么。
这是我的 graphql 架构:
type Account {
id: ID!
accountNumber: String!
customers: [Customer]
}
type Customer {
id: ID!
fullName: String
}
我创建了一个 CustomGraphQLContextBuilder:
@Component
public class CustomGraphQLContextBuilder implements GraphQLServletContextBuilder {
private final CustomerRepository customerRepository;
public CustomGraphQLContextBuilder(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
return DefaultGraphQLServletContext.createServletContext(buildDataLoaderRegistry(), null).with(httpServletRequest).with(httpServletResponse).build();
}
@Override
public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) {
return DefaultGraphQLWebSocketContext.createWebSocketContext(buildDataLoaderRegistry(), null).with(session).with(handshakeRequest).build();
}
@Override
public GraphQLContext build() {
return new DefaultGraphQLContext(buildDataLoaderRegistry(), null);
}
private DataLoaderRegistry buildDataLoaderRegistry() {
DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry();
dataLoaderRegistry.register("customerDataLoader",
new DataLoader<Long, Customer>(accountIds ->
CompletableFuture.supplyAsync(() ->
customerRepository.findCustomersByAccountIds(accountIds), new SyncTaskExecutor())));
return dataLoaderRegistry;
}
}
我还创建了一个 AccountResolver:
public CompletableFuture<List<Customer>> customers(Account account, DataFetchingEnvironment dfe) {
final DataLoader<Long, List<Customer>> dataloader = ((GraphQLContext) dfe.getContext())
.getDataLoaderRegistry().get()
.getDataLoader("customerDataLoader");
return dataloader.load(account.getId());
}
这是客户存储库:
public List<Customer> findCustomersByAccountIds(List<Long> accountIds) {
Instant begin = Instant.now();
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
String inClause = getInClauseParamFromList(accountIds, namedParameters);
String sql = StringUtils.replace(SQL_FIND_CUSTOMERS_BY_ACCOUNT_IDS,"__ACCOUNT_IDS__", inClause);
List<Customer> customers = jdbcTemplate.query(sql, namedParameters, new CustomerRowMapper());
Instant end = Instant.now();
LOGGER.info("Total Time in Millis to Execute findCustomersByAccountIds: " + Duration.between(begin, end).toMillis());
return customers;
}
我可以在客户存储库中放置一个断点并查看 SQL 执行和它 returns 客户对象列表。您还可以看到该模式需要一组客户。如果我删除上面的代码并放入解析器来一个一个地获取客户......它有效......但真的很慢。
我在配置中缺少什么会导致这种情况?
Can't resolve value (/findAccountById[0]/customers) : type mismatch error, expected type LIST got class com.daluga.api.account.domain.Customer
感谢您的帮助!
段
在CustomGraphQLContextBuilder
中,
我认为您应该将 DataLoader 注册为:
...
dataLoaderRegistry.register("customerDataLoader",
new DataLoader<Long, List<Customer>>(accountIds ->
...
因为,您需要一个帐户 ID 的 Customers
列表。
我想这应该行得通。
谢谢@Bms bharadwaj!问题在于我理解数据加载器中如何返回数据。我最终使用 MappedBatchLoader 将数据放入地图中。地图中的键是 accountId。
private DataLoader<Long, List<Customer>> getCustomerDataLoader() {
MappedBatchLoader<Long, List<Customer>> customerMappedBatchLoader = accountIds -> CompletableFuture.supplyAsync(() -> {
List<Customer> customers = customerRepository.findCustomersByAccountId(accountIds);
Map<Long, List<Customer>> groupByAccountId = customers.stream().collect(Collectors.groupingBy(cust -> cust.getAccountId()));
return groupByAaccountId;
});
// }, new SyncTaskExecutor());
return DataLoader.newMappedDataLoader(customerMappedBatchLoader);
}
这似乎成功了,因为之前我发布了数百个 SQL 声明,现在减少到 2 个(一个用于 driver SQL... 帐户,另一个为客户)。
我正在尝试使用 graphql-java-kickstart 库中的 DataLoader 功能:
https://github.com/graphql-java-kickstart
我的应用程序是 Spring 使用 2.3.0.RELEASE 的启动应用程序。我使用 graphql-spring-boot-starter 库的 7.0.1 版。
该库非常易于使用,当我不使用数据加载器时它也能正常工作。然而,我被 N+1 SQL 问题所困扰,因此需要使用数据加载器来帮助缓解这个问题。当我执行一个请求时,我最终得到这个:
Can't resolve value (/findAccountById[0]/customers) : type mismatch error, expected type LIST got class com.daluga.api.account.domain.Customer
我确定我在配置中遗漏了一些东西,但我真的不知道那是什么。
这是我的 graphql 架构:
type Account {
id: ID!
accountNumber: String!
customers: [Customer]
}
type Customer {
id: ID!
fullName: String
}
我创建了一个 CustomGraphQLContextBuilder:
@Component
public class CustomGraphQLContextBuilder implements GraphQLServletContextBuilder {
private final CustomerRepository customerRepository;
public CustomGraphQLContextBuilder(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
return DefaultGraphQLServletContext.createServletContext(buildDataLoaderRegistry(), null).with(httpServletRequest).with(httpServletResponse).build();
}
@Override
public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) {
return DefaultGraphQLWebSocketContext.createWebSocketContext(buildDataLoaderRegistry(), null).with(session).with(handshakeRequest).build();
}
@Override
public GraphQLContext build() {
return new DefaultGraphQLContext(buildDataLoaderRegistry(), null);
}
private DataLoaderRegistry buildDataLoaderRegistry() {
DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry();
dataLoaderRegistry.register("customerDataLoader",
new DataLoader<Long, Customer>(accountIds ->
CompletableFuture.supplyAsync(() ->
customerRepository.findCustomersByAccountIds(accountIds), new SyncTaskExecutor())));
return dataLoaderRegistry;
}
}
我还创建了一个 AccountResolver:
public CompletableFuture<List<Customer>> customers(Account account, DataFetchingEnvironment dfe) {
final DataLoader<Long, List<Customer>> dataloader = ((GraphQLContext) dfe.getContext())
.getDataLoaderRegistry().get()
.getDataLoader("customerDataLoader");
return dataloader.load(account.getId());
}
这是客户存储库:
public List<Customer> findCustomersByAccountIds(List<Long> accountIds) {
Instant begin = Instant.now();
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
String inClause = getInClauseParamFromList(accountIds, namedParameters);
String sql = StringUtils.replace(SQL_FIND_CUSTOMERS_BY_ACCOUNT_IDS,"__ACCOUNT_IDS__", inClause);
List<Customer> customers = jdbcTemplate.query(sql, namedParameters, new CustomerRowMapper());
Instant end = Instant.now();
LOGGER.info("Total Time in Millis to Execute findCustomersByAccountIds: " + Duration.between(begin, end).toMillis());
return customers;
}
我可以在客户存储库中放置一个断点并查看 SQL 执行和它 returns 客户对象列表。您还可以看到该模式需要一组客户。如果我删除上面的代码并放入解析器来一个一个地获取客户......它有效......但真的很慢。
我在配置中缺少什么会导致这种情况?
Can't resolve value (/findAccountById[0]/customers) : type mismatch error, expected type LIST got class com.daluga.api.account.domain.Customer
感谢您的帮助!
段
在CustomGraphQLContextBuilder
中,
我认为您应该将 DataLoader 注册为:
...
dataLoaderRegistry.register("customerDataLoader",
new DataLoader<Long, List<Customer>>(accountIds ->
...
因为,您需要一个帐户 ID 的 Customers
列表。
我想这应该行得通。
谢谢@Bms bharadwaj!问题在于我理解数据加载器中如何返回数据。我最终使用 MappedBatchLoader 将数据放入地图中。地图中的键是 accountId。
private DataLoader<Long, List<Customer>> getCustomerDataLoader() {
MappedBatchLoader<Long, List<Customer>> customerMappedBatchLoader = accountIds -> CompletableFuture.supplyAsync(() -> {
List<Customer> customers = customerRepository.findCustomersByAccountId(accountIds);
Map<Long, List<Customer>> groupByAccountId = customers.stream().collect(Collectors.groupingBy(cust -> cust.getAccountId()));
return groupByAaccountId;
});
// }, new SyncTaskExecutor());
return DataLoader.newMappedDataLoader(customerMappedBatchLoader);
}
这似乎成功了,因为之前我发布了数百个 SQL 声明,现在减少到 2 个(一个用于 driver SQL... 帐户,另一个为客户)。