7

有没有办法在 Web 请求之间使用 Hibernate 来维护数据库游标?

基本上,我正在尝试实现分页,但是被分页的数据一直在变化(即新记录被添加到数据库中)。我们正在尝试对其进行设置,以便当您进行初始搜索(最多返回 5000 个结果)并翻阅结果时,这些相同的记录始终出现在同一页面上(即我们不会连续运行查询每次单击下一页和上一页按钮)。我们目前实现这一点的方式是仅从我们正在分页的表中选择 5000 个(最多)主键,将这些键存储在内存中,然后一次只使用 20 个主键从数据库中获取它们的详细信息. 然而,

我尝试使用 Hibernate 的 ScrollableResults 执行此操作,但发现我无法调用 next() 和 previous() 之类的方法,如果您在不同的 Web 请求/Hibernate 会话中会导致异常(这并不奇怪)。

有没有办法将 ScrollableResults 对象重新附加到 Session,就像重新附加分离的数据库对象以使其持久化一样?

4

2 回答 2

7

千万不要使用offset,因为offset还要读取offset之前的所有数据,效率非常低。

您需要按索引的唯一属性进行排序,并在 API 调用中返回最后一项属性的值,并使用WHERE子句从您离开的位置开始。最后一项的属性值将是您的光标位置。例如,使用主键id作为游标的简单分页查询将如下所示:

List<MyEntity> entities = entityManager
    .createQuery("""
        FROM
            MyEntity e
        WHERE
            e.id > :cursorPosition
        ORDER BY
            e.id ASC
    """, MyEntity.class)
    .setParameter("cursorPosition", cursorPosition)
    .setMaxResults(pageSize)
    .getResultList()

第一次调用 API,该cursorPosition值可以是 0。第二次您将从客户端接收到客户端从第一次调用接收到的游标。了解Google 地图分页地点查询如何与该nextPageToken属性一起使用。

您的光标必须是标识查询的所有参数的字符串。因此,如果您有其他参数,则它必须可以使用游标检索。

我相信你可以通过多种方式做到这一点。一种方法是连接所有参数并cursorPosition在一个字符串中,将其编码为一个 URL 友好的字符串,如 Base64,并在接收回解码并将字符串拆分为原始参数:

 String nextPageToken = Base64.getUrlEncoder()
     .encodeToString("indexProperty=id&cursorPos=123&ageBiggerThan=65".getBytes())

您的 api 调用将返回一个像这样的 json:

{
    "items": [ ... ],
    "nextPageToken": "aW5kZXhQcm9wZXJ0eT1pZCZjdXJzb3JQb3M9MTIzJmFnZUJpZ2dlclRoYW49NjU="
}

客户下一个电话:

GET https://www.example.com/api/myservice/v1/myentity?pageToken=aW5kZXhQcm9wZXJ0eT1pZCZjdXJzb3JQb3M9MTIzJmFnZUJpZ2dlclRoYW49NjU=

连接和拆分光标字符串的部分可能很烦人,我真的不知道是否有一个库可以处理创建标记和解析它的工作,我实际上是在这个问题中,因为我正在寻找它。但我的猜测是 GSON 或 Jackson 可以在这方面为您节省几行代码。

于 2019-03-26T16:55:18.100 回答
2

从本质上讲,您是靠自己来完成这一任务的。您要做的是查看 OpenSessionInView 过滤器并构建您自己的过滤器,这样您就不会为每个请求创建一个新的 HibernateSession,而是从与用户的 Web 会话相关联的缓存中提取一个。

如果您没有像 Spring WebFlow 这样的框架来为您提供一些对话结构,那么您也需要构建它。因为您可能想要某种方式来管理 Hibernate 会话的生命周期,而不是“当 Web 会话到期时”。您也很可能不希望来自同一个 Web 会话的两个用户线程但不同的浏览器选项卡共享一个休眠会话。(很可能会引起欢闹。)

于 2010-05-04T16:14:30.307 回答