【问题标题】:GraphQL and Data Loader Using the graphql-java-kickstart library使用 graphql-java-kickstart 库的 GraphQL 和数据加载器
【发布时间】:2020-09-17 10:47:52
【问题描述】:

我正在尝试使用 graphql-java-kickstart 库中的 DataLoader 功能:

https://github.com/graphql-java-kickstart

我的应用程序是使用 2.3.0.RELEASE 的 Spring Boot 应用程序。我使用的是 7.0.1 版的 graphql-spring-boot-starter 库。

该库非常易于使用,并且在我不使用数据加载器时也可以使用。但是,我受到 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;
}

我可以在 Customer Repository 中设置一个断点,然后查看 SQL 的执行情况,它会返回一个 Customer 对象列表。您还可以看到架构需要一组客户。如果我删除上面的代码并放入解析器以逐个获取客户....它可以工作....但是真的很慢。

我在配置中遗漏了什么会导致这种情况?

Can't resolve value (/findAccountById[0]/customers) : type mismatch error, expected type LIST got class com.daluga.api.account.domain.Customer

感谢您的帮助!

【问题讨论】:

标签: java spring-boot graphql-java dataloader


【解决方案1】:

谢谢@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 条(一条用于驱动程序 SQL...帐户,一条用于客户)。

【讨论】:

    【解决方案2】:

    CustomGraphQLContextBuilder, 我认为您应该将 DataLoader 注册为:

    ...
    
    dataLoaderRegistry.register("customerDataLoader",
                    new DataLoader<Long, List<Customer>>(accountIds ->
    
    ...
    

    因为,您期望一个帐户 ID 的 Customers 列表。

    我猜应该可以。

    【讨论】:

    • 谢谢,@Bms bharadwaj!问题在于我理解数据如何在数据加载器中返回。我最终使用 MappedBatchLoader 将数据放入地图中。地图中的键是 accountId。
    猜你喜欢
    • 2021-09-12
    • 2018-05-06
    • 2017-07-27
    • 2019-11-13
    • 2017-06-27
    • 2019-07-14
    • 2021-05-31
    • 2020-12-02
    • 2019-03-11
    相关资源
    最近更新 更多