在JPA与Hibernate中,当需要多个表的字段时,如何避免join "爆炸"?

问题描述 投票:1回答:1

假设我在JpaRepository中使用了以下方法。

@EntityGraph(value = "Subject.allJoins", type = EntityGraphType.FETCH)
    @Query("select s from Subject s" + FIND_QUERY_WHERE)
    Page<Subject> findInProject(@Param("projectId") UUID projectId, <additional params>

正如你所看到的,我已经使用了一个EntityGraph来实现我所需要的连接,Hibernate生成的SQL查询如下(省略了大部分的where)。

select
    subject0_.id,
    <all kinds of fields including some duplicates>
from
    subject subject0_
    left outer join project project1_ on subject0_.project_id = project1_.id
    left outer join subject_property_value properties2_ on subject0_.id = properties2_.subject_id
    left outer join property_value propertyva3_ on properties2_.property_value_id = propertyva3_.id
    left outer join ingestion_id_mapping ingestedme4_ on subject0_.id = ingestedme4_.subject_id
where
    subject0_.project_id = '123'
order by
    subject0_.name asc

因为这里所有的联接都是将结果乘以行数作为联接的结果,所以即使科目总数只有几百行,结果集也会爆炸成几十万行。

请注意,我将会做一个投影,这样已经可以避免选择一些字段,但仍然需要联接。

我该怎么做才能优化呢?

请注意,我其实是需要所有的数据立即序列化到客户端的,所以只是让Hibernate通过获取模型实体,并对每个关联使用Getter方法,所花费的时间甚至比这个要长很多。

我现在的想法是,我必须对每一个单独的join用同样的where进行多次查询,之后再把结果合并成一个对象。如果我在后续的查询中因为原表中增加或删除了行而读到更多或更少的行,这并不是世界末日,因为我可以只取最小的subset of subject ids,然后从中做一个结果。

但是有没有比这更聪明或者更简单的方法呢?

java spring hibernate jpa jpql
1个回答
1
投票

我以一个足球俱乐部为例,它有一个国家,一个体育场和一个球员列表。

你的第一个查询应该只用于从数据库中过滤你想要的行。在这种情况下,你也可以获取1:1的关系,但不是1:n。

  • 过滤所有符合标准的俱乐部
  • 获取所有1:1的关系(每个俱乐部的国家& 体育场)。

然后,你可以为每个子列表做一个专门的.还是在我的例子中,你会选择每个球员的俱乐部在你提供的列表中作为查询的参数(是你的第一个查询的结果).查询将是类似于.的东西。

 String jpql = "select p from Player p where p.club in :clubs";

这样做,你也可以提供实体图来加载球员的属性,这在你进行分页的时候很好用(第一个查询的结果不会很重要)。

这种方法在Vlad Mihalcea那里有很好的描述。修复Hibernate MultipleBagFetchException的最佳方法。

我深深地建议你去看看。


1
投票

问题是,fetch-join对每个相关的实体表都做了一个子选择。相反,你应该只连接具有1:1关系的实体。当其他实体第一次被访问时,就会被获取。这样的结果是每个Subject只有一条记录,而对于不在初始选择中的每一个实体,都会有一个n行的选择。

如果子选择耗时太长,可以尝试将记录数最少的实体添加到选择中。


1
投票

这是一个完美的使用案例 Blaze-持久性实体视图.

我创建这个库的目的是为了让JPA模型和自定义接口或抽象类定义的模型之间能够轻松地进行映射,就像Spring Data Projections一样。这个想法是,你以你喜欢的方式定义你的目标结构(领域模型),并通过JPQL表达式将属性(getters)映射到实体模型。由于属性名被用作默认映射,你大多不需要显式映射,因为80%的用例是拥有实体模型子集的DTO。

有趣的部分是,你可以指定应该使用的获取策略。一个示例模型可以看起来像下面这样。

@EntityView(Subject.class)
public interface SubjectView {
    @IdMapping
    Integer getId();
    ProjectView getProject();
    @Mapping(fetch = SUBSELECT)
    Set<PropertyValueView> getProperties();
    Set<IngestionMappingView> getMappings();
}
@EntityView(Project.class)
public interface ProjectView {
    @IdMapping
    Integer getId();
    String getName();
}
@EntityView(PropertyValue.class)
public interface PropertyValueView {
    @IdMapping
    Integer getId();
    String getName();
}
@EntityView(IngestionMapping.class)
public interface IngestionMappingView {
    @IdMapping
    Integer getId();
    String getName();
}

查询是将实体视图应用于查询的问题,最简单的就是通过id进行查询。

SubjectView p = entityViewManager.find(entityManager, SubjectView.class, id);

Spring Data集成允许你使用它,几乎就像Spring Data Projections一样。https:/persistence.blazebit.comdocumentationentity-viewmanualen_USindex.html#spring-data-features。 即有一个类似于以下的仓库

@Repository
public interface SubjectRepository {
    Page<SubjectView> findByProjectId(@Param("projectId") UUID projectId, <additional params>);
}

你可以阅读更多关于支持的取值策略的信息,请参见 实体视图文档我一般会建议你使用 MULTISET 如果可能的话,获取策略通常提供最佳性能。

© www.soinside.com 2019 - 2024. All rights reserved.