【问题标题】:Hibernate fetch join -> cannot fetch multiple bagsHibernate fetch join -> 无法获取多个包
【发布时间】:2011-11-02 21:06:00
【问题描述】:

问题是我的实体中有两个包,我想在我的 jsf 前端显示它们(弹簧在后面,所以没有延迟加载)。所以我必须急切地获取它们以在这样的列表中显示信息:

  • 点 1(标签 1,标签 2)(标签 1 ...标签 n)
  • 第 2 点(标签 3、标签 4)(标签 1 ... 标签 n)

将两个列表都放入 Eager 不起作用。所以我用 fetch join 试试运气。它允许我获取一个列表,但是当我添加第二个列表时,我得到了已知的“无法获取多个包”错误。

Hibernate 可以在一个查询中处理两个 fetch 连接吗?

public class PointOfInterest
 @OneToMany(mappedBy="poi")
private List<PointOfInterestLabel> labels = new ArrayList<PointOfInterestLabel>();

@ManyToMany
private List<Tag> tags = new ArrayList<Tag>();

我的 fetch 加入:

SELECT DISTINCT p from PointOfInterest p 
        left join fetch p.labels 
        left join fetch p.tags WHERE p.figure = :figure

在启动时,我的休眠工厂的创建失败:

Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
    at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:94)
    at org.hibernate.loader.hql.QueryLoader.<init>(QueryLoader.java:123)
    at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:206)
    at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)
    at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:101)
    at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
    at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:98)
    at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:557)
    at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:422)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1385)
    at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:954)
    at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:883)
    ... 55 more

【问题讨论】:

    标签: hibernate join jpa-2.0 fetch


    【解决方案1】:

    答案是:不。它处理不了。就是这么说的。

    对于值类型(复合元素),它甚至不起作用,因为您无法获得实际属于同一个包项的信息。

    通常它甚至没有意义。如果您查询一个表并在起始表中获取 10 条记录,在第一个包中获取 10 条记录,在第二包中获取另外 10 条记录,那么您将检索 1000 条记录以在内存中创建这 30 个对象。想象一下,当每个表中有 100 条记录时(提示:1,000,000 而不是 300)以及当您 fetch join 另一个包时(提示:100,000,000 而不是 400)时的记录数......

    顺便说一句:join fetch 可能会导致奇怪的效果和问题,应该避免,除非你完全知道自己在做什么。

    【讨论】:

    • 谢谢!目前我用 Set 替换了它,但我想我明天会重构界面并显示更少的信息。感谢您的反馈!当您的前端没有持久性范围时,您将如何解决它。使用 DAO 中内置的数据传输对象而不是获取的实体?我继承了这个项目,我不能做巨大的设计更改,也不能把它切换到 j2ee。 :-/
    • 您可以关闭延迟加载,这对性能不利。这会通过单独的查询立即获取袋子。您还可以访问袋子(例如尺寸)以强制加载。完美的解决方案是在 dao 之外创建会话并将其保留用于整个业务事务。 (在对 dao 的每次调用中创建一个会话称为 session-per-call,是一种反模式。)
    • 所以这实际上是一个 catch 22 问题:如果你不使用 join fetch,你会得到 n+1 问题或延迟初始化异常。 dao 之外的会话是一种 hack,首先是因为它与 JPA 合约相去甚远,其次是因为它难以控制,因为您不应该在创建实体的那一刻对实体的生命周期做出任何假设。它可能跨越多个请求,不一定是 http。
    • 您可以通过出色的批量抓取功能避免 N+1,该功能透明且没有任何副作用。您不需要从 dal 外部引用会话,但您必须从 dal 外部控制事务的生命周期,因为它比对 db 的单个调用要长。您可以将其隐藏在 dal 接口后面。在我们的项目中,我们实现了一个环境事务的东西,它存储在dal中的一个线程静态字段中。
    【解决方案2】:

    您可以拆分查询并在不同的查询中加载实体,而不是使用 Set。
    比如

        From PointOfInterest p left join fetch p.labels WHERE p.figure = :figure
        From PointOfInterest p left join fetch p.tags WHERE p.figure = :figure            
    

    请参考the link

    【讨论】:

      【解决方案3】:

      同一查询中不能有 2 个 1-n eager-joins(或 HQL fetch-joins)。

      如果您连接到 1-n 表并且有 10 行匹配,则非 N 实体上的所有公共列都会为每一行重复。

      如果它返回 10 行,则必须确定是哪一个 1-n 导致了额外的行。从技术上讲,它可以基于连接键 - 如果连接键是唯一的主键。但是这个异常表明hibernate无法分辨。

      【讨论】:

        猜你喜欢
        • 2013-08-30
        • 2013-09-03
        • 1970-01-01
        • 1970-01-01
        • 2021-10-19
        • 1970-01-01
        • 2012-08-31
        • 1970-01-01
        • 2011-10-13
        相关资源
        最近更新 更多