LazyInitializationException with graphql-spring

Multi tool use
Multi tool use


LazyInitializationException with graphql-spring



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
private List<Competition> competition;
}

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



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!




4 Answers
4



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:


GraphQLQueryResolver


GraphQLResolver<T>


Show


@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!


Show


Competition



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


graphql-spring-boot-starter


graphql-java-tools



I first define a GraphQL Config like this:


@Configuration
public class GraphQLConfig {
@Bean
public Map<String, ExecutionStrategy> executionStrategies() {
Map<String, ExecutionStrategy> executionStrategyMap = new HashMap<>();
executionStrategyMap.put("queryExecutionStrategy", new AsyncTransactionalExecutionStrategy());
return executionStrategyMap;
}
}



Where AsyncTransactionalExecutionStrategy is defined like this:


AsyncTransactionalExecutionStrategy


@Service
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.





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?
– Paulo Faria
Jan 20 at 22:55


competitionRepository.findByShowId(show.getId())





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.
– puelo
Jan 20 at 23:01



show.getCompetitions()



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


@Transactional





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.
– puelo
May 17 at 18:57





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).
– Yogu
May 17 at 20:43



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.


Competition


@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.


ShowResolver



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.


competitions


getCompetition()


Show


LazyInitializationException


{
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.


OneToMany



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.


@Transactional





@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.
– puelo
Jul 2 at 22:37


@Transactional


ShowResolver


ExecutionStrategy


AsyncExecutionStrategy


execute


@Transactional





@puelo, please do as I am at a loss of how to do anything in graphql java.Nothing seems documented at all.
– ms.one
Jul 3 at 11:56





I added it to my original answer.
– puelo
2 days ago



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;





No, that is what i don't want, because i also want to be able to query all Shows without the competitions
– puelo
Dec 31 '17 at 11:44






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

FKhO6PBMH,05shCl GnA,jxS i CEcKxnbJFvgOLkj kwme92q
QThgpyD

Popular posts from this blog

Rothschild family

Cinema of Italy