JPA 继承@EntityGraph包括子类的可选关联

2022-09-02 13:30:43

给定以下域模型,我想加载所有 s,包括它们的 s 和各自的子子级,并将其放入 a 中,然后转换为 JSON。我有一个有效的解决方案,但它遭受了N + 1问题,我想通过使用临时.所有关联都已配置。AnswerValueAnswerDTO@EntityGraphLAZY

enter image description here

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

使用ad-hoc on the method,我可以确保预先获取值以防止关联上的N + 1。虽然我的结果很好,但还有另一个N + 1问题,因为延迟加载s的关联。@EntityGraphRepositoryAnswer->ValueselectedMCValue

使用这个

@EntityGraph(attributePaths = {"value.selected"})

失败,因为该字段当然只是某些实体的一部分:selectedValue

Unable to locate Attribute  with the the given name [selected] on this ManagedType [x.model.Value];

我怎么能告诉JPA只在值为a的情况下尝试获取关联?我需要类似的东西。selectedMCValueoptionalAttributePaths


答案 1

仅当关联属性是超类的一部分并且也是所有子类的一部分时,才能使用 EntityGraph。否则,将始终以您当前获得的失败。EntityGraphException

避免 N+1 选择问题的最佳方法是将查询拆分为 2 个查询:

第一个查询使用 来提取由属性映射的关联来提取实体。在该查询之后,这些实体将存储在Hibernate的第一级缓存/持久性上下文中。休眠将在处理第二次查询的结果时使用它们。MCValueEntityGraphselected

@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();

然后,第 2 个查询提取实体,并使用 和 也提取关联的实体。对于每个实体,Hibernate 将实例化特定的子类,并检查第一级缓存是否已经包含该类和主键组合的对象。如果是这种情况,Hibernate 将使用第一级缓存中的对象,而不是查询返回的数据。AnswerEntityGraphValueValue

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

由于我们已经获取了具有关联实体的所有实体,因此我们现在获取具有初始化关联的实体。如果关联包含实体,则其关联也将被初始化。MCValueselectedAnswervalueMCValueselected


答案 2

我不知道Spring-Data在那里做什么,但要做到这一点,你通常必须使用运算符才能访问子关联,但该运算符的实现非常错误。Hibernate支持隐式子类型属性访问,这是您在这里需要的,但显然Spring-Data无法正确处理这个问题。我可以建议你看看Blaze-Persistence Entity-Views,这是一个在JPA之上工作的库,它允许你根据实体模型映射任意结构。您可以以类型安全的方式映射 DTO 模型,也可以映射继承结构。您的用例的实体视图可能如下所示TREAT

@EntityView(Answer.class)
interface AnswerDTO {
  @IdMapping
  Long getId();
  ValueDTO getValue();
}
@EntityView(Value.class)
@EntityViewInheritance
interface ValueDTO {
  @IdMapping
  Long getId();
}
@EntityView(TextValue.class)
interface TextValueDTO extends ValueDTO {
  String getText();
}
@EntityView(RatingValue.class)
interface RatingValueDTO extends ValueDTO {
  int getRating();
}
@EntityView(MCValue.class)
interface TextValueDTO extends ValueDTO {
  @Mapping("selected.id")
  Set<Long> getOption();
}

通过Blaze-Persistence提供的spring数据集成,您可以定义这样的存储库并直接使用结果

@Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
  List<AnswerDTO> findAll();
}

它将生成一个 HQL 查询,该查询仅选择您在 其中映射的内容如下所示。AnswerDTO

SELECT
  a.id, 
  v.id,
  TYPE(v), 
  CASE WHEN TYPE(v) = TextValue THEN v.text END,
  CASE WHEN TYPE(v) = RatingValue THEN v.rating END,
  CASE WHEN TYPE(v) = MCValue THEN s.id END
FROM Answer a
LEFT JOIN a.value v
LEFT JOIN v.selected s

推荐