对集合字段的JPA迭代

时间:2012-01-20 21:14:27

标签: jpa collections iteration

假设有一个像这样的实体类

@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();

5 个答案:

答案 0 :(得分:2)

您正在使用哪个JPA实现? 如果你正在使用休眠,你可以利用一些休眠功能。

hibernate documentation

中所述

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)

您有两种选择:

  1. 将JPA QuerysetFirstResult()/setMaxResult()
  2. 一起使用
  3. 获取List并取出您需要的subList
  4. 选项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()方法后,您将检索新的实体子集。

希望这有助于为您的分页逻辑带来一些结构。