Hibernate Criteria与maxresults的n + 1问题

时间:2011-04-06 14:11:14

标签: hibernate criteria

使用hibernate ctiteria我想选择一个对象,它与oneToMany对象列表相关联。我想通过这个列表分页,避免可怕的hibernate n + 1选择问题

这是一个有效的解决方案,需要11个数据库访问10个父对象。

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();

这是一个只执行一个sql语句(hurray)但无法处理分页的解决方案,即父对象上的setMaxResults和setFirstResult不正确母亲(嘘)

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();

这似乎是一个常见的要求,但我已经挖掘出一个没有运气的解决方案。

任何参赛者?

2 个答案:

答案 0 :(得分:15)

将其归结为1个查询很难(即我不知道可移植解决方案),但将其归结为2个查询(无论n)都非常简单:

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();

对于某些数据库,subselect fetching children也可能有效。

答案 1 :(得分:2)

据我所知,没有好方法可以解决这个问题,除了以下使用本机SQL查询的技巧(确切的SQL语法取决于你的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);
    }
}