简体   繁体   English

使用 graphql-spring 的 LazyInitializationException

[英]LazyInitializationException with graphql-spring

I am currently in the middle of migrating my REST-Server to GraphQL (at least partly).我目前正在将我的 REST-Server 迁移到 GraphQL(至少部分迁移)。 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.大部分工作已经完成,但我偶然发现了这个我似乎无法解决的问题:graphql 查询中的 OneToMany 关系,使用 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.我正在使用: https : //github.com/graphql-java/graphql-spring-boothttps://github.com/graphql-java/graphql-java-tools进行集成。

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 org.hibernate.LazyInitializationException:无法延迟初始化角色集合:Show.competitions,无法初始化代理 - 没有会话

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.我不想让我的实体急切地获取所有关系,因为这会抵消 GraphQL 的一些优势。 Any ideas where i might need to look for a solution?我可能需要寻找解决方案的任何想法? Thanks!谢谢!

I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose.我解决了它,我想应该更仔细地阅读 graphql-java-tools 库的文档。 Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Show class, 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 Show class and is only used if the initially query requests to include the Competition objects.这告诉库如何解析我的Show类中的复杂对象,并且仅在初始查询请求包含Competition对象时使用。 Happy new Year!新年快乐!

EDIT 31.07.2019 : I since stepped away from the solution below.编辑 31.07.2019 :我从此离开了下面的解决方案。 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.我们开始实现 DataLoaders 以异步方式批量查询。 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).长时间运行的事务与 DataLoaders 的异步性质相结合可能会导致死锁: https : //github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (以上和以下为更多信息)。 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 :我正在使用graphql-spring-boot-startergraphql-java-tools

Create a Bean of type ExecutionStrategy that handles the transaction, like this:创建一个ExecutionStrategy类型的 Bean 来处理事务,如下所示:

@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.请注意,如果这是唯一存在的ExecutionStrategy Bean,则这也将用于突变,这与 Bean 名称可能暗示的相反。 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.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供参考。 To avoid this define another ExecutionStrategy to be used for mutations:为避免这种情况,请定义另一个用于突变的ExecutionStrategy

@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
    return new AsyncSerialExecutionStrategy();
}

My prefered solution is to have the transaction open until the Servlet sends its response.我的首选解决方案是在 Servlet 发送其响应之前打开事务。 With this small code change your LazyLoad will work right:通过这个小代码更改,您的 LazyLoad 将正常工作:

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();
  }

}

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.对于任何对接受的答案感到困惑的人,您需要更改 java 实体以包含双向关系,并确保您使用辅助方法添加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. graphql 解析器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的嵌套 graphql 查询将尝试在从前一个查询中检索到的每个Show实例上调用getCompetition() ,这将抛出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对我不起作用,尽管这样做的逻辑确实有意义,但我显然不了解根本原因。

For me using AsyncTransactionalExecutionStrategy worked incorrectly with exceptions.对我来说,使用AsyncTransactionalExecutionStrategy时出现异常。 Eg 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. Spring 事务机制然后在策略execute的边界处抛出仅回滚事务,导致HttpRequestHandlerImpl返回 400 空响应。 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.有关更多详细信息,请参阅https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250https://github.com/graphql-java/graphql-java/issues/1652

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对我有用的是使用Instrumentation将整个操作包装在一个事务中: https : //spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a

I am assuming that whenever you fetch an object of Show , you want all the associated Competition of the Show object.我假设每当您获取Show对象时,您都需要Show对象的所有相关竞争

By default the fetch type for all collections type in an entity is LAZY .默认情况下,实体中所有集合类型的提取类型为LAZY You can specify the EAGER type to make sure hibernate fetches the collection.您可以指定EAGER类型以确保 hibernate 获取集合。

In your Show class you can change the fetchType to EAGER .在您的Show类中,您可以将 fetchType 更改为EAGER

@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;

You just need to annotate your resolver classes with @Transactional .你只需要用@Transactional注释你的解析器类。 Then, entities returned from repositories will be able to lazily fetch data.然后,从存储库返回的实体将能够懒惰地获取数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM