graphql-spqr с Spring Boot и транзакционной границей - PullRequest
0 голосов
/ 26 марта 2020

Мы используем graphql-spqr и graphql-spqr-spring-boot-starter для нового проекта (с Spring DataJPA, hibernate и т. Д.).

У нас есть мутация, подобная этой:

@Transactional
@GraphQLMutation
public Match createMatch(@NotNull @Valid MatchForm matchForm) {
    Match match = new Match(matchForm.getDate());
    match.setHomeTeam(teamRepository.getOne(matchForm.getHomeId()));
    match.setAwayTeam(teamRepository.getOne(matchForm.getAwayId()));
    match.setResult(matchForm.getResult());
    matchRepository.save(match);
    return match;
}

Эта мутация работает нормально:

mutation createMatch ($matchForm: MatchFormInput!){   
  match: createMatch(matchForm: $matchForm) {       
    id
}
variables: {...}

Я пропустил переменные, поскольку они не важны. Он не работает, если я изменяю его на:

mutation createMatch ($matchForm: MatchFormInput!){   
  match: createMatch(matchForm: $matchForm) {       
    id
    homeTeam {
       name
    }
 }
variables: {...}

Я получаю исключение LazyInitalizationException и знаю, почему:

На homeTeam ссылается идентификатор и загружается teamRepository. Возвращенная команда - это только прокси в спящем режиме. Что хорошо для сохранения нового матча, больше ничего не нужно. Но для отправки результата GraphQL необходимо получить доступ к прокси и вызвать match.team.getName (). Но это происходит, очевидно, за пределами транзакции, помеченной @Transactional.

. Я могу это исправить с помощью Team homeTeam teamRepository.findById (matchForm.getHomeId ()). OrElse (null); match.setHomeTeam (homeTeam);

Hibernate больше не загружает прокси, а реальный объект. Но поскольку я не знаю, что именно запрашивает запрос GraphQL, не имеет смысла загружать все данные, если в дальнейшем они не нужны. Было бы неплохо, если бы GraphQL выполнялся внутри @Transactional, поэтому я могу определить границы транзакций для каждого запроса и мутации.

Любая рекомендация для этого?

PS: я удалил код и сделал некоторую очистку, чтобы сделать его более кратким. поэтому код может не выполняться, но он иллюстрирует проблему.

1 Ответ

1 голос
/ 27 марта 2020

Я могу прийти с парой вещей, которые вы можете рассмотреть.

1) С готовностью загрузить по мере необходимости

Вы всегда можете предварительно проверить, какие поля запроса хочет и охотно загружает их. Детали хорошо объяснены в Построении эффективных сборщиков данных, если заглянуть в статью в блоге graphql- java.

Короче говоря, вы можете получить звонок DataFetchingEnvironment#getSelectionSet(), который даст вам DataFetchingFieldSelectionSet и содержит всю информацию, необходимую для оптимизации загрузки.

В SPQR вы всегда можете получить DataFetchingEnvironment (и многое другое), введя ResolutionEnvironment:

@GraphQLMutation
public Match createMatch(
           @NotNull @Valid MatchForm matchForm,
           @GraphQLEnvironment ResolutionEnvironment env) { ... }

Если вам просто нужны имена подполей 1-го уровня, вы можете вместо этого ввести

@GraphQLEnvironment Set<String> selection

.

Для проверки вы всегда можете подключить свой собственный ArgumentInjector что "черри" выбирает именно то, что вам нужно, чтобы вводить тесты.

2) Выполнить полное разрешение запроса GraphQL в транзакции

вместо или в дополнение к наличию @Transactional для отдельных распознавателей, вы можете заменить контроллер по умолчанию на контроллер, выполняющий всю транзакцию в транзакции. Просто набросьте @Transactional на метод контроллера, и вы готовы к go.

...