GraphQL和Data Loader使用graphql-java-kickstart库。

huangapple go评论93阅读模式
英文:

GraphQL and Data Loader Using the graphql-java-kickstart library

问题

我正试图在 graphql-java-kickstart 库中使用 DataLoader 特性:

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

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

这个库很容易使用,当我不使用数据加载程序时,它可以正常工作。然而,我遇到了 N+1 SQL 问题,因此需要使用数据加载程序来帮助缓解这个问题。当我执行请求时,我得到了以下错误:

无法解析值 (/findAccountById[0]/customers):类型不匹配错误,预期类型为 LIST,但得到的是类 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());
}

这是 Customer Repository:

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 对象列表。你还可以看到模式需要一个客户数组。如果我删除上面的代码,并用逐个获取客户的解析器替换它...它可以工作...但速度非常慢。

在配置中我漏掉了什么,会导致这个问题吗?

无法解析值 (/findAccountById[0]/customers):类型不匹配错误,预期类型为 LIST,但得到的是类 com.daluga.api.account.domain.Customer

谢谢你的帮助!

英文:

I am attempting to use the DataLoader feature within the graphql-java-kickstart library:

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

My application is a Spring Boot application using 2.3.0.RELEASE. And I using version 7.0.1 of the graphql-spring-boot-starter library.

The library is pretty easy to use and it works when I don't use the data loader. However, I am plagued by the N+1 SQL problem and as a result need to use the data loader to help alleviate this issue. When I execute a request, I end up getting this:

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

I am sure I am missing something in the configuration but really don't know what that is.

Here is my graphql schema:

type Account {
  id: ID!
  accountNumber: String!
  customers: [Customer]
}

type Customer {
  id: ID!
  fullName: String
}

I have created a 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(&quot;customerDataLoader&quot;,
                new DataLoader&lt;Long, Customer&gt;(accountIds -&gt;
                        CompletableFuture.supplyAsync(() -&gt;
                                customerRepository.findCustomersByAccountIds(accountIds), new SyncTaskExecutor())));

        return dataLoaderRegistry;
    }
}

I also have create an AccountResolver:

public CompletableFuture&lt;List&lt;Customer&gt;&gt; customers(Account account, DataFetchingEnvironment dfe) {
    final DataLoader&lt;Long, List&lt;Customer&gt;&gt; dataloader = ((GraphQLContext) dfe.getContext())
            .getDataLoaderRegistry().get()
            .getDataLoader(&quot;customerDataLoader&quot;);

     return dataloader.load(account.getId());
}

And here is the Customer Repository:

public List&lt;Customer&gt; findCustomersByAccountIds(List&lt;Long&gt; accountIds) {

    Instant begin = Instant.now();

    MapSqlParameterSource namedParameters = new MapSqlParameterSource();
    String inClause = getInClauseParamFromList(accountIds, namedParameters);

    String sql = StringUtils.replace(SQL_FIND_CUSTOMERS_BY_ACCOUNT_IDS,&quot;__ACCOUNT_IDS__&quot;, inClause);

    List&lt;Customer&gt; customers = jdbcTemplate.query(sql, namedParameters, new CustomerRowMapper());

    Instant end = Instant.now();

    LOGGER.info(&quot;Total Time in Millis to Execute findCustomersByAccountIds: &quot; + Duration.between(begin, end).toMillis());

    return customers;
}

I can put a break point in the Customer Repository and see the SQL execute and it returns a List of Customer objects. You can also see that the schema wants an array of customers. If I remove the code above and put in the resolver to get the customers one by one....it works....but is really slow.

What am I missing in the configuration that would cause this?

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

Thanks for your help!

Dan

答案1

得分: 2

谢谢,@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 groupByAccountId;
    });
//    }, new SyncTaskExecutor());

    return DataLoader.newMappedDataLoader(customerMappedBatchLoader);
}

这似乎解决了问题,因为之前我发出了数百条 SQL 语句,现在只有 2 条(一个用于驱动 SQL...accounts,另一个用于 customers)。

英文:

Thanks, @Bms bharadwaj! The issue was on my side in understanding how the data is returned in the dataloader. I ended up using a MappedBatchLoader to bring the data in a map. The key in the map being the accountId.

private DataLoader&lt;Long, List&lt;Customer&gt;&gt; getCustomerDataLoader() {
    MappedBatchLoader&lt;Long, List&lt;Customer&gt;&gt; customerMappedBatchLoader = accountIds -&gt; CompletableFuture.supplyAsync(() -&gt; {
        List&lt;Customer&gt; customers = customerRepository.findCustomersByAccountId(accountIds);
        Map&lt;Long, List&lt;Customer&gt;&gt; groupByAccountId = customers.stream().collect(Collectors.groupingBy(cust -&gt; cust.getAccountId()));
        return groupByAaccountId;
    });
//    }, new SyncTaskExecutor());

    return DataLoader.newMappedDataLoader(customerMappedBatchLoader);
}

This seems to have done the trick because before I was issuing hundreds of SQL statement and now down to 2 (one for the driver SQL...accounts and one for the customers).

答案2

得分: 0

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

...

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

...
因为你期望一个帐户 ID 对应一个 `Customers` 列表。

我猜应该可以这样做。

<details>
<summary>英文:</summary>

In the `CustomGraphQLContextBuilder`,
I think you should have registered the DataLoader as :

    ...
    
    dataLoaderRegistry.register(&quot;customerDataLoader&quot;,
                    new DataLoader&lt;Long, List&lt;Customer&gt;&gt;(accountIds -&gt;
    
    ...
because, you are expecting a list of `Customers` for one account Id. 

That should work I guess.

</details>



huangapple
  • 本文由 发表于 2020年5月30日 03:16:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/62093189.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定