GraphQL и загрузчик данных с использованием библиотеки graphql- java -kickstart - PullRequest
0 голосов
/ 29 мая 2020

Я пытаюсь использовать функцию DataLoader в библиотеке graphql- java -kickstart:

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

Мое приложение является приложением Spring Boot используя 2.3.0.RELEASE. И я использую версию 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;
}

Я могу поставить точку останова в репозитории клиентов и увидеть, как выполняется SQL, и он возвращает список объектов клиентов. Вы также можете видеть, что схеме нужен массив клиентов. Если я удалю приведенный выше код и добавлю преобразователь, чтобы получать клиентов одного за другим .... он работает .... но очень медленно.

Что мне не хватает в конфигурации, из-за чего это ?

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

Спасибо за помощь!

Дэн

Ответы [ 2 ]

0 голосов
/ 01 июня 2020

Спасибо, @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. ..счета и один для клиентов).

0 голосов
/ 30 мая 2020

В CustomGraphQLContextBuilder, я думаю, вам следовало зарегистрировать DataLoader как:

...

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

...

, потому что вы ожидаете список Customers для одного идентификатора учетной записи.

Думаю, это должно сработать.

...