Spring Data JPA 中的分页(限制和偏移)

Posted

技术标签:

【中文标题】Spring Data JPA 中的分页(限制和偏移)【英文标题】:Pagination in Spring Data JPA (limit and offset) 【发布时间】:2014-09-20 10:06:55 【问题描述】:

我希望用户能够在我的查询方法中指定限制(返回金额的大小)和偏移量(返回的第一条记录/返回的索引)。

这是我没有任何分页功能的课程。 我的实体:

@Entity
public Employee 
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @Column(name="NAME")
    private String name;

    //getters and setters

我的仓库:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> 

    @Query("SELECT e FROM Employee e WHERE e.name LIKE :name ORDER BY e.id")
    public List<Employee> findByName(@Param("name") String name);

我的服务接口:

public interface EmployeeService 

    public List<Employee> findByName(String name);

我的服务实现:

public class EmployeeServiceImpl 

    @Resource
    EmployeeRepository repository;

    @Override
    public List<Employee> findByName(String name) 
        return repository.findByName(name);
    

现在我尝试提供支持偏移和限制的分页功能。 我的实体类保持不变。

我的“新”存储库采用可分页参数:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> 

    @Query("SELECT e FROM Employee e WHERE e.name LIKE :name ORDER BY e.id")
    public List<Employee> findByName(@Param("name") String name, Pageable pageable);

我的“新”服务接口接受两个附加参数:

public interface EmployeeService 

    public List<Employee> findByName(String name, int offset, int limit);

我的“新”服务实现:

public class EmployeeServiceImpl 

    @Resource
    EmployeeRepository repository;

    @Override
    public List<Employee> findByName(String name, int offset, int limit) 
        return repository.findByName(name, new PageRequest(offset, limit);
    

但这不是我想要的。 PageRequest 指定页面和大小(页面编号和页面大小)。现在指定大小正是我想要的,但是,我不想指定起始页#,我希望用户能够指定起始记录/索引。我想要类似的东西

public List<Employee> findByName(String name, int offset, int limit) 
    TypedQuery<Employee> query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.name LIKE :name ORDER BY e.id", Employee.class);
    query.setFirstResult(offset);
    query.setMaxResults(limit);
    return query.getResultList();

特别是 setFirstResult() 和 setMaxResult() 方法。但是我不能使用这种方法,因为我想使用 Employee 存储库接口。 (或者通过 entityManager 定义查询实际上更好吗?)无论如何,有没有办法在不使用 entityManager 的情况下指定偏移量?提前致谢!

【问题讨论】:

***.com/questions/30217552/… 【参考方案1】:

下面的代码应该可以做到。我在自己的项目中使用并针对大多数情况进行了测试。

用法:

   Pageable pageable = new OffsetBasedPageRequest(offset, limit);
   return this.dataServices.findAllInclusive(pageable);

和源代码:

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.io.Serializable;

/**
* Created by Ergin
**/
public class OffsetBasedPageRequest implements Pageable, Serializable 

    private static final long serialVersionUID = -25822477129613575L;

    private int limit;
    private int offset;
    private final Sort sort;

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset zero-based offset.
     * @param limit  the size of the elements to be returned.
     * @param sort   can be @literal null.
     */
    public OffsetBasedPageRequest(int offset, int limit, Sort sort) 
        if (offset < 0) 
            throw new IllegalArgumentException("Offset index must not be less than zero!");
        

        if (limit < 1) 
            throw new IllegalArgumentException("Limit must not be less than one!");
        
        this.limit = limit;
        this.offset = offset;
        this.sort = sort;
    

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset     zero-based offset.
     * @param limit      the size of the elements to be returned.
     * @param direction  the direction of the @link Sort to be specified, can be @literal null.
     * @param properties the properties to sort by, must not be @literal null or empty.
     */
    public OffsetBasedPageRequest(int offset, int limit, Sort.Direction direction, String... properties) 
        this(offset, limit, new Sort(direction, properties));
    

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset zero-based offset.
     * @param limit  the size of the elements to be returned.
     */
    public OffsetBasedPageRequest(int offset, int limit) 
        this(offset, limit, Sort.unsorted());
    

    @Override
    public int getPageNumber() 
        return offset / limit;
    

    @Override
    public int getPageSize() 
        return limit;
    

    @Override
    public int getOffset() 
        return offset;
    

    @Override
    public Sort getSort() 
        return sort;
    

    @Override
    public Pageable next() 
        return new OffsetBasedPageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
    

    public OffsetBasedPageRequest previous() 
        return hasPrevious() ? new OffsetBasedPageRequest(getOffset() - getPageSize(), getPageSize(), getSort()) : this;
    


    @Override
    public Pageable previousOrFirst() 
        return hasPrevious() ? previous() : first();
    

    @Override
    public Pageable first() 
        return new OffsetBasedPageRequest(0, getPageSize(), getSort());
    

    @Override
    public boolean hasPrevious() 
        return offset > limit;
    

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;

        if (!(o instanceof OffsetBasedPageRequest)) return false;

        OffsetBasedPageRequest that = (OffsetBasedPageRequest) o;

        return new EqualsBuilder()
                .append(limit, that.limit)
                .append(offset, that.offset)
                .append(sort, that.sort)
                .isEquals();
    

    @Override
    public int hashCode() 
        return new HashCodeBuilder(17, 37)
                .append(limit)
                .append(offset)
                .append(sort)
                .toHashCode();
    

    @Override
    public String toString() 
        return new ToStringBuilder(this)
                .append("limit", limit)
                .append("offset", offset)
                .append("sort", sort)
                .toString();
    

【讨论】:

@NickJ 它是用于比较字段相等性的 Apache 通用库。您可以一一比较每个字段。详情请看这篇文章mkyong.com/java/java-how-to-overrides-equals-and-hashcode 对于hasPrevious(),不应该是return offset &gt;= limit;吗? 在框架中直接包含这个或类似的东西是个好主意... 在 Spring 2.3.4 中 - 排序方法出现错误 'Sort(Direction, List) 具有私有访问权限 org.springframework.data.domain.Sort。偏移量也已更改为 long 数据类型而不是 int @shivajibhosale 通过检查文档,我发现Sort.by(Direction, String...) 可以根据方向和属性构造一个Sort 对象,以替换之前的构造函数。【参考方案2】:

您可以通过创建自己的 Pageable 来做到这一点。

试试这个基本示例。适合我:

public class ChunkRequest implements Pageable 

private int limit = 0;
private int offset = 0;

public ChunkRequest(int skip, int offset) 
    if (skip < 0)
        throw new IllegalArgumentException("Skip must not be less than zero!");

    if (offset < 0)
        throw new IllegalArgumentException("Offset must not be less than zero!");

    this.limit = offset;
    this.offset = skip;


@Override
public int getPageNumber() 
    return 0;


@Override
public int getPageSize() 
    return limit;


@Override
public int getOffset() 
    return offset;


@Override
public Sort getSort() 
    return null;


@Override
public Pageable next() 
    return null;


@Override
public Pageable previousOrFirst() 
    return this;


@Override
public Pageable first() 
    return this;


@Override
public boolean hasPrevious() 
    return false;



【讨论】:

为什么是offset = skiplimit = offset?为什么不直接设置offsetlimit呢?【参考方案3】:

给你:

public interface EmployeeRepository extends JpaRepository<Employee, Integer> 

    @Query(value="SELECT e FROM Employee e WHERE e.name LIKE ?1 ORDER BY e.id offset ?2 limit ?3", nativeQuery = true)
    public List<Employee> findByNameAndMore(String name, int offset, int limit);

【讨论】:

读者,请注意这是使用原生 SQL 查询的解决方案,而不是 JPQL 查询。【参考方案4】:

也许答案有点晚了,但我也想过同样的事情。根据偏移量和限制计算当前页面。好吧,它并不完全相同,因为它“假定”偏移量是限制的倍数,但也许您的应用程序适合于此。

@Override
public List<Employee> findByName(String name, int offset, int limit) 
    // limit != 0 ;)
    int page = offset / limit;
    return repository.findByName(name, new PageRequest(page, limit));

我建议改变架构。如果可能,更改您的控制器或调用该服务的任何内容以最初为您提供页面和限制。

【讨论】:

这只能在非常受控的情况下工作。如果偏移量低于限制,它将被窃听,因为页面将无法控制地四舍五入。例如,Offset = 9,Limit = 100,仍将返回前 9 行。【参考方案5】:

使用 spring data jpa 可能无法做到这一点。如果偏移量很小,您可以在检索后从查询中删除前 X 语句。

否则,您可以将页面大小定义为偏移量并从 page+1 开始。

【讨论】:

我喜欢您将页面大小定义为偏移量的想法,但唯一的缺点是我无法设置结果的实际页面大小(限制)。感谢您的反馈!【参考方案6】:

使用 long offsetSort.by() 调整好的 @codingmonkey awnser。

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.io.Serializable;

public class OffsetBasedPageRequest implements Pageable, Serializable 

    private static final long serialVersionUID = -25822477129613575L;

    private final int limit;
    private final long offset;
    private final Sort sort;

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset zero-based offset.
     * @param limit  the size of the elements to be returned.
     * @param sort   can be @literal null.
     */
    public OffsetBasedPageRequest(long offset, int limit, Sort sort) 
        if (offset < 0) 
            throw new IllegalArgumentException("Offset index must not be less than zero!");
        

        if (limit < 1) 
            throw new IllegalArgumentException("Limit must not be less than one!");
        
        this.limit = limit;
        this.offset = offset;
        this.sort = sort;
    

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset     zero-based offset.
     * @param limit      the size of the elements to be returned.
     * @param direction  the direction of the @link Sort to be specified, can be @literal null.
     * @param properties the properties to sort by, must not be @literal null or empty.
     */
    public OffsetBasedPageRequest(long offset, int limit, Sort.Direction direction, String... properties) 
        this(offset, limit, Sort.by(direction, properties));
    

    /**
     * Creates a new @link OffsetBasedPageRequest with sort parameters applied.
     *
     * @param offset zero-based offset.
     * @param limit  the size of the elements to be returned.
     */
    public OffsetBasedPageRequest(int offset, int limit) 
        this(offset, limit, Sort.unsorted());
    

    @Override
    public int getPageNumber() 
        return Math.toIntExact(offset / limit);
    

    @Override
    public int getPageSize() 
        return limit;
    

    @Override
    public long getOffset() 
        return offset;
    

    @Override
    public Sort getSort() 
        return sort;
    

    @Override
    public Pageable next() 
        return new OffsetBasedPageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
    

    public OffsetBasedPageRequest previous() 
        return hasPrevious() ? new OffsetBasedPageRequest(getOffset() - getPageSize(), getPageSize(), getSort()) : this;
    


    @Override
    public Pageable previousOrFirst() 
        return hasPrevious() ? previous() : first();
    

    @Override
    public Pageable first() 
        return new OffsetBasedPageRequest(0, getPageSize(), getSort());
    

    @Override
    public boolean hasPrevious() 
        return offset > limit;
    

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;

        if (!(o instanceof OffsetBasedPageRequest)) return false;

        OffsetBasedPageRequest that = (OffsetBasedPageRequest) o;

        return new EqualsBuilder()
                .append(limit, that.limit)
                .append(offset, that.offset)
                .append(sort, that.sort)
                .isEquals();
    

    @Override
    public int hashCode() 
        return new HashCodeBuilder(17, 37)
                .append(limit)
                .append(offset)
                .append(sort)
                .toHashCode();
    

    @Override
    public String toString() 
        return new ToStringBuilder(this)
                .append("limit", limit)
                .append("offset", offset)
                .append("sort", sort)
                .toString();
    

【讨论】:

谢谢哥们!我制作了limitoffset final,因为它们不会改变。 IntelliJ 也声称这一点。 2.6.0 版本中的 Spring-boot-parent 还需要一个名为 withPage 的方法。如果有人需要,我会留在这里:@Override public Pageable withPage(int pageNumber) return new OffsetBasedPageRequest((long) pageNumber * getPageSize(), getPageSize(), getSort()); 【参考方案7】:

试试看:

public interface ContactRepository extends JpaRepository<Contact, Long> 

    @Query(value = "Select c.* from contacts c where c.username is not null order by c.id asc limit ?1,  ?2 ", nativeQuery = true)         
    List<Contact> findContacts(int offset, int limit);        

【讨论】:

【参考方案8】:

假设您同时进行过滤、排序和分页 下面@Query 会帮助你

    @Query(value = "SELECT * FROM table  WHERE firstname= ?1  or lastname= ?2 or age= ?3 or city= ?4 or "
        + " ORDER BY date DESC OFFSET ?8 ROWS FETCH NEXT ?9 ROWS ONLY" , nativeQuery = true)
List<JobVacancy> filterJobVacancyByParams(final String firstname, final String lastname,
        final String age, final float city,int offset, int limit);

【讨论】:

以上是关于Spring Data JPA 中的分页(限制和偏移)的主要内容,如果未能解决你的问题,请参考以下文章

spring data jpa 的分页以及条件查询

Spring Data JPA中带条件的分页查询

Spring提供的JPA的分页的功能,和动态搜索后进行显示的分页功能的设置

序列化表单为json对象,datagrid带额外参提交一次查询 后台用Spring data JPA 实现带条件的分页查询

Spring Data Cassandra 中的分页和排序查询

Spring中的分页