Рекомендации по кодированию на Java для повторного использования части запроса для подсчета

реализация-result-paging-in-hibernate-get Вопрос -total-number-of-rows вызывает у меня еще один вопрос о некоторых проблемах с реализацией:

Теперь вы знаете, что вам нужно повторно использовать часть запроса HQL для подсчета, как эффективно повторно использовать?

Различия между двумя запросами HQL:

  1. выбор count(?) вместо pojo или property (или списка)
  2. выборки не должно происходить, поэтому некоторые таблицы не следует объединять
  3. order by должен исчезнуть

Есть ли другие отличия?

Есть ли у вас передовые методы кодирования для эффективного повторного использования (проблемы: усилия, ясность, производительность)?

Пример простого запроса HQL:

    select       a     from A a join fetch a.b b where a.id=66 order by a.name
    select count(a.id) from A a                  where a.id=66

ОБНОВЛЕНО

Я получил ответы на следующие темы:

  • с использованием критериев (но в основном мы используем HQL)
  • управление запросом String (но все согласны, что это кажется сложным и небезопасным)
  • обертывание запроса, полагаясь на оптимизацию базы данных (но есть ощущение, что это небезопасно)

Я надеялся, что кто-то предложит варианты по другому пути, более связанному с конкатенацией строк.
Можно ли построить оба HQL-запроса, используя общие части?


person KLE    schedule 21.10.2009    source источник


Ответы (4)


Хороший вопрос. Вот что я делал в прошлом (о многом вы уже упоминали):

  1. Check whether SELECT clause is present.
    1. If it's not, add select count(*)
    2. В противном случае проверьте, есть ли в нем DISTINCT или агрегатные функции. Если вы используете ANTLR для анализа своего запроса, это можно обойти, но это довольно сложно. Вам, вероятно, лучше просто обернуть все это select count(*) from ().
  2. Удалить fetch all properties
  3. Удалите fetch из объединений, если вы разбираете HQL как строку. Если вы действительно анализируете запрос с помощью ANTLR, вы можете полностью удалить left join; проверять все возможные ссылки довольно беспорядочно.
  4. Удалить order by
  5. В зависимости от того, что вы сделали в 1.2, вам необходимо удалить / отрегулировать group by / having.

Сказанное выше, естественно, относится к HQL. Для запросов Criteria вы довольно ограничены в том, что можете делать, потому что они не поддаются легким манипуляциям. Если вы используете какой-то слой-оболочку поверх Criteria, вы получите эквивалент (ограниченного) подмножества результатов синтаксического анализа ANTLR, и в этом случае вы сможете применить большую часть вышеперечисленного.

Поскольку обычно вы придерживаетесь смещения текущей страницы и общего количества, я обычно сначала запускаю фактический запрос с заданным пределом / смещением и запускаю запрос count(*) только в том случае, если количество возвращаемых результатов больше или равно ограничению И смещение равно ноль (во всех остальных случаях я либо запускал count(*) раньше, либо все равно получил обратно все результаты). Конечно, это оптимистичный подход в отношении одновременных модификаций.

Обновление (по ручной сборке HQL)

Мне такой подход не особенно нравится. При отображении как именованный запрос HQL имеет преимущество проверки ошибок во время сборки (ну, технически во время выполнения, потому что SessionFactory должен быть построен, хотя обычно это делается во время интеграционного тестирования в любом случае). Когда генерируется во время выполнения, он терпит неудачу во время выполнения :-) Оптимизировать производительность тоже не так-то просто.

Те же рассуждения, конечно, применимы и к критериям, но из-за четко определенного API, в отличие от конкатенации строк, ошибиться немного сложнее. Параллельное построение двух HQL-запросов (один с разбивкой по страницам и один с «глобальным подсчетом») также приводит к дублированию кода (и потенциально большему количеству ошибок) или вынуждает вас писать какой-то слой-оболочку поверх, чтобы сделать это за вас. Оба пути далеки от идеала. А если вам нужно сделать это из клиентского кода (например, через API), проблема усугубится.

Я действительно довольно много размышлял над этой проблемой. API поиска из Hibernate-Generic-DAO кажется разумным компромиссом; в моем ответе на связанный выше вопрос есть более подробная информация.

person ChssPly76    schedule 21.10.2009
comment
+1 Спасибо за точность манипулирования запросом. Благодарим также за превосходную точность, заключающуюся в том, что запрос подсчета должен запускаться только после первого запроса. - person KLE; 22.10.2009
comment
Я обновил свой вопрос, не могли бы вы дать еще один ответ, связанный с новой частью? Мне понравились многие ваши посты, а вы эксперт по java :-) ... - person KLE; 22.10.2009

Вы пытались прояснить свои намерения для Hibernate, установив проекцию на свои (SQL?) Критерии? Я в основном использовал критерии, поэтому я не уверен, насколько это применимо к вашему случаю, но я использовал

getSession().createCriteria(persistentClass).
setProjection(Projections.rowCount()).uniqueResult()

и позволяя Hibernate самостоятельно определять кеширование / повторное использование / умные вещи .. Не совсем уверен, сколько умных вещей он на самом деле делает .. Кто-нибудь хочет это прокомментировать?

person Tim    schedule 21.10.2009
comment
Hibernate не кэширует запросы сам по себе; вы должны сделать это явно. Проблема с вышеупомянутым подходом (и с использованием критериев в целом) заключается в том, что слой, собирающий критерии, должен создать еще одну его копию только для подсчета. Другими словами, я не могу просто создать критерий на бизнес-уровне, передать его службе (или DAO) и получить обратно 1 страницу результатов + общее количество. Это не имеет большого значения для небольших приложений, но приводит к МНОГО ненужного кода в более крупных. - person ChssPly76; 23.10.2009

Что ж, я не уверен, что это лучшая практика, но моя практика :)

Если у меня в качестве запроса есть что-то вроде:

select A.f1,A.f2,A.f3 from A, B where A.f2=B.f2 order by A.f1, B.f3

А я просто хочу знать, сколько результатов получу, выполняю:

select count(*) from ( select A.f1, ... order by A.f1, B.f3 )

А затем получите результат как целое число, не отображая результаты в POJO.

Проанализируйте ваш запрос на предмет удаления некоторых частей, например, "упорядочить по" очень сложно. Хорошая СУБД оптимизирует ваш запрос за вас.

Хороший вопрос.

person sinuhepop    schedule 21.10.2009
comment
Спасибо за вашу поддержку :-) Мой типичный запрос - HQL, возвращающий pojos или список свойств. - person KLE; 21.10.2009
comment
Как и вы, мне не хочется разбирать запрос, чтобы удалить некоторые части; но я не очень доверяю РСУБД для оптимизации запроса во всех случаях. Я думаю, что в некоторых случаях происходит сбой, и трудно предсказать, в каких именно. Есть ли список фактов об этих оптимизациях? - person KLE; 21.10.2009
comment
Я не знаю, где найти эти списки фактов и является ли эта информация общедоступной. Я не могу быть полностью уверен, что РСУБД оптимизируется таким образом. Но, будучи студентом, я видел несколько старых базовых оптимизаций, которые казались более сложными, чем эта. В этом случае, например, РСУБД подумает: они запрашивают у меня количество строк, поэтому порядка можно избежать. - person sinuhepop; 22.10.2009

В ситуации HQL от руки я бы использовал что-то вроде этого, но это не может быть повторно использовано, так как это довольно специфично для данных сущностей.

Integer count = (Integer) session.createQuery("select count(*) from ....").uniqueResult();

Сделайте это один раз и соответственно измените начальный номер, пока не пролистаете страницу.

Хотя для критериев я использую такой образец

final Criteria criteria = session.createCriteria(clazz);  
            List<Criterion> restrictions = factory.assemble(command.getFilter());
            for (Criterion restriction : restrictions)
                criteria.add(restriction);
            criteria.add(Restrictions.conjunction());
            if(this.projections != null)
                criteria.setProjection(factory.loadProjections(this.projections));
            criteria.addOrder(command.getDir().equals("ASC")?Order.asc(command.getSort()):Order.desc(command.getSort()));
            ScrollableResults scrollable = criteria.scroll(ScrollMode.SCROLL_INSENSITIVE);
            if(scrollable.last()){//returns true if there is a resultset
                genericDTO.setTotalCount(scrollable.getRowNumber() + 1);
                criteria.setFirstResult(command.getStart())
                        .setMaxResults(command.getLimit());
                genericDTO.setLineItems(Collections.unmodifiableList(criteria.list()));
            }
            scrollable.close();
            return genericDTO;

Но это делает счет каждый раз, вызывая ScrollableResults:last().

person non sequitor    schedule 21.10.2009