Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Criteria n+1 issue with maxresults

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?

like image 398
jaseFace Avatar asked Apr 06 '11 14:04

jaseFace


People also ask

What is the n + 1 select problem in hibernate?

The N + 1 Select problem is a performance issue in Hibernate. In this problem, a Java application makes N + 1 database calls (N = number of objects fetched). For example, if N= 2, the application makes 3 (N+1= 3) database calls. Example. Let’s understand this problem with the help of an example.

What is the difference between firstresult and maxresult in hibernate?

In this article we are going to see FirstResult and MaxResult in hibernate. FirstResult – Suppose if we like to retrieve records from database from 6th record, then we can set setFirstResult (6). MaxResult – Suppose if we need to retrieve only 10 records from database, then we can use setMaxResult (10).

Does hibernate favor consistency?

Because Hibernate favors consistency, it fetches the entire result set and does the pagination in memory. However, that can be suboptimal, so what can we do about it?

What is the difference between setfirstresult and setmaxresult in MySQL?

FirstResult – Suppose if we like to retrieve records from database from 6th record, then we can set setFirstResult (6). MaxResult – Suppose if we need to retrieve only 10 records from database, then we can use setMaxResult (10). FetchSize – This is almost similar to MaxResult, We can set number of rows to be retrieved.


2 Answers

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.

like image 128
meriton Avatar answered Nov 04 '22 18:11

meriton


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);
    }
}
like image 28
axtavt Avatar answered Nov 04 '22 17:11

axtavt