Hi, I'm in the middle of my first Hibernate project, so I might have missed how to do certain things.
However, here is a simplified view of my problem (and solution):
Lets say I have the following pojos: A, B, C, D
A has one-to-many relationships to B, C and D, i.e. A has three methods getAs() : Set, getBs() : Set and getDs() : Set
In my webapp I render a long list of A objects, that I fetch from a query. In each row I have to render all elements of B, C and D as well.
In HQL I have the possibility to use join fetch for one collection, (e.g. B) in order to speed things up. But on each row I render, there will be two new querys for the C and D collections.
If there are N elements of type A in the list, there will be 1 + 2N queries against the database for my list. This will be a huge performance hit for long lists of A objects.
My solution (or hack) to the problem is to run just 3 querys to load all the objects like this:
Code:
public static List getAList(Date from, Date to) throws HibernateException {
// The where clause has to be the same for all 3 queries
String WHERE =
"where b.date >= :from" +
"and b.date <= :to ";
Session sess = HibernateProvider.getSession();
Query query = sess.createQuery("select a " +
"from A a " +
"join fetch a.bs b " +
WHERE);
query.setParameter("from", from);
query.setParameter("to", to);
List aList = query.list();
// Init C collections for all A objects in aList (no more, no less)
query = sess.createQuery("select a.id, c " +
"from A a " +
"join a.bs b " +
"join a.cs c " +
WHERE);
query.setParameter("from", from);
query.setParameter("to", to);
Id2LazySet id2CSet = new Id2LazySet(query.list());
// Init all a->c sets
for (int i = 0, size = aList.size(); i < size; i++) {
A a = (A) aList.get(i);
// Update C collection for this A object
id2CsSet.updateLazySet(sess, a, a.getCs());
}
// Init D collections for all A objects in aList (no more, no less)
query = sess.createQuery("select a.id, d " +
"from A a " +
"join a.bs b " +
"join a.ds d " +
WHERE);
query.setParameter("from", from);
query.setParameter("to", to);
Id2LazySet id2DSet = new Id2LazySet(query.list());
// Init all a->d sets
for (int i = 0, size = aList.size(); i < size; i++) {
A a = (A) aList.get(i);
// Update D collection for this A object
id2DsSet.updateLazySet(sess, a, a.getDs());
}
return aList;
}
public class Id2LazySet {
private Map map = new HashMap();
public Id2LazySet(List id_objs) {
// Build a map with id->a list with collection elements
for (int i = 0, size = id_objs.size(); i < size; i++) {
Object[] id_obj = (Object[]) id_objs.get(i);
ArrayList list = (ArrayList) map.get(id_obj[0]);
if (null == list) {
list = new ArrayList();
map.put(id_obj[0], list);
}
list.add(id_obj[1]);
}
}
public void updateLazySet(Session sess, Object owner, java.util.Set lazySet) throws SysException {
try {
SessionImpl sessImpl = (SessionImpl) sess;
Set set = (Set) lazySet;
CollectionSnapshot collectionSnapshot = set.getCollectionSnapshot();
ArrayList contentList = (ArrayList) map.get(collectionSnapshot.getKey());
int size = (null != contentList ? contentList.size() : 0);
Serializable[] idArray = new Serializable[size];
for (int i = 0; i < size; i++) {
idArray[i] = sess.getIdentifier(contentList.get(i));
}
SessionFactoryImplementor sfi = (SessionFactoryImplementor) sess.getSessionFactory();
set.initializeFromCache(sfi.getCollectionPersister(collectionSnapshot.getRole()), idArray, owner);
} catch (HibernateException x) {
throw new SysException(x);
}
}
}
It works very well, but it feels like an ugly hack. Imagine if HQL could do this for you with something like this:
Code:
select a
from A a
join fetch a.bs b
query-fetch a.cs c
query-fetch a.ds d
where b.date >= :from
and b.date <= :to
This query would generate 3 sql queries against the DB, and all our objects and collections would be initiated...
What do you think? Am I alone with this problem?
Please let me know if it was hard to understand my point of view!
Dave