忽略关系中的 FetchType.EAGER

Posted

技术标签:

【中文标题】忽略关系中的 FetchType.EAGER【英文标题】:Ignore a FetchType.EAGER in a relationship 【发布时间】:2013-07-24 17:06:22 【问题描述】:

我对大型应用程序中的 EAGER 关系有疑问。此应用程序中的某些实体与其他实体有EAGER 关联。这在某些功能中成为“毒药”。

现在我的团队需要优化此功能,但我们无法将提取类型更改为 LAZY,因为我们需要重构整个应用程序。

所以,我的问题是:有没有办法在我返回的实体中忽略 EAGERs 关联来执行特定查询?

示例:当我有这个实体Person 时,我不想在查询Person 时带上地址列表。

@Entity
public class Person 

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;



Query query = EntityManager.createQuery("FROM Person person");
//list of person without the address list! But how???
List<Person> resultList = query.getResultList();

谢谢!

更新

我发现的唯一方法是不返回实体,只返回实体的一些字段。但我想找到一个可以返回实体的解决方案(在我的示例中,Person 实体)。

我在考虑是否可以在 Hibernate 中映射同一张表两次。这样,我可以在没有 EAGER 关联的情况下映射同一个表。这将在某些情况下对我有所帮助...

【问题讨论】:

***.com/questions/10997321/… 也许你最好重构一下。通常,EAGER 只会用于琐碎的关系,如果那样的话。最好让一切都变得懒惰,然后在您真正知道自己需要它时获得额外的东西。 嗨@Nicholas!这是更好的解决方案。遗憾的是,不可能重构所有实体,因为这会对应用程序产生很大影响。 【参考方案1】:

如果您使用的是 JPA 2.1 (Hibernate 4.3+),您可以使用 @NamedEntityGraph 实现您想要的。

基本上,您会像这样注释您的实体:

@Entity
@NamedEntityGraph(name = "Persons.noAddress")
public class Person 

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;


然后使用提示获取没有地址的人,如下所示:

EntityGraph graph = this.em.getEntityGraph("Persons.noAddress");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

return this.em.findAll(Person.class, hints);

有关该主题的更多信息,请访问here。

当您使用获取图时,只会急切地获取您放在 @NamedEntityGraph 中的字段。

在没有提示的情况下执行的所有现有查询将保持不变。

【讨论】:

实际上这不起作用,Hibernate 仍会获取标记为 EAGER 的属性,即使您没有将它们包含在 EntityGraph 中,请参阅:hibernate.atlassian.net/browse/HHH-8776 此答案无法经过测试,因为它没有按预期工作。 Hibernate 现在可以在 5.4.22 中正确实现 EntityGraphs(参见 hibernate.atlassian.net/browse/HHH-8776?focusedCommentId=106092 和 hibernate.atlassian.net/browse/HHH-8776?focusedCommentId=107330)。我已经用 5.4.27 成功测试过,它没有加载静态标记为 EAGER 的字段。【参考方案2】:

更新(09/06/2020):

issue 已在 5.4.11 版本上解决。我现在无法测试,但预计图中未包含的 JPA 实体图属性应保持卸载状态,即使它们被声明为 EAGER

原答案

这么多年过去了,在 Hibernate 上覆盖 EAGER 映射还不可能。来自latest Hibernate documentation(5.3.10.Final):

尽管 JPA 标准规定您可以覆盖 EAGER 使用 javax.persistence.fetchgraph 在运行时获取关联 提示,目前,Hibernate 没有实现这个功能,所以 EAGER 不能延迟获取关联。有关更多信息,请查看 HHH-8776 Jira 问题。

执行 JPQL 查询时,如果省略 EAGER 关联, Hibernate 将为所需的每个关联发出辅助选择 被急切地获取,这可能导致 dto N+1 查询问题。

因此,最好使用 LAZY 关联,并且只获取 他们热切地在每个查询的基础上。

还有:

EAGER 获取策略不能在每个查询上被覆盖 基础,所以即使你 不需要它。更多,如果你忘记 JOIN FETCH 一个 EAGER 协会 在 JPQL 查询中,Hibernate 将使用辅助初始化它 语句,这反过来又会导致 N+1 查询问题。

【讨论】:

【参考方案3】:

默认情况下,如果集合在域模型中被映射为 LAZY,Hibernate 的 HQL、Criteria 和 NativeSQL 为我们提供了灵活加载集合的灵活性。

反过来说,即在域模型中将集合映射为 EAGER 并尝试使用 HQL、Criteria 或 NativeSQL 进行 LAZY 加载,我找不到一个直接或更简单的方法来实现使用 HQL/Criteria/NativeSQL 来满足这一点。

虽然我们可以在 Criteria 上设置 FetchMode.LAZY,但它已被弃用,它等同于 FetchMode.SELECT。并且有效地 FetchMode.LAZY 实际上会触发一个额外的 SELECT 查询,并且仍然急切地加载集合。

但是,如果我们想延迟加载映射为 EAGER 的集合,您可以尝试以下解决方案:使 HQL/Criteria/NativeSQL 返回标量值并使用 ResultTransformer(Transformers.aliasToBean(..)) 返回实体对象(或DTO) 具有从标量值填充的字段。

在我的场景中,我有一个 Forest 实体,其中包含 Tree 实体的集合,其中 oneToMany 映射为 FetchType.EAGER 和 @987654325 @。为了只加载 Forest 实体而不加载任何树,我使用了以下带有 scalar valuesTransformers.aliasToBean(...) 的 HQL 查询>。这适用于 Criteria 和 Native SQL,只要使用了标量和 aliasToBean Transformer。

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

我已经测试了上述简单查询,它可能正在检查这是否也适用于复杂案例并适合您的所有用例。

很想知道是否有更好或更简单的方法,尤其是在没有标量和变形金刚的情况下。

【讨论】:

【参考方案4】:

从未真正尝试过,但可能值得一试...假设会话工厂通过注入或任何其他方式在 DAO 层可用,您可以在(可能是新的)DAO 方法中实现类似的东西:

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;

【讨论】:

根据休眠文档,FetchMode.LAZY 已被弃用,它等同于FetchMode.SELECT。所以FetchMode.LAZY 实际上会触发一个额外的 SELECT 查询来急切地加载集合。这并没有真正加载集合 LAZYily。【参考方案5】:
    是的,您可以将两个实体类映射到同一个表,这是一种有效的解决方法。但是,请注意两种类型的实例同时存在于同一持久性上下文中的情况,因为一种类型的实体实例的更新不会反映到另一种类型的同一实例中。此外,此类实体的二级缓存变得更加复杂。 Fetch profiles 也很有趣,但目前非常有限,您只能使用连接样式的提取配置文件覆盖默认的提取计划/策略(您可以使惰性关联急切,但反之则不行)。但是,您可以使用此trick 来反转该行为:默认情况下使关联变得惰性,并默认为所有会话/事务启用配置文件。然后禁用要延迟加载的事务中的配置文件。

【讨论】:

【参考方案6】:

你没有说为什么你不能从渴望变成懒惰。然而,我的印象是这是出于性能原因,所以我想质疑这个假设。如果是这种情况,请考虑以下事项。我意识到这个答案并没有严格回答你的问题,它违反了没有延迟加载的条件,但这里有一个替代方案,它反映了我的开发团队对我认为相同的潜在问题的方法。

将获取类型设置为惰性,然后设置@BatchSize 注释。由于 hibernate 通常使用单独的 DB 查询来加载集合,这会保持这种行为,但通过调整 BatchSize,您可以避免每个元素 1 个查询(例如,在循环中)——前提是您的会话仍然打开。

OneToOne 关系的 backref 的行为有点有趣(关系的引用端 - 没有外键的端)。但是对于 OneToOne 的另一边,对于 OneToMany 和 ManyToOne,这给出了我认为您可能想要的最终结果:您仅在实际需要时才查询表,但避免了每条记录的延迟加载,而且您不需要必须明确配置每个用例。这意味着在您执行延迟加载的情况下,您的性​​能将保持可比性,但如果您实际上不需要它,则不会发生此加载。

【讨论】:

这是为了性能!我们有很多 EAGER 实体。您的解决方案不能直接回答,但在 EAGER 无处不在的某些情况下(例如我的情况),它是解决性能问题的一个很好的选择。【参考方案7】:

首先,您不能覆盖 FetchType.EAGER。但是你可以使用不同的方式。

创建一个新的额外 PersonVo 类。

public class PersonVo 

private String name;

public PersonVo(String name)
   this.name = name;


// getter and setter


之后,您可以在 JPA 存储库界面中编写如下查询。此查询将返回不带地址的 PersonVo 类。 com.bla.bla.PersonVo 是您的 PersonVo 类包路径。

@Query("SELECT NEW com.bla.bla.PersonVo(p.name) from Person p where p.id in :idList")
List<PersonVo> findAllById(@Param("idList") List<Long> idList);

当我尝试这种方式时,它成功了。

【讨论】:

以上是关于忽略关系中的 FetchType.EAGER的主要内容,如果未能解决你的问题,请参考以下文章

FetchType.LAZY和FetchType.EAGER什么区别

@LazyCollection(LazyCollectionOption.FALSE) 和 @OneToMany(fetch = FetchType.EAGER) 之间的区别

hibernate FetchType理解

EclipseLink - 在运行时定义 FetchType

hibernate的反向生成改懒加载的地方

@ManyToMany 关系不保存