忽略关系中的FetchType.EAGER

时间:2013-07-25 01:04:07

标签: java hibernate jpa hql jpql

我在大型应用程序中遇到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关联的情况下映射同一个表。在少数情况下,这对我有帮助......

7 个答案:

答案 0 :(得分:9)

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

}

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

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中的字段。

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:3)

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

关于反过来,即,将集合映射为域模型中的EAGER并尝试使用HQL,Criteria或NativeSQL进行LAZY加载,我无法找到一种直接或简单的方式我们可以使用HQL / Criteria / NativeSQL来满足这个要求。

虽然我们可以在条件上设置FetchMode.LAZY,但不推荐使用它,它等同于FetchMode.SELECT。实际上,FetchMode.LAZY实际上会导致触发额外的SELECT查询并仍然急切地加载集合。

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

在我的场景中,我有一个 Forest 实体,其中包含实体的集合,其中 oneToMany 映射为FetchType.EAGERFetchMode.JOIN。要仅加载 Forest 实体而不加载任何树,我使用了以下HQL查询标量值 Transformers.aliasToBean(...)。这适用于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();

我已经测试过上面的简单查询,它可能正在检查这是否适用于复杂的情况并且适合所有用例。

很想知道是否有更好或更简单的方法,特别是没有标量和变形金刚。

答案 3 :(得分:2)

  1. 是的,您可以将两个实体类映射到同一个表,这是一个有效的解决方法。但是,请注意两种类型的实例同时存在于同一持久性上下文中的情况,因为一种类型的实体实例的更新不会反映到另一种类型的同一实例中。此外,此类实体的二级缓存变得更加复杂。
  2. Fetch profiles也很有意思,但目前非常有限,你可以只使用连接式获取配置文件覆盖默认的获取计划/策略(你可以使懒惰的关联渴望,但不是副反之亦然)。但是,您可以使用此trick来反转该行为:默认情况下使关联变为惰性,并默认为所有会话/事务启用配置文件。然后禁用您希望延迟加载的交易中的个人资料。

答案 4 :(得分:1)

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

将fetch类型设置为lazy,但随后设置@BatchSize注释。由于hibernate通常使用单独的数据库查询来加载集合,这可以维护该行为,但是通过调整BatchSize,您可以避免每个元素1个查询(例如,在循环中) - 只要您的会话仍处于打开状态。

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

答案 5 :(得分:0)

这些年来,在Hibernate上尚无法覆盖EAGER映射。来自latest Hibernate documentation(5.3.10。最终):

  

尽管JPA标准指定您可以覆盖EAGER   使用javax.persistence.fetchgraph在运行时获取关联   提示,当前,Hibernate未实现此功能,因此,EAGER   不能懒洋洋地获取关联。有关更多信息,请查看   HHH-8776吉拉问题。

     

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

     

因此,最好使用LAZY关联,并且仅获取   他们渴望每个查询。

并且:

  

无法在每个查询中覆盖EAGER提取策略   基础,因此即使您仍然可以获取关联   不需要它更多,如果您忘记加入EAGER协会   在JPQL查询中,Hibernate将使用辅助数据库对其进行初始化   语句,这又可能导致N + 1个查询问题。

答案 6 :(得分: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);

当我尝试这种方式时,它是可行的。

相关问题