Hibernate - HQL 分页查询

23

这是一个类似于以下问题的问题:HQL - row identifier for pagination

我正在尝试使用HQL实现分页,我的数据库是PostgreSQL。

int elementsPerBlock = 10;
int page = 2; //offset = 2*10

String sqlQuery = "FROM Messages AS msg " +
                  " LEFT JOIN FETCH msg.commands AS cmd " +   
                  "ORDER BY msg.identifier ASC" ;

Query query = session.createQuery( sqlQuery )
                     .setFirstResult( elementsPerBlock * ( (page-1) +1 ) )
                     .setMaxResults( elementsPerBlock );

Hibernate会获取所有的消息,然后在加载完成后返回所需的消息。

因此,Hibernate会获取210000个实体,而不是返回的30个实体(每个消息恰好有2个命令)。

是否有一种方法可以将开销减少7000倍?

编辑:我尝试添加.setFetchSize(elementsPerBlock),但没有帮助。

编辑2:生成的SQL查询语句为:

select ... 
from schemaName.messages messages0_ 
left outer join schemaName.send_commands commands1_ 
on messages0_.unique_key=commands1_.message_key 
order by messages0_.unique_identifier ASC

完全没有LIMIT或OFFSET限制


可能是一个错误。您能否启用调试参数以显示SQL查询并查看实际执行的查询吗? - ahmet alp balkan
这个 SQL 查询完全没有 LIMIT 或 OFFSET。 - iliaden
1
setFirstResult( elementsPerBlock * ( (page-1) +1 ) ) 这里的“-1 +1”似乎不正确 :o) - Peter Wippermann
6个回答

18
根据JPA 2.0规范,第3.8.6节查询执行中,将setMaxResults或setFirstResult应用于涉及集合的抓取连接的查询的效果是未定义的。这因数据库而异,在我的经验中,Hibernate通常会在内存中进行分页,而不是在数据库查询级别上进行分页。
我通常所做的是使用单独的查询获取所需对象的ID,并将其传递到带有抓取连接的查询中。

如何在子查询中使用分页?(我理解第二个想法,但是我的交流可能会变得非常啰嗦) - iliaden
已编辑以删除对子查询的引用。如果使用子查询,它确实会打败整个目的。 - Stevi Deter
尽管这样做会违背初衷,但我希望所有的计算都在数据库端完成,而不是客户端。这就是为什么我认为如果子查询可以进行分页,我会使用它的原因。 - iliaden
我认为子查询不能进行分页操作,当然尝试一下也没有坏处。 - Stevi Deter

7
我正在使用这个解决方案:
/**
 * @param limitPerPage
 * @param page
 * @return
 */
public List<T> searchByPage(int limitPerPage, int page, String entity) {
    String sql = "SELECT t FROM " + entity + " t";
    Query query = em.createQuery(sql)
            .setFirstResult(calculateOffset(page, limitPerPage))
            .setMaxResults(limitPerPage);
    return query.getResultList();
}

/**
 * @param page
 * @return
 */
private int calculateOffset(int page, int limit) {
    return ((limit * page) - limit);
}

欢迎。

为什么不把它翻译成中文呢? - Shantaram Tupe
1
我曾经在不知道单词含义的情况下翻译了很多英文代码到葡萄牙语... 为什么你不能做同样的事情呢? - Odravison

2
我们可以使用查询和条件接口来实现分页:
使用查询接口实现分页有两种方法:
1. Query setFirstResult(int startPosition): 该方法接受一个整数,表示结果集中的第一行,从第0行开始。
2. Query setMaxResults(int maxResult): 该方法告诉Hibernate检索固定数量maxResults的对象。通过上述两种方法的结合,我们可以在我们的Web或Swing应用程序中构建一个分页组件。
例子:
Query query = session.createQuery("FROM Employee");
query.setFirstResult(5);
query.setMaxResults(10);
List<Employee> list = query.list();
for(Employee emp: list) {            
   System.out.println(emp);
}

使用Criteria接口进行分页:

Criteria接口有两种用于分页的方法。

1. Criteria setFirstResult(int firstResult):

设置要检索的第一个结果。

2. Criteria setMaxResults(int maxResults):

设置要检索的对象数量上限。

示例:

Criteria criteria = session.createCriteria(Employee.class);
criteria.setFirstResult(5);
criteria.setMaxResults(10);            
List<Employee> list = criteria.list();
for(Employee emp: list) {            
    System.out.println(emp);
}

你试过这个吗?Stevi Deter说这个的效果是未定义的。 - cslotty

1

很可能,如果您使用HQL创建自己的查询,则查询构建器方法无法解析自定义的hql查询并更改它。因此,您应该将LIMIT ?, ?语句放在HQL查询的末尾,并绑定偏移参数。


我有些困惑,不太理解你的意思。1)我该如何在HQL查询的末尾附加一个SQL字符串?2)你所说的“绑定偏移参数”是什么意思? - iliaden

1

由于您没有根据command实体的某些属性过滤结果集,因此您还可以避免SQL连接并为message的命令配置延迟获取。没有连接,Hibernate将使用数据库分页功能。

但是,您必须关注N+1选择问题,即避免为每个惰性获取的commands属性选择单个选择。您可以通过在Hibernate映射中设置batch-size属性或全局设置hibernate.default_batch_fetch_size属性来避免这种情况。

例如:如果您在Hibernate会话中获取了100个message对象并设置了batch-size为10,则当您首次调用getCommands()时,Hibernate将获取10个不同消息对象的10个command关联。查询数量减少到10加上原始消息获取一个。

请看这里:http://java.dzone.com/articles/hibernate-tuning-queries-using?page=0,1 作者比较了一个简单示例中不同的提取策略


0

我认为你原来的异常不正确。

发生的情况是Hibernate获取了所有消息,并在加载完毕后返回所需的消息。

查询处理时发生的情况是,setFirstResult(calculateOffset(page, limitPerPage))被翻译成OFFSET,setMaxResults(limitPerPage)被翻译成LIMIT。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接