34

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

现在我的团队需要优化这个功能,但我们不能将 fetch 类型更改为 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 关联的情况下映射同一个表。这将在某些情况下帮助我......

4

7 回答 7

14

如果您使用的是 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);

更多关于这个主题的信息可以在这里找到。

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

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

于 2015-12-31T12:57:53.937 回答
8

更新(2020 年 9 月 6 日):

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

原始答案

经过这么多年,在 Hibernate 上覆盖 EAGER 映射是不可能的。最新的 Hibernate 文档(5.3.10.Final):

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

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

出于这个原因,最好使用 LAZY 关联,并且只在每个查询的基础上急切地获取它们。

和:

EAGER 获取策略不能在每个查询的基础上被覆盖,因此即使您不需要它,也总是会检索关联。此外,如果您忘记在 JPQL 查询中加入 EAGER 关联,Hibernate 将使用辅助语句对其进行初始化,这反过来可能导致 N+1 查询问题。

于 2018-12-20T10:54:25.297 回答
3

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

关于另一种方式,即将集合映射为域模型中的 EAGER 并尝试使用 HQL、Criteria 或 NativeSQL 进行 LAZY 加载,我找不到一个直接或更简单的方法来解决这个问题HQL/标准/NativeSQL。

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

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

在我的场景中,我有一个Forest实体,它包含一组Tree实体,其中oneToMany映射为FetchType.EAGERFetchMode.JOIN。为了只加载Forest实体而不加载任何树,我使用了以下带有标量值Transformers.aliasToBean(...)的 HQL 查询。只要使用标量和 aliasToBean Transformer,这适用于 Criteria 和 Native SQL。

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();

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

很想知道是否有更好或更简单的方法来做这件事,尤其是没有标量和变形金刚。

于 2015-12-31T12:34:19.507 回答
2

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

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;
于 2013-07-25T04:16:47.313 回答
2
  1. 是的,您可以将两个实体类映射到同一个表,这是一种有效的解决方法。但是,请注意两种类型的实例同时存在于同一持久性上下文中的情况,因为一种类型的实体实例的更新不会反映到另一种类型的同一实例中。此外,此类实体的二级缓存变得更加复杂。
  2. 提取配置文件也很有趣,但目前非常有限,您只能使用连接样式的提取配置文件覆盖默认的提取计划/策略(您可以使惰性关联急切,但反之则不行)。但是,您可以使用此技巧来反转该行为:默认情况下使关联变得惰性,并默认为所有会话/事务启用配置文件。然后在您想要延迟加载的事务中禁用配置文件。
于 2015-12-30T21:55:36.310 回答
1

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

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

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

于 2015-12-31T08:38:39.500 回答
0

首先,您不能覆盖 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);

当我尝试这种方式时,它奏效了。

于 2020-07-24T13:33:55.683 回答