У меня есть следующие записи 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));
Есть идеи ?
Проблема здесь в логическом порядке операций 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))
Это работает для вашего простого примера, но как только вы начнете объединять вещи, вам придется дублировать много логики запроса между внутренним и внешним запросом, а это не очень желательно.
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.*;
Согласитесь, это не так красиво. Возможно, вы могли бы преобразовать это в другой формат или написать какую-нибудь более общую утилиту для такого рода результатов, но я надеюсь, что вы поняли суть этого.
Примечание. Этот подход не даст вам правильного значения счетчика, если вы разбиваете страницы за пределы последней страницы.