Критерии гибернации n + 1 проблема с maxresults - PullRequest
8 голосов
/ 06 апреля 2011

Используя hibernate ctiteria, я хочу выбрать объект и связанный с ним список объектов oneToMany. Я хочу разбить этот список на страницы, избегая страшной спящей проблемы n + 1 select

Вот рабочее решение, которое требует 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 (ура), но не может обработать нумерацию страниц, т.е. setMaxResults и setFirstResult некорректны для родительского объекта 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();

Это кажется таким распространенным требованием, но я нашел решение без удачи.

Кто-нибудь принимает?

Ответы [ 2 ]

15 голосов
/ 06 апреля 2011

Сложно получить 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();

Для некоторых баз данных выборка подвыбора children также может работать.

2 голосов
/ 06 апреля 2011

Насколько я знаю, нет хороших способов решения этой проблемы, за исключением следующего трюка с собственным запросом SQL (точный синтаксис SQL зависит от вашей СУБД):

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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...