Java JPA继承@EntityGraph包含子类的可选关联
给定以下域模型,我希望加载所有Java JPA继承@EntityGraph包含子类的可选关联,java,postgresql,hibernate,jpa,entitygraph,Java,Postgresql,Hibernate,Jpa,Entitygraph,给定以下域模型,我希望加载所有Answers,包括它们的值和各自的子项,并将其放入AnswerDTO中,然后转换为JSON。我有一个可行的解决方案,但它有一个N+1问题,我想通过使用一个特别的@EntityGraph来解决这个问题。所有关联都已配置LAZY 失败,因为所选字段当然只是部分值实体的一部分: Unable to locate Attribute with the the given name [selected] on this ManagedType [x.model.Valu
Answer
s,包括它们的值和各自的子项,并将其放入AnswerDTO
中,然后转换为JSON。我有一个可行的解决方案,但它有一个N+1问题,我想通过使用一个特别的@EntityGraph
来解决这个问题。所有关联都已配置LAZY
失败,因为所选
字段当然只是部分值
实体的一部分:
Unable to locate Attribute with the the given name [selected] on this ManagedType [x.model.Value];
如果值是MCValue
,我如何告诉JPA仅尝试获取所选的关联?我需要类似于optionalAttributePath
的东西,我不知道Spring数据在那里做什么,但要做到这一点,通常必须使用TREAT
操作符才能访问子关联,但该操作符的实现非常有缺陷。
Hibernate支持隐式子类型属性访问,这正是您在这里需要的,但显然Spring数据无法正确处理这一点。我可以建议您看看,一个在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();
}
我的最新项目使用了GraphQL(对我来说是第一次),我们在N+1查询方面遇到了一个大问题,并试图优化查询,以便在需要时只连接表。我发现这是不可替代的。它扩展了JpaRepository
,并添加了将实体图传递给查询的方法。然后,您可以在运行时构建动态实体图,以便仅为所需的数据添加左连接
我们的数据流如下所示:
接收GraphQL请求
解析GraphQL请求并转换为查询中的实体图节点列表
从发现的节点创建实体图,并传递到存储库中执行
为了解决不将无效节点包括到实体图中的问题(例如,graphql中的\uuu typename
),我创建了一个实用程序类来处理实体图的生成。调用类传入它为其生成图形的类名,然后根据ORM维护的元模型验证图形中的每个节点。如果节点不在模型中,则会将其从图形节点列表中删除。(此检查需要是递归的,并检查每个子项)
在找到这一点之前,我尝试了Spring JPA/Hibernate文档中推荐的投影和所有其他替代方案,但似乎没有什么能优雅地解决问题,或者至少在您的评论之后编辑了大量额外的代码:
很抱歉,我没有在第一轮中理解您的问题,您的问题发生在spring data启动时,而不仅仅是在您尝试调用findAll()时
因此,您现在可以浏览完整的示例,可以从my github中获取:
您可以轻松复制并修复此项目中的问题
实际上,Spring数据和hibernate在默认情况下无法确定“选定”图形,您需要指定收集选定选项的方式
因此,首先,您必须声明类的NamedEntityGraphsAnswer
如您所见,对于类答案的属性值,有两个名称dentityGraph
- 所有值的第一个与负载没有特定关系
- 第二个用于特定的多选值。如果删除此项,则会复制异常
其次,如果要获取类型为LAZY的数据,则需要处于事务上下文answerRepository.findAll()
@实体
@表(name=“answer”)
@姓名识别图({
@姓名识别图(
name=“graph.Answer”,
attributeNodes=@NamedAttributeNode(value=“value”)
),
@姓名识别图(
name=“graph.AnswerMultichoice”,
attributeNodes=@NamedAttributeNode(value=“value”),
子图={
@命名子图(
name=“graph.AnswerMultichoice.selected”,
属性节点={
@NamedAttributeNode(“选定”)
}
)
}
)
}
)
公开课答案
{
@身份证
@GeneratedValue(策略=GenerationType.IDENTITY)
@列(updateable=false,nullable=false)
私有int-id;
@OneToOne(级联=级联类型.ALL)
@JoinColumn(name=“value\u id”,referencedColumnName=“id”)
私人价值;
// ..
}
只有当关联属性是超类的一部分并且也是所有子类的一部分时,才能使用。否则,EntityGraph
将始终失败,并出现当前获得的异常
避免N+1选择问题的最佳方法是将查询分为两个查询:
第一个查询使用EntityGraph
获取MCValue
实体,以获取所选属性映射的关联。在该查询之后,这些实体将存储在Hibernate的一级缓存/持久性上下文中。Hibernate将在处理第二个查询的结果时使用它们
@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();
因为我们已经获取了所有MCValue
实体以及相关的所选的实体,所以现在我们获得了应答
实体以及初始化的值
关联。如果关联包含一个MCValue
实体,那么它所选择的关联也将被初始化。Hmm感谢您的提示
@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();
}
@Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
List<AnswerDTO> findAll();
}
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
@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();
@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();