忽略关系中的FetchType.EAGER
我在大型应用程序中遇到EAGER关系问题。 此应用程序中的某些实体与其他实体具有EAGER
关联。 这在一些function中变成了“毒药”。
现在我的团队需要优化这些function,但我们无法将获取类型更改为LAZY ,因为我们需要重构整个应用程序。
所以,我的问题是:有没有办法在我返回的实体中忽略EAGERs关联来执行特定查询?
示例:当我有这个实体Person时,我想在查询人员时不带地址列表。
@Entity public class Person { @Column private String name; @OneToMany(fetch=FetchType.EAGER) private List address; } Query query = EntityManager.createQuery("FROM Person person"); //list of person without the address list! But how??? List resultList = query.getResultList();
谢谢!
更新
我找到的唯一方法是不返回实体,只返回实体的某些字段。 但我想找到一个解决方案,我可以返回实体(在我的例子中, Person
实体)。
我在想是否可以在Hibernate中将同一个表映射两次。 通过这种方式,我可以在没有EAGER关联的情况下映射同一个表。 在少数情况下这会帮助我……
如果您使用的是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 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);
有关此主题的更多信息,请点击此处 。
当您使用获取图时,只会将您放在@NamedEntityGraph中的字段急切地获取。
在没有提示的情况下执行的所有现有查询将保持不变。
从来没有尝试过,但可能值得一试……假设会话工厂通过注入或任何其他方式在DAO层可用,您可以在(可能是新的)DAO方法中实现类似的东西:
List result = (List ) sessionFactory.getCurrentSession() .createCriteria(Person.class) .setFetchMode("address", FetchMode.LAZY) .list(); return result;
默认情况下,Hibernate的HQL,Criteria和NativeSQL为我们提供了EAGERly加载集合的灵活性,如果它在域模型中被映射为LAZY。
关于反过来,即,将集合映射为域模型中的EAGER并尝试使用HQL,Criteria或NativeSQL进行LAZY加载,我找不到直接或更简单的方式来我们可以满足这一点HQL /标准/ nativeSQL将。
虽然我们可以在Criteria上设置FetchMode.LAZY
,但它已被弃用,它等同于FetchMode.SELECT
。 实际上,FetchMode.LAZY实际上会导致触发额外的SELECT查询并仍然急切地加载集合。
但是,如果我们想要LAZY加载映射为EAGER的集合,您可以尝试此解决方案:使HQL / Criteria / NativeSQL返回标量值并使用ResultTransformer(Transformers.aliasToBean(..))
返回具有从标量值填充的字段的实体对象(或DTO)。
在我的场景中,我有一个Forest实体,它有一组Tree实体, 其中包含FetchType.EAGER
和FetchMode.JOIN
oneToMany映射。 要仅加载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();
我已经测试了上面的简单查询,它可能正在检查这是否适用于复杂的情况,并适合所有用例。
很想知道是否有更好或更简单的方法,特别是没有标量和变形金刚。
- 是的,您可以将两个实体类映射到同一个表,这是一个有效的解决方法。 但是,请注意两种类型的实例同时存在于同一持久性上下文中的情况,因为一种类型的实体实例的更新不会反映到另一种类型的同一实例中。 此外,此类实体的二级缓存变得更加复杂。
- 获取配置文件也很有趣,但目前非常有限,您只能使用连接样式获取配置文件覆盖默认获取计划/策略(您可以使懒惰关联渴望,但反之亦然)。 但是,您可以使用此技巧来反转该行为:默认情况下使关联变为惰性,并默认为所有会话/事务启用配置文件。 然后在要延迟加载的事务中禁用配置文件。
你没有说为什么你不能从渴望变为懒惰。 然而,我得到的印象是出于性能原因,所以我想质疑这个假设。 如果是这种情况,请考虑以下事项。 我意识到这个答案没有严格回答你的问题而且它违反了没有延迟加载的条件,但是这里有一个替代方案,它反映了我的开发团队对我认为是同一个潜在问题的方法。
将fetch类型设置为lazy,但随后设置@BatchSize注释。 由于hibernate通常使用单独的数据库查询来加载集合,这可以维护该行为,但是通过调整BatchSize,您可以避免每个元素进行1次查询(例如,在循环中) – 前提是您的会话仍处于打开状态。
OneToOne关系的backref的行为有点滑稽(关系的引用方 – 没有外键的一方)。 但是对于OneToOne的另一面,对于OneToMany和ManyToOne,这给出了我认为您可能想要的最终结果:如果您确实需要它们,您只查询表但是您避免每条记录的延迟加载,而您没有必须明确配置每个用例。 这意味着在执行延迟加载的情况下,您的性能将保持可比性,但如果您实际上不需要它,则不会发生此加载。