2013-07-25 25 views
17

我在大型应用程序中遇到EAGER关系问题。此应用程序中的某些实体与其他实体具有关联关系EAGER。这在某些功能上变成了“毒药”。忽略关系中的FetchType.EAGER

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

所以,我的问题:有没有办法做了具体的查询忽略了我返回实体EAGERs关联?

例子:当我有这个实体的人,我想,当我做一个查询,以找到一个人不把地址列表。

@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关联的同一张表。这将帮助我在少数情况下...

+0

http://stackoverflow.com/questions/10997321/how-to-override-fetchtype-eager-to-be-lazy-at-runtime?lq=1 –

+1

也许你最好重构。通常情况下,EAGER只能用于微不足道的关系。最好是让所有的东西都懒惰,然后在你真正知道你需要的时候获得额外的东西。 –

+0

嗨@尼古拉斯!这是更好的解决方案。可悲的是,它不可能重构所有实体,因为在应用程序中有很多影响。 – Dherik

回答

3

实际上从未尝试过这一点,但可能值得一试......假设会话工厂通过注射可在DAO层或任何其他手段可以实现类似的东西在(可能是新的)DAO方法:

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

嗨!好答案。我做了更多的研究,我认为你的建议是唯一的方法...朋友建议我看看FetchProfile:http://www.concretepage.com/hibernate/fetchprofile_hibernate_annotation.php – Dherik

+0

不幸的是,FetchProfile不适用于Hibernate 3.3 .0(我的应用程序的Hibernate版本)。我无法测试FetchProfile是否可以解决问题,即使在Hibernate 3.5.0(具有FetchProfile的第一个版本)中也是如此。 – Dherik

+3

根据hibernate文档,'FetchMode.LAZY'不推荐使用,它等同于'FetchMode.SELECT'。所以'FetchMode.LAZY'实际上会导致一个额外的SELECT查询来激发集合的加载。这并不真正加载收藏LAZYily。 –

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

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

将获取类型懒,但然后设置一个@BatchSize注解。由于hibernate通常使用单独的数据库查询来加载集合,因此这会保持该行为,但调整后的BatchSize会避免每个元素有1个查询(例如在循环中) - 前提是会话仍处于打开状态。

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

+0

这是为了表演!我们有很多EAGER的实体。您的解决方案不能直接回答,但对于解决EAGER无处不在的某些情况下性能问题非常好,就像我的情况。 – Dherik

2

默认情况下,如果Hibernate的HQL,Criteria和NativeSQL在域模型中映射为LAZY,那么EAGERly可以灵活地加载集合。

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

虽然我们有FetchMode.LAZY,我们可以在标准上设置,它已被弃用,它相当于FetchMode.SELECT。实际上,FetchMode.LAZY实际上导致引发额外的SELECT查询并仍然热切地加载集合。

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

我的方案,我有了实体的FetchType.EAGERFetchMode.JOIN一对多映射的集合森林实体。只加载森林实体不加载任何树木,我已经使用标量值Transformers.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();

我已经测试了上述简单的查询,它可能是工作检查,如果这种方式更适合复杂的案件,以及和适合您的所有用例。

希望知道是否有更好或更简单的方法来做到这一点,特别是没有标量和变形金刚。

+0

非常好。你的解决方案就像返回一个新的'VO':'选择新的MyVO(f.id,f.name)FROM [...]',但使用该实体。我需要测试这种方法是否可以使用返回的实体(在您的示例中为Forest)来处理我可以对一个“常规”实体执行的所有操作,例如使用它来与另一个实体建立关联。像:'treeNormalEntity.setForest(forestFromTransformer);'。 – Dherik

7

如果您使用的是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中的字段才会被提取。

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

+0

非常好。我在另一个评论中说我的休眠版本(3.3.0),但你的回答将帮助使用最新版本的同样问题的人。谢谢! – Dherik

+1

在实践中这不起作用,即使你没有将它们包含在EntityGraph中,Hibernate仍然会获取标记为EAGER的属性,请参阅:https://hibernate.atlassian.net/browse/HHH-8776此答案不能已经过测试,因为它无法按预期工作。 –

+0

试过这种方法,不起作用。 –