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

Stu*_*uck 18 java postgresql hibernate jpa entitygraph

给定以下域模型,我想加载所有Answers,包括它们的Values 和它们各自的子子级,并将其放入 an 中AnswerDTO,然后转换为 JSON。我有一个可行的解决方案,但它遇到了 N+1 问题,我想通过使用临时@EntityGraph. 所有关联都已配置LAZY

在此处输入图片说明

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

@EntityGraph在该Repository方法上使用临时方法,我可以确保预取值以防止Answer->Value关联中出现 N+1 。虽然我的结果很好,但由于延迟加载s的selected关联,还有另一个 N+1 问题MCValue

使用这个

@EntityGraph(attributePaths = {"value.selected"})
Run Code Online (Sandbox Code Playgroud)

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

Unable to locate Attribute  with the the given name [selected] on this ManagedType [x.model.Value];
Run Code Online (Sandbox Code Playgroud)

我如何告诉 JPA 仅selected在值为 a 的情况下尝试获取关联MCValue?我需要类似的东西optionalAttributePaths

Tho*_*sen 10

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

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

第一个查询MCValue使用 anEntityGraph获取实体以获取由selected属性映射的关联。在那个查询之后,这些实体然后被存储在 Hibernate 的第一级缓存/持久化上下文中。Hibernate 将在处理第二个查询的结果时使用它们。

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

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

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

因为我们已经获取了所有MCValue具有关联selected实体的实体,所以我们现在获取Answer具有初始化value关联的实体。如果关联包含一个MCValue实体,它的selected关联也将被初始化。


Chr*_*kov 8

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

@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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

它将生成一个 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
Run Code Online (Sandbox Code Playgroud)