使用graphql-spring的LazyInitializationException

时间:2017-12-30 20:25:12

标签: spring hibernate spring-boot graphql graphql-java

我目前正在将我的REST服务器迁移到GraphQL(至少部分)。大部分工作已经完成,但我偶然发现了这个问题,我似乎无法解决:在Graphql查询中使用FetchType.LAZY的OneToMany关系。

我正在使用: https://github.com/graphql-java/graphql-spring-boothttps://github.com/graphql-java/graphql-java-tools用于集成。

以下是一个例子:

实体:

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

   @OneToMany
   private List<Competition> competition;
}

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

架构:

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

type Competition {
    id: ID!
    name: String
}

extend type Query {
    shows : [Show]
}

解析器:

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

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

如果我现在使用此(简写)查询查询端点:

{
  shows {
    id
    name
    competitions {
      id
    }
  }
}

我得到:

  

org.hibernate.LazyInitializationException:懒得初始化   角色集合:Show.competitions,无法初始化代理 -   没有会话

现在我知道为什么会发生这个错误以及它意味着什么,但我真的不知道应该为此修复。我不想让我的热衷于急切地获取所有关系,因为这会否定GraphQL的一些优点。我可能需要寻找解决方案的任何想法? 谢谢!

6 个答案:

答案 0 :(得分:5)

我解决了它,我应该更仔细地阅读graphql-java-tools库的文档。 在解决基本查询的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()));
    }
}

这告诉库如何解析Show类中的复杂对象,并且仅在最初查询请求包含Competition对象时使用。新年快乐!

编辑:根据要求,这是另一种使用自定义执行策略的解决方案。我正在使用graphql-spring-boot-startergraphql-java-tools

我首先定义一个如下的GraphQL配置:

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

AsyncTransactionalExecutionStrategy的定义如下:

@Service
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {

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

这将查询的整个执行放在同一个事务中。我不知道这是否是最优化的解决方案,并且它在错误处理方面也有一些缺点,但您不需要这样定义类型解析器。

答案 1 :(得分:2)

对于对接受的答案感到困惑的任何人,那么您都需要更改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;
}

接受的答案的一般直觉是:

graphql解析器ShowResolver将打开一个事务以获取显示列表,但是一旦完成操作,它将关闭该事务。

然后,针对competitions的嵌套graphql查询将尝试在从先前查询中检索到的每个getCompetition()实例上调用Show,这将引发LazyInitializationException,因为事务已被执行。关闭。

{
  shows {
    id
    name
    competitions {
      id
    }
  }
}

接受的答案本质上是 绕过通过OneToMany关系检索比赛列表,而是在新交易中创建新查询,从而消除了问题。

不确定这是否是黑客,但对解析器的@Transactional对我不起作用,尽管这样做的逻辑确实有些道理,但我显然不了解根本原因。

答案 2 :(得分:2)

我首选的解决方案是在Servlet发送响应之前打开事务。通过此小代码更改,您的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();
  }

}

答案 3 :(得分:0)

对我来说,使用AsyncTransactionalExecutionStrategy可以正常工作,但有例外。例如。惰性初始化或应用程序级异常将事务触发为仅回滚状态。然后,Spring事务机制在策略execute的边界处引发了仅回滚事务,从而导致HttpRequestHandlerImpl返回400个空响应。有关更多详细信息,请参见https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250https://github.com/graphql-java/graphql-java/issues/1652

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

答案 4 :(得分:-2)

我假设每当你获取 Show 的对象时,你想要 Show 对象的所有相关竞争

默认情况下,实体中所有集合类型的获取类型为 LAZY 。您可以指定 EAGER 类型以确保hibernate获取集合。

Show 课程中,您可以将fetchType更改为 EAGER

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

答案 5 :(得分:-2)

您只需要使用@Transactional注释您的解析程序类。然后,从存储库返回的实体将能够懒惰地获取数据。