我目前正在将我的REST服务器迁移到GraphQL(至少部分)。大部分工作已经完成,但我偶然发现了这个问题,我似乎无法解决:在Graphql查询中使用FetchType.LAZY的OneToMany关系。
我正在使用: 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
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的一些优点。我可能需要寻找解决方案的任何想法? 谢谢!
答案 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-starter
和graphql-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/250和https://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
注释您的解析程序类。然后,从存储库返回的实体将能够懒惰地获取数据。