Reputation: 1455
Using hibernate ctiteria I want to select an object and it's associated oneToMany list of objects. I want to paginate through this list avoiding the dreaded hibernate n+1 select issue
Here's a working solution which requires 11 trips to the database for 10 parent objects.
Criteria criteria = this.getSession().createCriteria(Mother.class);
criteria.addOrder(Order.asc("title"))
.setMaxResults(details.getMaxRows())
.setFirstResult(details.getStartResult())
.setFetchMode("kittens", FetchMode.SELECT);
List test = criteria.list();
And here's a solution which executes only one sql statement (hurray) but cannot handle pagination ie the setMaxResults and setFirstResult are incorrect on the parent object Mother (boo)
Criteria criteria = this.getSession().createCriteria(Mother.class);
criteria.addOrder(Order.asc("title"))
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.setMaxResults(details.getMaxRows())
.setFirstResult(details.getStartResult())
.setFetchMode("kittens", FetchMode.JOIN);
List test = criteria.list();
This seems like such a common requirement but I've dug around for a solution with no luck.
Any takers?
Upvotes: 8
Views: 3925
Reputation: 70564
Getting it down to 1 query is tough (i.e. I don't know a portable solution), but getting it down to 2 queries (irrespective of n) is pretty simple:
Criteria criteria = this.getSession().createCriteria(Mother.class);
criteria.addOrder(Order.asc("title"))
.setMaxResults(details.getMaxRows())
.setFirstResult(details.getStartResult())
.setProjection(Projections.id());
List<?> ids = criteria.list();
criteria = getSession().createCriteria(Mother.class)
.add(Restrictions.in("id", ids))
.setFetchMode("children", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
return criteria.list();
For some databases, subselect fetching children
might work, too.
Upvotes: 15
Reputation: 242686
As far as I know there are no good ways to solve this problem except for the following trick with native SQL query (exact SQL syntax depends on your DBMS):
List<Mother> result = s.createSQLQuery(
"select {m.*}, {k.*} " +
"from (select limit :firstResult :maxResults * from Mother m) m " +
"left join Kitten k on k.motherId = m.id"
)
.addEntity("m", Mother.class)
.addJoin("k", "m.kittens")
.setParameter("firstResult", ...)
.setParameter("maxResults", ...)
.setResultTransformer(MyDistrictRootEntityResultTransformer.INSTANCE)
.list();
...
// Unfortunately built-in DistrictRootEntityResultTransformer cannot be used
// here, since it assumes that root entity is the last in the tuple, whereas
// with addEntity()/addJoin() it's the first in the tuple
public class MyDistrictRootEntityResultTransformer implements ResultTransformer {
public static final MyDistrictRootEntityResultTransformer INSTANCE = new MyDistrictRootEntityResultTransformer();
public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple[0];
}
public List transformList(List collection) {
return DistinctResultTransformer.INSTANCE.transformList(collection);
}
}
Upvotes: 2