Разбиение по страницам (Pagination) в Java Hibernate

1- Что такое пагинация?

Pagination (пагинация) это техника разделения списка нескольких записей (record) на подсписки. Например вы ищете ключевое слово на Google и получаете тысячи результатов. Но на каждой станице Google отображается для вас только 10 результатов, другие результаты будут отображены на следующих страницах.
В приложении  Java используя  Hibernate, команда запроса (query statement), можно вернуть список со многими записями (record) и вы задаете вопрос как взять только часть списка (записи с позиции  N1 до позиции  N2), одновременно получить информацию общего количества записей и общего количества страниц.

Hibernate vs JPA

Несмотря на то, что  Hibernate и JPA являются довольно похожими технологиями, если вы знаете про Hibernate вам легко работать с  JPA и наоборот. Но у них есть несколько различий. Одно из этих различий это  Hibernate предоставляет лучшую технику для пагинации чем  JPA, в данной статье я покажу вам как использовать технику пагинации у  Hibernate 5. Заметьте, что данная техника не поддерживается JPA.

2- Пагинация с Hibernate

Все что вам нужно сделать это создать объект  Query, и предоставить параметры  page, maxResult, maxNavigationResult чтобы создать объект  PaginationResult.
PaginationResult это утилитарный класс, который помогает вам выполнить запрос и вернуть список подходящих записей с параметрами выше,
PaginationResult.java
package org.o7planning.tutorial.hibernate.pagination;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.query.Query;

public class PaginationResult<E> {

    private int totalRecords;
    private int currentPage;
    private List<E> list;
    private int maxResult;
    private int totalPages;

    private int maxNavigationPage;

    private List<Integer> navigationPages;

    // @page: 1, 2, ..
    public PaginationResult(Query<E> query, int page, int maxResult, int maxNavigationPage) {
        final int pageIndex = page - 1 < 0 ? 0 : page - 1;

        int fromRecordIndex = pageIndex * maxResult;
        int maxRecordIndex = fromRecordIndex + maxResult;

        ScrollableResults resultScroll = query.scroll(ScrollMode.SCROLL_INSENSITIVE  );

        List<E> results = new ArrayList<E>();

        boolean hasResult =   resultScroll.first();

        if (hasResult) {
        
            // Scroll to position:
             hasResult = resultScroll.scroll(fromRecordIndex);

            if (hasResult) {
                do {
                    E record = (E) resultScroll.get(0);
                    results.add(record);
                } while (resultScroll.next()//
                        && resultScroll.getRowNumber() >= fromRecordIndex
                        && resultScroll.getRowNumber() < maxRecordIndex);

            }
 
            // Go to Last record.
             resultScroll.last();
        }
 
        // Total Records
        this.totalRecords = resultScroll.getRowNumber() + 1;
        this.currentPage = pageIndex + 1;
        this.list = results;
        this.maxResult = maxResult;

        if (this.totalRecords % this.maxResult == 0) {
            this.totalPages = this.totalRecords / this.maxResult;
        } else {
            this.totalPages = (this.totalRecords / this.maxResult) + 1;
        }

        this.maxNavigationPage = maxNavigationPage;

        if (maxNavigationPage < totalPages) {
            this.maxNavigationPage = maxNavigationPage;
        }
        resultScroll.close();

        this.calcNavigationPages();
    }

    private void calcNavigationPages() {

        this.navigationPages = new ArrayList<Integer>();

        int current = this.currentPage > this.totalPages ? this.totalPages : this.currentPage;

        int begin = current - this.maxNavigationPage / 2;
        int end = current + this.maxNavigationPage / 2;
 
        // The first page
        navigationPages.add(1);
        if (begin > 2) {

    
            // Using for '...'
            navigationPages.add(-1);
        }

        for (int i = begin; i < end; i++) {
            if (i > 1 && i < this.totalPages) {
                navigationPages.add(i);
            }
        }

        if (end < this.totalPages - 2) {
 
            // Using for '...'
            navigationPages.add(-1);
        }
    
        // The last page.
        navigationPages.add(this.totalPages);
    }

    public int getTotalPages() {
        return totalPages;
    }

    public int getTotalRecords() {
        return totalRecords;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public List<E> getList() {
        return list;
    }

    public int getMaxResult() {
        return maxResult;
    }

    public List<Integer> getNavigationPages() {
        return navigationPages;
    }

}

Пример:

Employee.java
package org.o7planning.tutorial.hibernate.entities;
 
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
 
@Entity
@Table(name = "EMPLOYEE", //
        uniqueConstraints = { @UniqueConstraint(columnNames = { "EMP_NO" }) })
public class Employee {
 
    @Id
    @Column(name = "EMP_ID")
    private Long empId;
 
    @Column(name = "EMP_NO", length = 20, nullable = false)
    private String empNo;
 
    @Column(name = "EMP_NAME", length = 50, nullable = false)
    private String empName;
 
    @Column(name = "JOB", length = 30, nullable = false)
    private String job;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MNG_ID")
    private Employee manager;
 
    @Column(name = "HIRE_DATE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date hideDate;
 
    @Column(name = "SALARY", nullable = false)
    private Float salary;
 
    @Lob
    @Column(name = "IMAGE", length = Integer.MAX_VALUE, nullable = true)
    private byte[] image;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "DEPT_ID", nullable = false)
    private Department department;
 
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "empId")
    private Set<Employee> employees = new HashSet<Employee>(0);
 
    // Getter & Setter
 
}
Department.java
package org.o7planning.tutorial.hibernate.entities;
 
import java.util.HashSet;
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
 
@Entity
@Table(name = "DEPARTMENT", //
        uniqueConstraints = { @UniqueConstraint(columnNames = { "DEPT_NO" }) })
public class Department {
    @Id
    @Column(name = "DEPT_ID")
    private Integer deptId;
 
    @Column(name = "DEPT_NO", length = 20, nullable = false)
    private String deptNo;
    @Column(name = "DEPT_NAME", nullable = false)
    private String deptName;
 
    @Column(name = "LOCATION")
    private String location;
 
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
    private Set<Employee> employees = new HashSet<Employee>(0);
 
    public Department() {
    }  
 
    // Getter & Setter
}
EmployeeInfo.java
package org.o7planning.tutorial.hibernate.beans;

public class EmployeeInfo {

    private Long empId;

    private String empNo;

    private String empName;

    private String deptNo;

    private String deptName;

    public EmployeeInfo() {

    }

    // This constructor is used by Hibernate Query.
    public EmployeeInfo(Long empId, String empNo, String empName, String deptNo, String deptName) {
        this.empId = empId;
        this.empNo = empNo;
        this.empName = empName;
        this.deptNo = deptNo;
        this.deptName = deptName;
    }

    // Getter & Setter
}

Пример 1:

PaginationExample1.java
package org.o7planning.tutorial.hibernate.pagination;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.o7planning.tutorial.hibernate.HibernateUtils;
import org.o7planning.tutorial.hibernate.entities.Employee;

public class PaginationExample1 {

    public static void main(String[] args) {

        // Have sessionFactory object...
        SessionFactory factory = HibernateUtils.getSessionFactory();

        Session session = factory.getCurrentSession();

        String sql = "Select e from " + Employee.class.getName() + " e " //
                + " Where e.empId > :empId ";
        Query<Employee> query = session.createQuery(sql, Employee.class);
        query.setParameter("empId", 100);

        int page = 1;
        int maxResult = 20;
        int maxNavigationResult = 10;

        PaginationResult<Employee> result = new PaginationResult<Employee>(query, page, maxResult, maxNavigationResult);

        // Result:
        List<Employee> emps = result.getList();
        int totalPages = result.getTotalRecords();
        int totalRecords = result.getTotalRecords();

        // 1 2 3 4 5 ... 11 12 13
        List<Integer> navPages = result.getNavigationPages();

    }

}

Пример 2:

PaginationExample2.java
package org.o7planning.tutorial.hibernate.pagination;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.o7planning.tutorial.hibernate.HibernateUtils;
import org.o7planning.tutorial.hibernate.beans.EmployeeInfo;
import org.o7planning.tutorial.hibernate.entities.Employee;

public class PaginationExample2 {

    public static void main(String[] args) {

        // Get sessionFactory object...
        SessionFactory factory = HibernateUtils.getSessionFactory();

        Session session = factory.getCurrentSession();

        String sql = "Select new " + EmployeeInfo.class.getName() //
                + " (e.empId,e.empNo,e.empName,d.deptNo,d.deptName) " //
                + " from " + Employee.class.getName() + " e " //
                + " Join e.department d " //
                + " Order by e.empNo ";
        Query<EmployeeInfo> query = session.createQuery(sql, EmployeeInfo.class);

        int page = 1;
        int maxResult = 20;
        int maxNavigationResult = 10;

        PaginationResult<EmployeeInfo> result
             = new PaginationResult<EmployeeInfo>(query, page, maxResult, maxNavigationResult);

        // Result:
        List<EmployeeInfo> emps = result.getList();
        int totalPages = result.getTotalRecords();
        int totalRecords = result.getTotalRecords();

        // 1 2 3 4 5 ... 11 12 13
        List<Integer> navPages = result.getNavigationPages();

    }

}
И пример .... сниппет кода  JSP используя  PaginationResult:
JSP Pagination
<c:if test="${paginationProducts.totalPages > 1}">
   <div class="page-navigator">
      <c:forEach items="${paginationProducts.navigationPages}" var = "page">
         <c:if test="${page != -1 }">
            <a href="productList?page=${page}" class="nav-item">${page}</a>
         </c:if>
         <c:if test="${page == -1 }">
            <span class="nav-item"> ... </span>
         </c:if>
      </c:forEach>
   </div>
</c:if>