enlait enlait - 6 months ago 37
Java Question

Force hibernate to eagerly load multiple associations without changing mapping

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

Answer

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.