Есть ли способ получить размер счетчика для именованного запроса JPA с набором результатов?

Мне нравится идея именованных запросов в JPA для статических запросов, которые я собираюсь выполнять, но я часто хочу получить результат подсчета для запроса, а также список результатов из некоторого подмножества запроса. Я бы не стал писать два почти идентичных NamedQueries. В идеале я хотел бы иметь что-то вроде:

@NamedQuery(name = "getAccounts", query = "SELECT a FROM Account")
.
.
  Query q = em.createNamedQuery("getAccounts");
  List r = q.setFirstResult(s).setMaxResults(m).getResultList();
  int count = q.getCount();

Допустим, m равно 10, s равно 0 и в Account 400 строк. Я бы ожидал, что r будет иметь список из 10 элементов, но я хотел бы знать, что всего 400 строк. Я мог бы написать второй @NamedQuery:

@NamedQuery(name = "getAccountCount", query = "SELECT COUNT(a) FROM Account")

но это кажется СУХИМ нарушением, если я всегда просто хочу подсчитать. В этом простом случае легко синхронизировать их, но если запрос изменится, кажется менее идеальным, что мне нужно обновить оба @NamedQueries, чтобы значения соответствовали друг другу.

Обычным вариантом использования здесь будет получение некоторого подмножества элементов, но при этом потребуется какой-либо способ указать общее количество («Отображение 1-10 из 400»).


person Tim    schedule 17.02.2010    source источник


Ответы (3)


Таким образом, решение, которое я использовал, состояло в том, чтобы создать два @NamedQuery, один для набора результатов и один для подсчета, но зафиксировав базовый запрос в статической строке, чтобы поддерживать DRY и гарантировать, что оба запроса остаются согласованными. Итак, для вышеизложенного у меня было бы что-то вроде:

@NamedQuery(name = "getAccounts", query = "SELECT a" + accountQuery)
@NamedQuery(name = "getAccounts.count", query = "SELECT COUNT(a)" + accountQuery)
.
static final String accountQuery = " FROM Account";
.
  Query q = em.createNamedQuery("getAccounts");
  List r = q.setFirstResult(s).setMaxResults(m).getResultList();
  int count = ((Long)em.createNamedQuery("getAccounts.count").getSingleResult()).intValue();

Очевидно, что в этом примере тело запроса тривиально, и это излишество. Но с гораздо более сложными запросами вы получаете одно определение тела запроса и можете обеспечить синхронизацию двух запросов. Вы также получаете то преимущество, что запросы предварительно скомпилированы, и, по крайней мере, с Eclipselink вы получаете проверку во время запуска, а не при вызове запроса.

Выполняя согласованные имена между двумя запросами, можно обернуть тело кода для запуска обоих наборов, просто взяв за основу базовое имя запроса.

person Tim    schedule 10.10.2010
comment
Интересно, я даже не знал, что вы можете ссылаться на статические финалы при настройке аннотаций. Однако я думаю, что решение, которое не требует двух отдельных именованных запросов, все же было бы идеальным. - person aroth; 08.10.2015

Используя setFirstResult/< a href="http://java.sun.com/javaee/5/docs/api/javax/persistence/Query.html#setMaxResults(int)" rel="noreferrer">setMaxResults сделать не возвращает подмножество результирующего набора, запрос даже не был запущен, когда вы вызываете эти методы, они влияют на сгенерированный запрос SELECT, который будет выполняться при вызове getResultList. Если вы хотите получить общее количество записей, вам придется SELECT COUNT создавать свои сущности в отдельном запросе (обычно перед разбиением на страницы).

Полный пример см. на странице Разбиение наборов данных на страницы в примере приложения с использованием JSF, сеанса без сохранения состояния фасада каталога и API сохраняемости Java.

person Pascal Thivent    schedule 17.02.2010
comment
да. Я хочу получить полный подсчет, к которому приведет запрос. Таким образом, подсчет в примере не будет соответствовать r.size(), или я просто буду использовать его. Потенциальный вариант использования заключается в том, что я хочу получить страницу результатов для перечисления учетных записей, но для того, чтобы сообщить им общее количество страниц, я хотел бы получить общее количество из запроса, даже если я просто получаю 25 результатов. - person Tim; 18.02.2010
comment
@ Тим, я уточнил свой ответ. Если это все еще не ясно, дайте мне знать. - person Pascal Thivent; 18.02.2010
comment
Спасибо. Я попытался уточнить свой вопрос. По сути, я знаю, что модификаторы изменяют запрос до его запуска. Что я надеюсь сделать, так это иметь один @NamedQuery, который действует как база для возврата подмножества, а также для получения количества, вместо того, чтобы писать один запрос для количества и один для возвращаемого набора. В идеале все еще используйте @NamedQuery. Я проверю ссылку, на которую вы ссылаетесь. - person Tim; 19.02.2010
comment
Проверил ссылку, и она отображает проблему, которую я пытаюсь избежать. По сути, у них есть два разных запроса в двух разных методах, чтобы получить объекты и получить количество. Таким образом, в одном классе вы выбираете объект (o) из Item как o, а в другом выбираете count (o) из Item как o. Если вы обновите первый, не осознавая, что существует соответствующий запрос на подсчет, вы получите противоречивые результаты. Я хотел бы определить базовый запрос в одном месте и получить варианты объектов (o) и count (o). Если бы я использовал String для запроса, это было бы довольно просто, но вы потеряли бы преимущества Named. - person Tim; 19.02.2010

да ладно, вы можете использовать самоанализ для получения аннотаций именованных запросов, таких как:

String getNamedQueryCode(Class<? extends Object> clazz, String namedQueryKey) {
    NamedQueries namedQueriesAnnotation = clazz.getAnnotation(NamedQueries.class);
    NamedQuery[] namedQueryAnnotations = namedQueriesAnnotation.value();

    String code = null;
    for (NamedQuery namedQuery : namedQueryAnnotations) {
        if (namedQuery.name().equals(namedQueryKey)) {
            code = namedQuery.query();
            break;
        }
    }

    if (code == null) {
        if (clazz.getSuperclass().getAnnotation(MappedSuperclass.class) != null) {
            code = getNamedQueryCode(clazz.getSuperclass(), namedQueryKey);
        }
    }

    //if not found
    return code;
}
person alessandro    schedule 28.06.2011
comment
Не знаю, как это относится к моему вопросу. Я хочу использовать NamedQuery для преимуществ предварительной компиляции, кэширования и других оптимизаций. Я хочу, чтобы два запроса были идентичными, за исключением того, что один возвращает результаты, а другой возвращает счет. И я хочу следовать DRY, а не копировать и вставлять условное предложение. - person Tim; 30.06.2011