Force hibernate to eagerly load multiple associati

2020-07-08 00:07发布

问题:

I have a few entities with lazy one to many relationships (logic omitted for brevity):

@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{
}

In some (very rare) case I want to load an instance of A and all of its associations eagerly. The reason for that is that I want to make some modifications on that A instance and it's children as a whole, and then either save or discard them, depending on user input.

If I were to load things as they are needed, I'd have to reattach entities to a new session on user request, but they are being modified, and modifications should not be persisted yet.

So I write something like that:

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

When I try to access stuff I get an exception on line 152:

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

If I change fetch strategy to JOIN everywhere in the criteria, I get the same exception, but on line 153 (in other words, the first association gets loaded, but not the others).

EDIT: Alexey Malev suggested that fetch mode is set for an alias; it does seem to be true. With a following criteria:

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

I'm getting a different Exception: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags on line 149. So, join-fetching is not an option.

The question is, how do I load the whole thing?

Hibernate version 4.2.12.Final

回答1:

I found a solution to my original problem by using a slightly different approach. Quoting Hibernate ORM documentation:

Sometimes a proxy or collection needs to be initialized before closing the Session. You can force initialization by calling cat.getSex() or cat.getKittens().size(), for example. However, this can be confusing to readers of the code and it is not convenient for generic code.

The static methods Hibernate.initialize() and Hibernate.isInitialized(), provide the application with a convenient way of working with lazily initialized collections or proxies. Hibernate.initialize(cat) will force the initialization of a proxy, cat, as long as its Session is still open. Hibernate.initialize( cat.getKittens() ) has a similar effect for the collection of kittens.

Simply getting lazy collection (a.getBlist()) does not make it load - I initially made that mistake. If I try to get some data from that collection (get an item, get collection size) it will load. Calling Hibernate.initialize(..) on that collection will do the same.

So, iterating over entity associations, and their respective associations, etc, and explicitly initializing them (eg with Hibernate.initialize()) within session will load everything to be available outside the session once it's closed.

Criteria fetch modes are not used at all with that approach (why won't they work as documented is another question).

It is an obvious case of N+1 problem, but something I can live with.



回答2:

I think you're using wrong fetch mode. Most likely you need JOIN. Try this instead:

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

Note - I have added fetch mode for alias too. The behavior when you're not able to load any list which has an alias leads to that guess..



回答3:

For the record I had a similar problem due to the key value setFecthMode(key,...)
I was using table names instead of field names.