我有一个服务可以返回分页结果,或者使用此签名实现或多或少的方法:
List<Record> getRecordsPage(long page, int pageSize);
当调用它时,我只需创建一个查询并进行如下配置:
TypedQuery<Record> query = entityManager.createQuery(criteriaQuery);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
此页面显示结果。这是按预期工作的,非常简单。
我的另一个要求是实现一个方法来检索包含特定记录的页面。使用以下签名实现方法:
List<Record> getRecordsPage(Record record, int pageSize);
此方法需要生成记录所在的右页。例如,对于getRecordsPage(RECORD4, 2)
调用,考虑数据库状态:
1. RECORD1
2. RECORD2
3. RECORD3
4. RECORD4
5. RECORD5
返回的页面应为2,其中包含[RECORD3, RECORD4]
。
ORDER BY
参数始终设置,可以是多个字段。
到目前为止,我有一些解决方案:
使用提供的查询我只选择没有分页的id,只执行indexOf
以找到它的位置,并根据我可以找到记录页面的位置,然后使用getRecordsPage(long page, int pageSize)
已经实施。
当我使用mySQL时,我可以执行一个类似:select r from (select rownum r, id from t order by x,y) z where z.id = :id
的sql,什么会返回记录的位置,我可以使用它来调用getRecordsPage(long page, int pageSize)
。
要求:
一个好的解决方案是:
答案 0 :(得分:5)
为了确保我理解正确:您正在显示Record
,并希望显示所有记录的分页列表,其中包含预选包含您商品的页面?
首先,您必须知道关系数据库不提供数据库中任何记录的隐式排序。虽然它们似乎是从头到尾添加的,但这不便携且可靠。
因此,您的分页列表/网格必须由某些列明确排序。为简单起见,您的网格按id
排序。您知道当前显示的记录的ID(例如:X
)。首先需要弄清楚您的记录中关于此排序顺序的记录位置:
SELECT COUNT(r)
FROM Record r
WHERE r.id < :X
此查询将返回记录前的记录数。现在很简单:
int page = count / pageSize
page
从0开始。
不幸的是,如果您的排序列不是唯一的,这可能并不适用于所有情况。但是如果列不是唯一的,则排序本身不稳定(具有相同值的记录可能以随机顺序出现),因此请考虑使用额外的唯一列进行排序:
...
ORDER BY r.sex, r.id
在这种情况下,记录首先按sex
(大量重复)和id排序。在当前记录之前计算记录的解决方案仍然有效。
答案 1 :(得分:1)
鉴于赏金尚未分配,我将添加另一个答案。 (这与Tomasz Nurkiewicz的回答非常相似,但更多地讨论了我认为棘手的部分:使WHERE
条款正确。如果被认为是不礼貌,我会删除它。)
您不需要原生查询来查找记录的位置,只需精心设计的SELECT COUNT(r)
。
为了使其具体化,我们假设Record
类型具有您用于排序的属性foo
和bar
(即ORDER BY foo, bar
)。然后你想要的方法的快速和脏版本看起来像这样(未经测试):
List<Record> getRecordsPage(Record record, int pageSize) {
// Note this is NOT a native query
Query query = entityManager.createQuery(
"SELECT COUNT(r) " +
"FROM Record r " +
"WHERE (r.foo < :foo) OR ((r.foo = :foo) AND (r.bar < :bar))");
query.setParameter("foo", record.getFoo());
query.setParameter("bar", record.getBar());
int zeroBasedPosition = ((Number) query.getSingleResult()).intValue();
int pageIndex = zeroBasedPosition / pageSize;
return getRecordsPage(pageIndex, pageSize);
}
有两个棘手的考虑因素:准确地使WHERE
子句正确,并在所有排序列中处理“关系”。
关于WHERE
子句,目标是计算排序“低于”给定Record
的记录。使用一个排序列很容易:例如,它只是带有r.foo < :foo
的记录。
对于两个排序列,它稍微更难,因为第一列中可能存在“关联”,必须用第二列打破。因此,“较低”记录要么r.foo < :foo
,要么r.foo = :foo
和r.bar < :bar
。 (如果有三个,那么你需要三个OR
条件 - 类似于(r.foo < :foo) OR ((r.foo = :foo) AND (r.bar < :bar)) OR ((r.foo = :foo) AND (r.bar = :bar) AND (r.baz < :baz))
。)
然后在所有排序列中都存在“联系”的可能性。如果发生这种情况,那么你的寻呼机功能(getRecordsPage(long,int)
)每次都可能获得不同的结果(如果它是天真地实现的)(因为数据库每次执行查询时都有不同顺序返回“相等”行的余地)。
解决此问题的一种简单方法是按记录ID添加排序,以确保每次都以相同的方式断开关系。而且你想要在你的问题中提到的函数中同时放置相应的逻辑。这意味着在您的查询末尾添加id
,例如ORDER BY foo,bar,id
,在寻呼机功能中,并向OR
中的WHERE
添加另一个getRecordsPage(Record,int)
条件。)