簡體   English   中英

帶有 Spring Boot 和事務邊界的 graphql-spqr

[英]graphql-spqr with Spring Boot and Transactional Boundary

我們正在為一個新項目(使用 Spring DataJPA、hibernate 等)使用 graphql-spqr 和 graphql-spqr-spring-boot-starter。

我們有這樣的突變:

@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 由 ID 引用並由teamRepository加載。 返回的團隊只是一個休眠代理。 這對於保存新匹配很好,不需要更多。 但是為了發回結果,GraphQL 需要訪問代理並調用 match.team.getName()。 但這顯然發生在標有@Transactional的事務之外。

我可以用 Team homeTeam teamRepository.findById(matchForm.getHomeId()).orElse(null); 修復它。 match.setHomeTeam(homeTeam);

Hibernate 不再加載代理而是加載真實的對象。 但由於我不知道 GraphQL 查詢究竟要求什么,如果以后不需要,急切地加載所有數據是沒有意義的。 如果 GraphQL 可以在@Transactional執行,那就太好了,這樣我就可以為每個查詢和突變定義事務邊界。

對此有什么建議嗎?

PS:我剝離了代碼並進行了一些清理以使其更簡潔。 所以代碼可能無法運行,但確實說明了問題。

@kaqqao 的回答非常好,但我想對這些發表評論並展示第三個解決方案:

  1. 我不喜歡這個解決方案,因為我必須在一個我真的不關心它的環境中做很多工作。 這不是 GraphQL 的意義所在。 如果需要,應該進行解析。 展望每個 GraphQL 查詢和路徑的每個方向,對我來說聽起來很奇怪。

  2. 我設法用一個服務類來實現這一點。 但是您會遇到異常問題,因為異常被包裝並且沒有正確處理。

     public class TransactionalGraphQLExecutor implements GraphQLServletExecutor { private final ServletContextFactory contextFactory; @Autowired(required = false) @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") private DataLoaderRegistryFactory dataLoaderRegistryFactory; private final TxGraphQLExecutor txGraphQLExecutor; public TransactionalGraphQLExecutor(ServletContextFactory contextFactory, TxGraphQLExecutor txGraphQLExecutor) { this.contextFactory = contextFactory; this.txGraphQLExecutor = txGraphQLExecutor; } @Override public Map<String, Object> execute(GraphQL graphQL, GraphQLRequest graphQLRequest, NativeWebRequest nativeRequest) { ExecutionInput executionInput = buildInput(graphQLRequest, nativeRequest, contextFactory, dataLoaderRegistryFactory); if (graphQLRequest.getQuery().startsWith("mutation")) { return txGraphQLExecutor.executeReadWrite(graphQL, executionInput); } else { return txGraphQLExecutor.executeReadOnly(graphQL, executionInput); } }

    }

     public class TxGraphQLExecutor { @Transactional public Map<String, Object> executeReadWrite(GraphQL graphQL, ExecutionInput executionInput) { return graphQL.execute(executionInput).toSpecification(); } @Transactional(readOnly = true) public Map<String, Object> executeReadOnly(GraphQL graphQL, ExecutionInput executionInput) { return graphQL.execute(executionInput).toSpecification(); }

    }

  1. 還有一種可能性,見https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a

  2. 目前我最喜歡的是擁有另一個手動解析器

     @GraphQLQuery @Transactional(readOnly = true) public Team getHomeTeam(@GraphQLContext Match match) { return matchRepository.getOne(match.getId()).getHomeTeam(); }
  3. 當然你也可以設置spring.jpa.open-in-view=false (反模式)

  4. 或者你可以急切地取物。

如果您可以使用 GraphQL 定義事務邊界,那就太好了。

我可以提供一些您可能需要考慮的事項。

1)按需加載

您始終可以先發制人地檢查查詢所需的字段並急切地加載它們。 通過在 graphql-java 博客上的前瞻文章構建高效的數據獲取器中詳細解釋了詳細信息。

簡而言之,您可以調用DataFetchingEnvironment#getSelectionSet() ,它會為您提供DataFetchingFieldSelectionSet並包含優化加載所需的所有信息。

在 SPQR 中,您始終可以通過注入ResolutionEnvironment來獲取DataFetchingEnvironment (以及更多):

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

如果你只需要第一級子字段的名稱,你可以注入

@GraphQLEnvironment Set<String> selection

反而。

為了可測試性,您始終可以連接到您自己的ArgumentInjector ,以准確挑選您需要注入的內容,因此在測試中更容易模擬。

2)在一個事務中運行整個GraphQL查詢解析

除了在單個解析器上使用@Transactional之外,您還可以將默認控制器替換為在事務中運行整個事物的控制器。 只需將@Transactional放在控制器方法上,就可以了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM