LazyInitializationException with graphql-spring
Asked Answered
O

7

18

I am currently in the middle of migrating my REST-Server to GraphQL (at least partly). Most of the work is done, but i stumbled upon this problem which i seem to be unable to solve: OneToMany relationships in a graphql query, with FetchType.LAZY.

I am using: https://github.com/graphql-java/graphql-spring-boot and https://github.com/graphql-java/graphql-java-tools for the integration.

Here is an example:

Entities:

@Entity
class Show {
   private Long id;
   private String name;

   @OneToMany(mappedBy = "show")
   private List<Competition> competition;
}

@Entity
class Competition {
   private Long id;
   private String name;

   @ManyToOne(fetch = FetchType.LAZY)
   private Show show;
}

Schema:

type Show {
    id: ID!
    name: String!
    competitions: [Competition]
}

type Competition {
    id: ID!
    name: String
}

extend type Query {
    shows : [Show]
}

Resolver:

@Component
public class ShowResolver implements GraphQLQueryResolver {
    @Autowired    
    private ShowRepository showRepository;

    public List<Show> getShows() {
        return ((List<Show>)showRepository.findAll());
    }
}

If i now query the endpoint with this (shorthand) query:

{
  shows {
    id
    name
    competitions {
      id
    }
  }
}

i get:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Show.competitions, could not initialize proxy - no Session

Now i know why this error happens and what it means, but i don't really know were to apply a fix for this. I don't want to make my entites to eagerly fetch all relations, because that would negate some of the advantages of GraphQL. Any ideas where i might need to look for a solution? Thanks!

Orsino answered 30/12, 2017 at 20:25 Comment(0)
O
14

I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose. Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Showclass, which looks like this:

@Component
public class ShowResolver implements GraphQLResolver<Show> {
    @Autowired
    private CompetitionRepository competitionRepository;

    public List<Competition> competitions(Show show) {
        return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
    }
}

This tells the library how to resolve complex objects inside my Showclass and is only used if the initially query requests to include the Competitionobjects. Happy new Year!

EDIT 31.07.2019: I since stepped away from the solution below. Long running transactions are seldom a good idea and in this case it can cause problems once you scale your application. We started to implement DataLoaders to batch queries in an async matter. The long running transactions in combination with the async nature of the DataLoaders can lead to deadlocks: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (above and below for more information). I will not remove the solution below, because it might still be good starting point for smaller applications and/or applications which will not need any batched queries, but please keep this comment in mind when doing so.

EDIT: As requested here is another solution using a custom execution strategy. I am using graphql-spring-boot-starter and graphql-java-tools:

Create a Bean of type ExecutionStrategy that handles the transaction, like this:

@Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {

    @Override
    @Transactional
    public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        return super.execute(executionContext, parameters);
    }
}

This puts the whole execution of the query inside the same transaction. I don't know if this is the most optimal solution, and it also already has some drawbacks in regards to error handling, but you don't need to define a type resolver that way.

Notice that if this is the only ExecutionStrategy Bean present, this will also be used for mutations, contrary to what the Bean name might suggest. See https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot/GraphQLWebAutoConfiguration.java#L161-L166 for reference. To avoid this define another ExecutionStrategy to be used for mutations:

@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
    return new AsyncSerialExecutionStrategy();
}
Orsino answered 1/1, 2018 at 0:7 Comment(10)
Looks like you're using a bidirectional one-to-many association so you can call competitionRepository.findByShowId(show.getId()). Is this the only way you could access the competition collection from the show entity without eager loading?Papistry
I am not sure what you are asking, but without having the show inside the competition there would be no way of knowing what competitions belong to which show. I would think that while having an open session show.getCompetitions() just returns proxies (lazy) and then if a complete object is needed also hits the database similarily to how i have done it.Orsino
@Orsino You AsyncTransactionalExecutionStrategy is already a @Service so Spring will create a bean for it. So there is no need to create a new AsyncTransactionalExecutionStrategy() inside the executionStrategies() method. You should simply inject the AsyncTransactionalExecutionStrategy bean there.Yestreen
@Orsino what kind of drawbacks did you face?Yestreen
Another improvement for a specific QUERY execution strategy is to use @Transactional(readOnly = true)Yestreen
You could also use com.oembedler.moon.graphql.boot.GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY instead of the "magical" string "queryExecutionStrategy". Thanks to this you can discover that you can do the similar to mutations and subscriptions :)Indo
Important note: both solutions introduce N+1 problem. GraphQL engine will evaluate competitions of each Show object one by one. So the more Show objects (records in database) you have the more requests will be made to database which dramatically affects performance.Cryptoanalysis
@Lu55 True. Although i don't think that the solution here should aim to solve both type of problems. If you have the resolvers set up correctly it should not be to hard to replace your current solution with batch loaders.Orsino
@Orsino Spring does not handle a Map<String, Bean> map as instantiation of multiple beans with distinct names. In the GraphQLWebAutoConfiguration class this bean will be autowired, but with another name. The only reason this works is that the class has a fallback for the case where only one ExecutionStrategy Bean is present (github.com/graphql-java-kickstart/graphql-spring-boot/blob/…); but it will actually use this strategy for query AND mutation execution.Synopsize
@H.Schulz You are right. I think it is good pointing this out, since a big Transaction around the mutations can do some harm.Orsino
M
18

My prefered solution is to have the transaction open until the Servlet sends its response. With this small code change your LazyLoad will work right:

import javax.servlet.Filter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  /**
   * Register the {@link OpenEntityManagerInViewFilter} so that the
   * GraphQL-Servlet can handle lazy loads during execution.
   *
   * @return
   */
  @Bean
  public Filter OpenFilter() {
    return new OpenEntityManagerInViewFilter();
  }

}
Marshamarshal answered 24/8, 2018 at 7:20 Comment(0)
O
14

I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose. Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Showclass, which looks like this:

@Component
public class ShowResolver implements GraphQLResolver<Show> {
    @Autowired
    private CompetitionRepository competitionRepository;

    public List<Competition> competitions(Show show) {
        return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
    }
}

This tells the library how to resolve complex objects inside my Showclass and is only used if the initially query requests to include the Competitionobjects. Happy new Year!

EDIT 31.07.2019: I since stepped away from the solution below. Long running transactions are seldom a good idea and in this case it can cause problems once you scale your application. We started to implement DataLoaders to batch queries in an async matter. The long running transactions in combination with the async nature of the DataLoaders can lead to deadlocks: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (above and below for more information). I will not remove the solution below, because it might still be good starting point for smaller applications and/or applications which will not need any batched queries, but please keep this comment in mind when doing so.

EDIT: As requested here is another solution using a custom execution strategy. I am using graphql-spring-boot-starter and graphql-java-tools:

Create a Bean of type ExecutionStrategy that handles the transaction, like this:

@Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {

    @Override
    @Transactional
    public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        return super.execute(executionContext, parameters);
    }
}

This puts the whole execution of the query inside the same transaction. I don't know if this is the most optimal solution, and it also already has some drawbacks in regards to error handling, but you don't need to define a type resolver that way.

Notice that if this is the only ExecutionStrategy Bean present, this will also be used for mutations, contrary to what the Bean name might suggest. See https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot/GraphQLWebAutoConfiguration.java#L161-L166 for reference. To avoid this define another ExecutionStrategy to be used for mutations:

@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
    return new AsyncSerialExecutionStrategy();
}
Orsino answered 1/1, 2018 at 0:7 Comment(10)
Looks like you're using a bidirectional one-to-many association so you can call competitionRepository.findByShowId(show.getId()). Is this the only way you could access the competition collection from the show entity without eager loading?Papistry
I am not sure what you are asking, but without having the show inside the competition there would be no way of knowing what competitions belong to which show. I would think that while having an open session show.getCompetitions() just returns proxies (lazy) and then if a complete object is needed also hits the database similarily to how i have done it.Orsino
@Orsino You AsyncTransactionalExecutionStrategy is already a @Service so Spring will create a bean for it. So there is no need to create a new AsyncTransactionalExecutionStrategy() inside the executionStrategies() method. You should simply inject the AsyncTransactionalExecutionStrategy bean there.Yestreen
@Orsino what kind of drawbacks did you face?Yestreen
Another improvement for a specific QUERY execution strategy is to use @Transactional(readOnly = true)Yestreen
You could also use com.oembedler.moon.graphql.boot.GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY instead of the "magical" string "queryExecutionStrategy". Thanks to this you can discover that you can do the similar to mutations and subscriptions :)Indo
Important note: both solutions introduce N+1 problem. GraphQL engine will evaluate competitions of each Show object one by one. So the more Show objects (records in database) you have the more requests will be made to database which dramatically affects performance.Cryptoanalysis
@Lu55 True. Although i don't think that the solution here should aim to solve both type of problems. If you have the resolvers set up correctly it should not be to hard to replace your current solution with batch loaders.Orsino
@Orsino Spring does not handle a Map<String, Bean> map as instantiation of multiple beans with distinct names. In the GraphQLWebAutoConfiguration class this bean will be autowired, but with another name. The only reason this works is that the class has a fallback for the case where only one ExecutionStrategy Bean is present (github.com/graphql-java-kickstart/graphql-spring-boot/blob/…); but it will actually use this strategy for query AND mutation execution.Synopsize
@H.Schulz You are right. I think it is good pointing this out, since a big Transaction around the mutations can do some harm.Orsino
S
2

For anyone confused about the accepted answer then you need to change the java entities to include a bidirectional relationship and ensure you use the helper methods to add a Competition otherwise its easy to forget to set the relationship up correctly.

@Entity
class Show {
   private Long id;
   private String name;

   @OneToMany(cascade = CascadeType.ALL, mappedBy = "show")
   private List<Competition> competition;

   public void addCompetition(Competition c) {
      c.setShow(this);
      competition.add(c);
   }
}

@Entity
class Competition {
   private Long id;
   private String name;

   @ManyToOne(fetch = FetchType.LAZY)
   private Show show;
}

The general intuition behind the accepted answer is:

The graphql resolver ShowResolver will open a transaction to get the list of shows but then it will close the transaction once its done doing that.

Then the nested graphql query for competitions will attempt to call getCompetition() on each Show instance retrieved from the previous query which will throw a LazyInitializationException because the transaction has been closed.

{
  shows {
    id
    name
    competitions {
      id
    }
  }
}

The accepted answer is essentially bypassing retrieving the list of competitions through the OneToMany relationship and instead creates a new query in a new transaction which eliminates the problem.

Not sure if this is a hack but @Transactional on resolvers doesn't work for me although the logic of doing that does make some sense but I am clearly not understanding the root cause.

Savona answered 1/7, 2018 at 10:36 Comment(3)
@Transactional on the ShowResolver does not work, because by the time that GraphQL tries to resolve the competitions the transaction is already closed. I am currently using another solution (which i am also not sure if it is optimal): I defined a custom ExecutionStrategy (which basically is the same as the AsyncExecutionStrategy) where the execute method is annoted with @Transactional. I can provide an update to my answer if needed.Orsino
@puelo, please do as I am at a loss of how to do anything in graphql java.Nothing seems documented at all.Savona
I added it to my original answer.Orsino
S
0

For me using AsyncTransactionalExecutionStrategy worked incorrectly with exceptions. E.g. lazy init or app-level exception triggered transaction to rollback-only status. Spring transaction mechanism then threw on rollback-only transaction at the boundary of strategy execute, causing HttpRequestHandlerImpl to return 400 empty response. See https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652 for more details.

What worked for me was using Instrumentation to wrap the whole operation in a transaction: https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a

Shack answered 21/5, 2020 at 6:0 Comment(0)
D
0

As Oleg pointed out, the error handling using AsyncTransactionalExecutionStrategy is broken inside nested transactions.

Since the URL in his answer does not work anymore, here is how I have solved it.

First lets have some exception I want to properly handle through GraphQL response

public class UserFriendlyException extends RuntimeException {
    public UserFriendlyException(String message) {
        super(message);
    }
}

Then I defined error response

public class UserFriendlyGraphQLError implements GraphQLError {
    /** Message shown to user */
    private final String message;

    private final List<SourceLocation> locations;

    private final ExecutionPath path;

    public UserFriendlyGraphQLError(String message, List<SourceLocation> locations, ExecutionPath path) {
        this.message = message;
        this.locations = locations;
        this.path = path;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public List<SourceLocation> getLocations() {
        return locations;
    }

    @Override
    public ErrorClassification getErrorType() {
        return CustomErrorClassification.USER_FRIENDLY_ERROR;
    }

    @Override
    public List<Object> getPath() {
        return path.toList();
    }
}
public enum CustomErrorClassification implements ErrorClassification {
    USER_FRIENDLY_ERROR
}

Then I created DataFetcherExceptionHandler to transform it into proper GraphQL response

/**
 * Converts exceptions into error response
 */
public class GraphQLExceptionHandler implements DataFetcherExceptionHandler {

    private final DataFetcherExceptionHandler delegate = new SimpleDataFetcherExceptionHandler();

    @Override
    public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
        // handle user friendly errors
        if (handlerParameters.getException() instanceof UserFriendlyException) {
            GraphQLError error = new UserFriendlyGraphQLError(
                    handlerParameters.getException().getMessage(),
                    List.of(handlerParameters.getSourceLocation()),
                    handlerParameters.getPath());

            return DataFetcherExceptionHandlerResult.newResult().error(error).build();
        }

        // delegate to default handler otherwise
        return delegate.onException(handlerParameters);
    }
}

And finally used it in the AsyncTransactionalExecutionStrategy, using also the @Transactional annotation to allow lazy resolvers

@Component
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {

    public AsyncTransactionalExecutionStrategy() {
        super(new GraphQLExceptionHandler());
    }

    @Override
    @Transactional
    public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
        return super.execute(executionContext, parameters);
    }
}

Now if you throw new UserFriendlyException("Email already exists"); somewhere you would end up with nice response like

{
    "errors": [
        {
            "message": "Email already exists",
            "locations": [
                {
                    "line": 2,
                    "column": 3
                }
            ],
            "path": [
                "createUser"
            ],
            "extensions": {
                "classification": "USER_FRIENDLY_ERROR"
            }
        }
    ],
    "data": null
}

Given the classification USER_FRIENDLY_ERROR you can directly show it to the user, if you made UserFriendlyException messages user-friendly :)

However if you throw new UserFriendlyException("Email already exists"); inside some method annotated with @Transactional you end up with empty response and HTTP 400 status.

Adding @Transactional(propagation = Propagation.REQUIRES_NEW) to Mutation solves this issue

@Transactional(propagation = Propagation.REQUIRES_NEW)
public class Mutation implements GraphQLMutationResolver {

    public User createUser(...) {
        ...
    }
}

Note that this is probably not so performant solution. However it could suffice for some smaller projects.

Deathwatch answered 21/7, 2023 at 11:20 Comment(0)
C
-4

I am assuming that whenever you fetch an object of Show, you want all the associated Competition of the Show object.

By default the fetch type for all collections type in an entity is LAZY. You can specify the EAGER type to make sure hibernate fetches the collection.

In your Show class you can change the fetchType to EAGER.

@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
Causation answered 31/12, 2017 at 7:19 Comment(2)
No, that is what i don't want, because i also want to be able to query all Shows without the competitionsOrsino
OP said in his question that he didn't want to make all collections eagerMullens
A
-4

You just need to annotate your resolver classes with @Transactional. Then, entities returned from repositories will be able to lazily fetch data.

Administer answered 17/5, 2018 at 15:37 Comment(2)
Are you sure? Wouldn't the session be closed once the resolver method is finished executing, and thus will still fail when the GraphQL DataFetcher executes any getter/type-resolver on the @OneToMany relationship entity? This was my expierence with this at least.Orsino
Ok, I did not test it for this case, but at least it lets you use lazily loaded collections within the resolver method (which otherwise yields this same exception).Administer

© 2022 - 2024 — McMap. All rights reserved.