我目前正在将我的REST服务器迁移到GraphQL(至少部分)。大部分的工作已经完成,但我偶然发现了这个我似乎无法解决的问题:Graphql查询中的OneToMany关系,使用FetchType. LAZY。
我使用:https://github.com/graphql-java/graphql-spring-boot和https://github.com/graphql-java/graphql-java-tools进行集成。
下面是一个示例:
实体:
@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;
}
字符串
架构:
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,cannot initialize proxy - no Session
现在我知道为什么会发生这个错误,这意味着什么,但我真的不知道我们要应用这个修复程序。我不想让我的实体急切地获取所有关系,因为这会否定GraphQL的一些优点。有什么想法我可能需要寻找一个解决方案?谢谢你,谢谢
7条答案
按热度按时间ni65a41a1#
我的首选解决方案是打开事务,直到Servlet发送其响应。通过这个小的代码更改,您的LazyLoad将正常工作:
字符串
wgx48brx2#
我解决了这个问题,我想我应该更仔细地阅读graphql-java-tools库的文档。除了解决基本查询的
GraphQLQueryResolver
之外,我还需要一个GraphQLResolver<T>
用于我的Show
类,它看起来像这样:字符串
这告诉库如何解析
Show
类中的复杂对象,并且仅在最初的查询请求包含Competition
对象时使用。新年快乐!编辑31.07.2019:从那以后,我就离开了下面的解决方案。长时间运行的事务很少是一个好主意,在这种情况下,一旦扩展应用程序,它可能会导致问题。我们开始实现DataLoaders,以异步方式批处理查询。长时间运行的事务与DataLoaders的异步特性相结合可能导致死锁:https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715(以上和以下获取更多信息)。我不会删除下面的解决方案,因为它可能仍然是小型应用程序和/或不需要任何批量查询的应用程序的良好起点,但请在这样做时记住这一点。
**编辑:**此处请求的是使用自定义执行策略的另一种解决方案。我正在使用
graphql-spring-boot-starter
和graphql-java-tools
:创建一个
ExecutionStrategy
类型的Bean来处理事务,如下所示:型
这将查询的整个执行放在同一个事务中。我不知道这是否是最佳的解决方案,而且它在错误处理方面也有一些缺点,但您不需要以这种方式定义类型解析器。
请注意,如果这是唯一存在的
ExecutionStrategy
Bean,则它也将用于突变,这与Bean名称可能暗示的相反。参见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以获取参考。为了避免这种情况,定义另一个ExecutionStrategy
用于突变:型
rnmwe5a23#
对于任何对接受的答案感到困惑的人,那么你需要改变java实体以包含双向关系,并确保你使用helper方法添加一个
Competition
,否则很容易忘记正确设置关系。字符串
公认答案背后的一般直觉是:
graphql解析器
ShowResolver
将打开一个事务以获取节目列表,但一旦完成,它将关闭该事务。然后
competitions
的嵌套graphql查询将尝试调用getCompetition()
上的每个Show
示例,因为事务已经关闭,这将抛出LazyInitializationException
。型
接受的答案基本上是绕过通过
OneToMany
关系检索竞争列表,而是在新事务中创建新查询,从而消除了问题。不确定这是否是一个黑客,但
@Transactional
解析器不为我工作,虽然这样做的逻辑确实有一些意义,但我显然不明白根本原因。kx1ctssn4#
对我来说,使用
AsyncTransactionalExecutionStrategy
工作不正确,有异常。例如,延迟初始化或应用程序级异常触发事务至仅回滚状态。Spring事务机制随后在策略execute
的边界抛出了仅回滚事务,导致HttpRequestHandlerImpl
返回400个空响应。有关详细信息,请参阅https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250和https://github.com/graphql-java/graphql-java/issues/1652。对我来说有效的是使用
Instrumentation
将整个操作 Package 在一个事务中:https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6aruarlubt5#
正如Oleg所指出的,使用
AsyncTransactionalExecutionStrategy
的错误处理在嵌套事务中被破坏了。由于他的答案中的URL不再起作用,以下是我如何解决的。
首先,让我们来看看我想通过GraphQL响应正确处理的一些异常
字符串
然后我定义了错误响应
的数据
然后我创建了
DataFetcherExceptionHandler
来将其转换为适当的GraphQL响应型
最后在
AsyncTransactionalExecutionStrategy
中使用了它,还使用了@Transactional
注解来允许惰性解析器。型
现在,如果你在某个地方
throw new UserFriendlyException("Email already exists");
,你最终会得到这样的好响应型
给定分类
USER_FRIENDLY_ERROR
,如果您使UserFriendlyException
消息用户友好,则可以直接将其显示给用户:)但是,如果你在某个方法中使用
throw new UserFriendlyException("Email already exists");
注解@Transactional
,你最终会得到空响应和HTTP 400状态。将
@Transactional(propagation = Propagation.REQUIRES_NEW)
添加到Mutation
可解决此问题型
tzdcorbm6#
我假设,每当你获取一个 Show 对象时,你想要所有与 Show 对象相关的 Competition。
默认情况下,实体中所有集合类型的获取类型为LAZY。您可以指定EAGER类型以确保hibernate获取集合。
在 Show 类中,可以将fetchType更改为EAGER。
字符串
hpcdzsge7#
您只需要用
@Transactional
注解解析器类。然后,从存储库返回的实体将能够延迟地获取数据。