提问人:Joseph Larson 提问时间:11/18/2023 更新时间:11/18/2023 访问量:22
使用限制和偏移时获取总记录计数
Get total record count while using limit and offset
问:
在将一些遗留代码从 QueryDSL 3.x 转换为 5.x 之前,我仍在学习 QueryDSL。我有这个代码:
query = query.distinct()
.from(qCamera)
.where()
.orderBy(order)
.limit( pageSize )
.offset((long) pageNum * pageSize );
List<Tuple> datatuple = query.list( a whole ton of fields)
long totalResults = query.count();
显然,这有很多问题。但现在,我需要了解如何使用 ,同时还要返回整个记录计数,以便我们也可以实现分页。limit()
offset()
是否可以在原始查询上使用并获取整个大小?还是我还需要做些什么?fetchCount()
答:
我不知道我将要发布的内容是否是最好的方式,但它对我有用。这是一个示例项目,它似乎可以工作。
简而言之,我在 中准备了基本查询。然后,我克隆查询并更改选择,如您所见。然后,在执行fetch之前,我添加了order-by以及limit和offset调用。buildBookQuery()
runBookQuery()
我不知道这是否是最佳方法。我相当确定这会变成对数据库的两次点击。 通常,但并不总是很快。SELECT count(*)
如果有其他方法可以做到这一点,我希望有人发布更好的方法。
public List<BookInfo> pagedBookInfo(int pageSize, int pageNumber) {
System.out.printf("pagedBookInfo(%d,%d)\n", pageSize, pageNumber);
JPAQuery<Tuple> query = buildBookQuery();
long size = query.clone().select(QBook.book.count()).fetchFirst();
System.out.printf("Full length: %d\n", size);
query = query.offset( pageSize * pageNumber ).limit(pageSize);
return runBookQuery(query);
}
/**
* This is how you a join when you want to select data from both tables.
* The important part is the sequence of
* .select().from().innerJoin().on().
*/
JPAQuery<Tuple> buildBookQuery(
JPAQueryFactory queryFactory = new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager);
QMember member = QMember.member;
QBook book = QBook.book;
JPAQuery<Tuple> query = queryFactory
.select(member.id, member.name, book.title)
.from(member)
.innerJoin(member.books, book)
.where(member.id.eq(1))
;
return query;
}
/**
* This is how you execute a Tuple-based query and
* return consolidated results.
*/
ArrayList<BookInfo> runBookQuery(JPAQuery<Tuple> query) {
OrderSpecifier<?> byName = new OrderSpecifier<>(com.querydsl.core.types.Order.ASC, QMember.member.name);
OrderSpecifier<?> byTitle = new OrderSpecifier<>(com.querydsl.core.types.Order.ASC, QBook.book.title);
query.orderBy(byName, byTitle);
List<Tuple> results = query
.fetchJoin()
.fetch();
ArrayList<BookInfo> list = new ArrayList<>();
for (Tuple tuple: results) {
BookInfo bi = new BookInfo();
bi.memberName = tuple.get(1, String.class);
bi.title = tuple.get(2, String.class);
list.add(bi);
}
return list;
}
更新说明:QueryDSL 页面是这样说的:
fetchCount 需要计算计数查询。在 querydsl-sql 中,这是通过将查询包装在子查询中来完成的,如下所示: SELECT COUNT(*) FROM (<original query>)。不幸的是,JPQL(JPA 的查询语言)不允许从子查询进行查询。因此,在 JPQL 中没有一种通用的方法来表达计数查询。从历史上看,QueryDSL 会尝试生成修改后的查询来计算结果数。但是,此方法仅适用于简单查询。具体来说,具有多个 group by 子句的查询和带有 having 子句的查询被证明是有问题的。这是因为 COUNT(DISTINCT a, b, c) 虽然在大多数方言中都是有效的 SQL,但在大多数方言中却不是有效的 JPQL。此外,having 子句可以引用 select 元素或聚合函数,因此不能通过将谓词移动到 where 子句来模拟。为了支持具有多个 group by 元素或 having 子句的查询的 fetchCount,我们在内存中生成计数。这意味着该方法只是回退到返回 fetch() 的大小。对于大型结果集,这可能会严重影响性能。对于非常具体的领域模型,其中 fetchCount() 必须与包含多个 group by 元素和/或 having 子句的复杂查询结合使用,我们建议使用 QueryDSL 的 Blaze-Persistence 集成。在其他高级查询功能中,Blaze-Persistence 使得从 JPQL 中的子查询中进行选择成为可能。因此,集成提供的 BlazeJPAQuery 正确地实现了 fetchCount,并始终执行正确的计数查询。
我认为这意味着我的答案是错误的。如果我让它与 Blaze Persistence 一起使用,我会发布一个新的答案。
最终编辑
我做了更多的测试,并更新了我的初始答案,以解决一些问题。
首先,我以次优方式进行联接,当我的表大小增加时,我的查询甚至在不应该增长的时候也会增长(返回 4 条记录,需要 10 秒)。您看到的副本是正确的方法。
错误的方式:
query.join(book).on(book.in(member.books))
更好的方法:
query.join(member.books, book)
没有电话。第二个参数列为 ,但我的 select 子句可以按照我最初编写的方式执行。on()
alias
book.title
尽管 QueryDSL javadocs 中有很长的段落,但它正在及时工作。如果我查询的是一个小成员(几本书)或一个大计数(11000)但页面大小很小,则调用速度非常快,并且返回的计数是准确的。
除非有人告诉我我做错了,否则这是我的最终答案。
评论