带有 graphql-spring 的 LazyInitializationException
LazyInitializationException with graphql-spring
我目前正在将我的 REST-Server 迁移到 GraphQL(至少部分)。大部分工作已经完成,但我偶然发现了这个我似乎无法解决的问题:OneToMany relationships in a graphql query, with 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());
}
}
如果我现在使用此 (shorthand) 查询查询端点:
{
shows {
id
name
competitions {
id
}
}
}
我得到:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: Show.competitions, could not initialize proxy -
no Session
现在我知道为什么会出现这个错误以及它意味着什么,但我真的不知道是否要为此应用修复程序。我不想让我的实体急切地获取所有关系,因为这会抵消 GraphQL 的一些优势。我可能需要寻找解决方案的任何想法?
谢谢!
我假设无论何时获取 Show 的对象,您都需要 的所有关联 Competition显示个对象。
默认情况下,实体中所有集合类型的提取类型都是LAZY。您可以指定 EAGER 类型以确保休眠提取集合。
在您的 Show class 中,您可以将 fetchType 更改为 EAGER.
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
我解决了它,我想应该更仔细地阅读 graphql-java-tools 库的文档。
除了解决基本查询的 GraphQLQueryResolver
之外,我的 Show
class 还需要一个 GraphQLResolver<T>
,它看起来像这样:
@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
class 中的复杂对象,并且仅在初始查询请求包含 Competition
对象时使用。新年快乐!
编辑 2019 年 7 月 31 日:我已经放弃了下面的解决方案。长 运行 事务很少是一个好主意,在这种情况下,一旦您扩展应用程序,它可能会导致问题。我们开始实施 DataLoaders 以异步方式批量查询。长 运行 事务与 DataLoader 的异步性质相结合可能导致死锁:https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715(有关更多信息,请参见上文和下文)。我不会删除下面的解决方案,因为它可能仍然是小型应用程序的良好起点 and/or 不需要任何批量查询的应用程序,但在这样做时请记住此评论。
编辑: 这里要求的是另一种使用自定义执行策略的解决方案。我正在使用 graphql-spring-boot-starter
和 graphql-java-tools
:
创建一个 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);
}
}
这会将查询的整个执行放在同一个事务中。我不知道这是否是最佳解决方案,并且它在错误处理方面也已经存在一些缺点,但是您不需要那样定义类型解析器。
请注意,如果这是唯一的 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
用于突变:
@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
您只需要用 @Transactional
注释您的解析器 类。然后,从存储库返回的实体将能够延迟获取数据。
对于任何对接受的答案感到困惑的人,您需要更改 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
关系检索比赛列表,而是在新事务中创建新查询,从而消除了问题。
不确定这是否是 hack,但解析器上的 @Transactional
对我不起作用,尽管这样做的逻辑确实有一定道理,但我显然不理解根本原因。
我的首选解决方案是在 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();
}
}
对我来说,使用 AsyncTransactionalExecutionStrategy
时出现异常,但工作不正确。例如。延迟初始化或应用程序级异常触发事务到仅回滚状态。 Spring 事务机制然后在策略 execute
的边界处启动仅回滚事务,导致 HttpRequestHandlerImpl
到 return 400 空响应。有关详细信息,请参阅 https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652。
对我有用的是使用 Instrumentation
将整个操作包装在一个事务中:https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a
我目前正在将我的 REST-Server 迁移到 GraphQL(至少部分)。大部分工作已经完成,但我偶然发现了这个我似乎无法解决的问题:OneToMany relationships in a graphql query, with 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());
}
}
如果我现在使用此 (shorthand) 查询查询端点:
{
shows {
id
name
competitions {
id
}
}
}
我得到:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Show.competitions, could not initialize proxy - no Session
现在我知道为什么会出现这个错误以及它意味着什么,但我真的不知道是否要为此应用修复程序。我不想让我的实体急切地获取所有关系,因为这会抵消 GraphQL 的一些优势。我可能需要寻找解决方案的任何想法? 谢谢!
我假设无论何时获取 Show 的对象,您都需要 的所有关联 Competition显示个对象。
默认情况下,实体中所有集合类型的提取类型都是LAZY。您可以指定 EAGER 类型以确保休眠提取集合。
在您的 Show class 中,您可以将 fetchType 更改为 EAGER.
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
我解决了它,我想应该更仔细地阅读 graphql-java-tools 库的文档。
除了解决基本查询的 GraphQLQueryResolver
之外,我的 Show
class 还需要一个 GraphQLResolver<T>
,它看起来像这样:
@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
class 中的复杂对象,并且仅在初始查询请求包含 Competition
对象时使用。新年快乐!
编辑 2019 年 7 月 31 日:我已经放弃了下面的解决方案。长 运行 事务很少是一个好主意,在这种情况下,一旦您扩展应用程序,它可能会导致问题。我们开始实施 DataLoaders 以异步方式批量查询。长 运行 事务与 DataLoader 的异步性质相结合可能导致死锁:https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715(有关更多信息,请参见上文和下文)。我不会删除下面的解决方案,因为它可能仍然是小型应用程序的良好起点 and/or 不需要任何批量查询的应用程序,但在这样做时请记住此评论。
编辑: 这里要求的是另一种使用自定义执行策略的解决方案。我正在使用 graphql-spring-boot-starter
和 graphql-java-tools
:
创建一个 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);
}
}
这会将查询的整个执行放在同一个事务中。我不知道这是否是最佳解决方案,并且它在错误处理方面也已经存在一些缺点,但是您不需要那样定义类型解析器。
请注意,如果这是唯一的 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
用于突变:
@Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
您只需要用 @Transactional
注释您的解析器 类。然后,从存储库返回的实体将能够延迟获取数据。
对于任何对接受的答案感到困惑的人,您需要更改 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
关系检索比赛列表,而是在新事务中创建新查询,从而消除了问题。
不确定这是否是 hack,但解析器上的 @Transactional
对我不起作用,尽管这样做的逻辑确实有一定道理,但我显然不理解根本原因。
我的首选解决方案是在 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();
}
}
对我来说,使用 AsyncTransactionalExecutionStrategy
时出现异常,但工作不正确。例如。延迟初始化或应用程序级异常触发事务到仅回滚状态。 Spring 事务机制然后在策略 execute
的边界处启动仅回滚事务,导致 HttpRequestHandlerImpl
到 return 400 空响应。有关详细信息,请参阅 https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652。
对我有用的是使用 Instrumentation
将整个操作包装在一个事务中:https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a