假设有一个像这样的实体类
@Entity
public class User {
...
public Collection<User> followers;
...
}
让我们说用户有数以千计的用户关注者。我想分页...我是否必须亲自进入JPQL才能对结果进行分页而不做任何其他选择?
int page = 5;
User u = em.find(User.class, id);
for (User u : u.getFollowers(page, 100)) { // get the 5th 100 result
// do some stuff
}
有没有类似的解决方案或模式?是否可以通过仅使用下面的代码访问字段u.getFollowers(page, 100)
来对集合运行时的字段进行分页?
我已经知道了这个......
int page = 5;
List<User> followers = em.createQuery("select u.followers from User u where u.id=?1", User.class)
.setParameter(1, id).setFirstResult(page*100).setMaxResult(100).getResultList();
答案 0 :(得分:2)
您正在使用哪个JPA实现? 如果你正在使用休眠,你可以利用一些休眠功能。
中所述 createFilter()
方法可用于有效检索集合的子集,而无需初始化整个集合。
List<User> followerPage = session.createFilter( followers, "")
.setFirstResult(500).setMaxResults(100)
.list();
您可以使用createFilter()
来获取集合的大小而不进行初始化,这对于分页也很重要。
( (Integer) session.createFilter( followers, "select count(*)" ).list().get(0) ).intValue()
答案 1 :(得分:1)
是的,您必须执行查询才能进行分页。
上一个示例中的代码段很好,除了分页而不使用任何顺序对查询结果进行排序不是一个好主意:在不使用任何{的结果的确定性顺序中无法保证{ {1}}条款,AFAIK。
答案 2 :(得分:1)
您发布的JPQL查询唯一的问题是没有指定ORDER BY语句。使用ORDER BY很重要,因为每个页面都会使用多个查询,并且您希望确保以相同的顺序返回结果。
关于你对获取所有行然后提取其子列表的低效率的评论:好吧,JPA提供程序将在幕后构建一个完整的查询,包括LIMIT语句,当JDBC连接器支持它时。所以,你不必关心它。
如果要确保有效地将查询发送到JDBC连接器,请将JPA提供程序的日志记录级别设置为FINE并将其签出。
如需进一步参考,如果您使用的是EclipseLink提供程序,请查看此link。
答案 3 :(得分:1)
您有两种选择:
Query
与setFirstResult()/setMaxResult()
List
并取出您需要的subList
。选项1是最兼容,干净且推荐的解决方案。
选项2可能是性能(甚至是内存)问题 - 或者不是。
这取决于在最坏情况下可以读取多少行以及通常读取的行比率。 如果行数很小或者您在大多数情况下都会阅读整个列表 - 那就去吧。
可能也是一些JPA实现,在某些情况下它实际上只是懒惰地获取列表的请求记录,但要小心并详细查看JPA实现的文档。 (但我不知道这样的实现)
答案 4 :(得分:0)
如果你想以这种方式调用它,有一种方法或模式,在Adam Bien的书Real World Java EE Patterns Rethinking Best Practices中描述,称为Paginator。
一种实现策略基于JPA查询,使用已经提到的Query方法(第一个和最大结果)。以下示例不在第125页的书中:
@Stateful
public class CustomerQueryBean implements Iterator<List<Customer>> {
@PersistenceContext
private EntityManager em;
private int index = 0;
private int pageSize = 10;
private boolean next = true;
public void setPageSize(int pageSize){
this.pageSize = pageSize;
}
public List<Customer> next(){
List<Customer> retVal = null;
Query query = this.em.createNamedQuery(Customer.ALL);
query.setFirstResult(getFirst());
query.setMaxResults(pageSize);
retVal = query.getResultList();
if(retVal.size()==0){
this.next = false;
}
index++;
return retVal;
}
private int getFirst(){
return index * pageSize;
}
public boolean hasNext() {
return this.next;
}
public void remove() {
throw new UnsupportedOperationException("Operation remove …");
}
@Remove
public void close(){
this.em.clear();
this.em.close();
}
}
如您所见,该方法基于Iterator接口。原因是客户端只对迭代逻辑感兴趣。执行next()方法后,您将检索新的实体子集。
希望这有助于为您的分页逻辑带来一些结构。