spring-projects / spring-projects/spring-graphql

Spring @Transactional support for Virtual Thread

Open
#1,349 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
1.6k
Forks
336
PR merge metrics
No merged PRs in 30d

Description

As described in #448, when Spring GraphQL receives JPA entities from a DataFetcher, attempting to touch FetchType.LAZY fields triggers:

LazyInitializationException: Cannot lazily initialize collection of role '???' (no session)

Following the latest documentation(transaction-management), I tried enabling "global transaction" with this configuration:

@Bean
public GraphQlSourceBuilderCustomizer customizer(
    FederationSchemaFactory factory,
    ObjectProvider<DataFetcherExceptionResolver> resolvers) {

  final var exceptionHandler =
      DataFetcherExceptionResolver.createExceptionHandler(resolvers.stream().toList());

  return schemaBuilder ->
      schemaBuilder
          .configureGraphQl(
              gqlBuilder ->
                  gqlBuilder
                      .queryExecutionStrategy(new AsyncSerialExecutionStrategy(exceptionHandler))
                      .mutationExecutionStrategy(new AsyncSerialExecutionStrategy(exceptionHandler)));
}

@Component
@RequiredArgsConstructor
public static class GraphQLTransactionalInstrumentation extends SimplePerformantInstrumentation {

  private final PlatformTransactionManager txManager;

  @Override
  public @Nullable InstrumentationContext<ExecutionResult> beginExecuteOperation(
      InstrumentationExecuteOperationParameters parameters, InstrumentationState state) {

    final var tx = txManager.getTransaction(null);

    return new SimpleInstrumentationContext<>() {

      @Override
      public void onCompleted(@Nullable ExecutionResult result, @Nullable Throwable t) {
        if (t == null && (result == null || result.getErrors().isEmpty())) {
          txManager.commit(tx);

        } else {
          txManager.rollback(tx);
        }
      }
    };
  }
}

However, this only works when spring.threads.virtual.enabled = false.

Upon investigation, I found that with virtual threads, AnnotatedControllerConfigurer attempts to configure SchemaMappingDataFetcher/BatchLoaderHandlerMethod with invokeAsync = true. If this succeeds, field resolution always runs in the executor, resulting in a transaction-less context (transactions don't auto-propagate to new threads).

To override this, I tried a BeanPostProcessor to modify shouldInvokeAsync() behavior:

@Component
public static class AnnotatedControllerConfigurerPostProcessor
    implements BeanPostProcessor, Ordered {

  @Override
  public @Nullable Object postProcessAfterInitialization(Object bean, String beanName)
      throws BeansException {
    if (bean instanceof AnnotatedControllerConfigurer configurer) {
      configurer.setBlockingMethodPredicate(ignored -> false);
    }

    return bean;
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }
}

While this works, is there a more robust way to configure this without tightly coupling to Spring GraphQL's internals?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the transaction-management documentation and inspect AnnotatedControllerConfigurer, SchemaMappingDataFetcher, and BatchLoaderHandlerMethod where invokeAsync and shouldInvokeAsync are configured. Determine whether a supported configuration point can keep field resolution in the transaction context with virtual threads, then verify that lazy JPA fields work without coupling to internal implementation details.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, java, spring
Domain
api, backend, backend-api-design
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.