Как написать запрос JOOQ, чтобы получить только одну запись Java, содержащую все результаты

У меня есть следующие записи Java:

record Info(Integer count){}
record Person(Integer id, String name) {}
record MyResult(Info info, List<Person> persons){}

Мне нужно вернуть объект MyResult, содержащий общее количество лиц в Info и неполный список этих лиц.

Я понятия не имею, можно ли это сделать напрямую только с одним запросом JOOQ и как это сделать.

Вот попытка, но, очевидно, она получит список, так что это неправильный способ.

DSL.getContext(conn)
  .select(
    DSL.row(DSL.count().over()).mapping(Info::new),
    DSL.multiset(
      DSL.select(
         PERSON.PERSON_ID,
         PERSON.NAME
      ).convertFrom(r -> r.map(Records.mapping(PERSON::new)))
    )
  )
   .from(PERSON)
   .where(PERSON.NAME.startWith("A"))
   .orderBy(PERSON.NAME.asc())
   .offset(0)
   .limit(5) 
   .fetchOne(Records.mapping(MyResult::new));

Есть идеи ?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
263
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема здесь в логическом порядке операций SQL . Любые средства подсчета результатов (как совокупные, так и оконные) выполняются до применения LIMIT. Итак, если вы не хотите, чтобы LIMIT как-либо влиял на ваш подсчет, вы должны написать запрос таким образом, чтобы он учитывался в первую очередь. Но если вы не получаете никаких результатов от своего предложения WHERE, то вы не получаете никаких строк при использовании подсчета окон. При использовании совокупных подсчетов вы получите строку, но вы больше не сможете так легко применять LIMIT. (Без LIMIT вы могли бы просто использовать совокупный счетчик и multisetAgg() вместо этого).

Итак, я вижу 2 решения: медленное, но простое, более быстрое, но более сложное:

Делаем все в запросе, дважды обращаемся к таблице:

Если вы хотите переместить всю логику сопоставления в сам запрос, я подозреваю, что вам придется выполнить 2 запроса к таблице PERSON, что, вероятно, нежелательно по соображениям производительности:

ctx.select(
      count().filterWhere(PERSON.NAME.startsWith("A")).convertFrom(Info::new),
      multiset(
        select(PERSON_ID, PERSON_NAME)
        .from(PERSON)
        .where(PERSON.NAME.startsWith("A"))
        .orderBy(PERSON.NAME.asc())
        .limit(5)
      ).convertFrom(r -> r.map(mapping(Person::new)))
    )
   .from(PERSON)
   .fetchOne(mapping(MyResult::new))

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

Использование JDK Collector API

Но вам не нужно формировать запрос иерархически, как вашу целевую структуру данных. Вы все еще можете выполнять некоторую логику сопоставления в клиенте вне jOOQ, например:

ctx.select(
      count().over().convertFrom(Info::new),
      row(PERSON_ID, PERSON_NAME).mapping(Person::new)
    )
    .from(PERSON)
    .where(PERSON.NAME.startsWith("A"))
    .orderBy(PERSON.NAME.asc())
    .limit(5)
    .collect(collectingAndThen(
        groupingBy(
            // There's a single possible value from count().over()
            r -> r.value1(),
            mapping(r -> r.value2(), toList())
        ),
        m -> {
            // Probably, just use var here...
            Iterator<Entry<Info, List<Person>>> it = m.entrySet().iterator();

            if (it.hasNext()) {
                Entry<Info, List<Person>> e = it.next();
                return new MyResult(e.getKey(), e.getValue());
            }
            else
                return new MyResult(new Info(0), Collections.emptyList());
        }
    )));

Это предполагает следующий статический импорт:

import static java.util.stream.Collectors.*;

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

Примечание. Этот подход не даст вам правильного значения счетчика, если вы разбиваете страницы за пределы последней страницы.

Другие вопросы по теме