强制hibernate在不改变映射的情况下急切地加载多个关联

时间:2014-05-15 10:58:27

标签: java hibernate lazy-loading

我有一些实体具有懒惰的一对多关系(为简洁起见省略了逻辑):

@Entity
class A{
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "a_pk", nullable = false)
    List<B> blist = new ArrayList<>();

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "a_pk", nullable = false)
    List<C> clist = new ArrayList<>();

    @Column(name = "natural_identifier", nullable = false)
    private String id;
}

@Entity
class B{
}

@Entity
class C{
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "c_pk", nullable = false)
    List<D> dlist = new ArrayList<>();

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "c_pk", nullable = false)
    List<E> elist = new ArrayList<>();
}

@Entity
class D{
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "d_pk", nullable = false)
    List<F> flist = new ArrayList<>();
}

@Entity
class E{
}

@Entity
class F{
}

在某些(非常罕见的)情况下,我想热切地加载A的实例及其所有关联。原因是我希望对该A实例进行一些修改,并将它作为一个整体,然后根据用户输入保存或丢弃它们。

如果我要根据需要加载内容,我必须根据用户请求将实体重新附加到新会话,但是它们正在被修改,并且修改不应该保留。

所以我写了类似的东西:

Session session = sessionFactory.openSession();
s.beginTransaction();
Criteria c = session
                    .createCriteria(A.class)
                    .add(Restrictions.eq("id", someValue))
                    .setFetchMode("blist", SELECT)
                    .setFetchMode("clist", SELECT)
                    .createAlias("clist", "c")
                    .setFetchMode("c.dlist", SELECT)
                    .setFetchMode("c.elist", SELECT)
                    .createAlias("c.dlist", "d")
                    .setFetchMode("d.flist", SELECT);
A a = (A) c.uniqueResult();
session.close(); // line 150

a.getBlist().size(); // line 152 - debug
a.getClist().size(); // line 153 - debug

当我尝试访问内容时,我在line 152上获得了例外:

org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role: 
A.blist, could not initialize proxy - no Session

如果我在条件中的任何地方将获取策略更改为JOIN,我会得到相同的异常,但是在line 153上(换句话说,第一个关联被加载,而不是其他关联)。 / p>

编辑:Alexey Malev建议为别名设置获取模式;它看起来确实如此。符合以下条件:

Criteria c = session
                    .createCriteria(A.class)
                    .add(Restrictions.eq("id", someValue))
                    .setFetchMode("blist", JOIN)
                    .setFetchMode("clist", JOIN);

我在第149行遇到了一个不同的例外: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags 。因此,加入提取不是一种选择。

问题是,如何加载整个内容?

Hibernate版本4.2.12.Final

3 个答案:

答案 0 :(得分:7)

我通过使用稍微不同的方法找到了原始问题的解决方案。 引用Hibernate ORM文档:

  

有时需要在关闭Session之前初始化代理或集合。例如,您可以通过调用cat.getSex()或cat.getKittens()。size()来强制初始化。但是,这可能会使代码的读者感到困惑,并且对于通用代码来说不方便。

     

静态方法Hibernate.initialize()和Hibernate.isInitialized()为应用程序提供了一种使用延迟初始化集合或代理的便捷方式。只要其Session仍处于打开状态,Hibernate.initialize(cat)将强制执行代理cat的初始化。 Hibernate.initialize(cat.getKittens())对小猫集合有类似的效果。

简单地进行懒惰的收集(a.getBlist())并没有使它加载 - 我最初犯了这个错误。如果我尝试从该集合中获取一些数据(获取项目,获取集合大小),它将加载。在该集合上调用Hibernate.initialize(..)也会这样做。

因此,迭代实体关联及其各自的关联等,并在会话中显式初始化它们(例如,使用Hibernate.initialize())将加载在会话结束后可用的所有内容。

标准获取模式根本不用于该方法(为什么它们不能像记录的那样工作是另一个问题)。

这是N + 1问题的一个明显例子,但我可以忍受。

答案 1 :(得分:1)

我认为您使用了错误的获取模式。您很可能需要JOIN。 试试这个:

Criteria c = session
                .createCriteria(A.class)
                .add(Restrictions.eq("id", someValue))
                .setFetchMode("blist", JOIN)
                .setFetchMode("clist", JOIN)
                .createAlias("clist", "c")
                .setFetchMode("c", JOIN)
...//the same for others

注意 - 我也为别名添加了获取模式。当您无法加载任何具有别名的列表时的行为会导致猜测..

答案 2 :(得分:0)

对于记录,由于键值setFecthMode(key,...)
,我遇到了类似的问题 我使用的是表名而不是字段名