繁体   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