带有 Spring Boot 和事务边界的 graphql-spqr



我们正在为一个新项目使用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,我知道为什么:

主团队由 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) { ... }

如果您只需要 1st 级子字段的名称,您可以注入

@GraphQLEnvironment Set<String> selection

相反。

对于可测试性,您始终可以连接自己的ArgumentInjector,准确挑选您需要注入的内容,因此在测试中更容易模拟。

2) 在事务中运行整个 GraphQL 查询解析

除了在单个解析器上具有@Transactional之外,您可以将默认控制器替换为在事务中运行整个解析器的控制器。只需将@Transactional拍到控制器方法上,您就可以开始了。

相关内容

  • 没有找到相关文章

最新更新