The Spring Data JPA provides a useful Pageable interface that provides number of methods for building pagination in the UI side. Even you can sort the data returned by the JPA query using the methods provided in this interface API.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>springboot-jpa-pageable</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
---------------------------------- ProductRestController -------------------------------
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ramsis.springboot.jpa.pageable.dto.ProductDto;
import com.ramsis.springboot.jpa.pageable.service.ProductService;
@RestController
@CrossOrigin(origins = "*")
public class ProductRestController {
@Autowired
private ProductService productService;
@GetMapping("/product")
public Page<ProductDto> products(Pageable pageable) {
return productService.products(pageable);
}
}
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column
private String name;
@Column
private String prise;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrise() {
return prise;
}
public void setPrise(String prise) {
this.prise = prise;
}
}
------------------------ EntityDtoConverter -------------------------
package com.ramsis.springboot.jpa.pageable.converter;
import com.ramsis.springboot.jpa.pageable.dto.ProductDto;
import com.ramsis.springboot.jpa.pageable.entity.Product;
public final class EntityDtoConverter {
private EntityDtoConverter() {
}
public static ProductDto entityToDto(Product product) {
ProductDto productDto = new ProductDto();
productDto.setId(product.getId());
productDto.setName(product.getName());
productDto.setPrise(product.getPrise());
return productDto;
}
public static Product dtoToEntity(ProductDto productDto) {
Product product = new Product();
product.setId(productDto.getId());
product.setName(productDto.getName());
product.setPrise(productDto.getPrise());
return product;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.ramsis.springboot.jpa.pageable.converter.EntityDtoConverter;
import com.ramsis.springboot.jpa.pageable.dto.ProductDto;
import com.ramsis.springboot.jpa.pageable.entity.Product;
import com.ramsis.springboot.jpa.pageable.repository.ProductRepository;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Page<ProductDto> products(Pageable pageable) {
Page<Product> products = productRepository.findAll(pageable);
Page<ProductDto> pages = products.map(entity -> {
ProductDto dto = EntityDtoConverter.entityToDto(entity);
return dto;
});
return pages;
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import com.ramsis.springboot.jpa.pageable.entity.Product;
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
$(document).ready(function() {
let totalPages = 1;
function fetchNotes(startPage) {
//console.log('startPage: ' +startPage);
/**
* get data from Backend's REST API
*/
$.ajax({
type : "GET",
url : "http://localhost:8080/product",
data: {
page: startPage,
size: 10
},
success: function(response){
$('#noteTable tbody').empty();
// add table rows
$.each(response.content, (i, product) => {
let noteRow = '<tr>' +
'<td>' + product.id + '</td>' +
'<td>' + product.name + '</td>' +
'<td>' + product.prise + '</td>' +
'</tr>';
$('#noteTable tbody').append(noteRow);
});
if ($('ul.pagination li').length - 2 != response.totalPages){
// build pagination list at the first time loading
$('ul.pagination').empty();
buildPagination(response);
}
},
error : function(e) {
alert("ERROR: ", e);
console.log("ERROR: ", e);
}
});
}
function buildPagination(response) {
totalPages = response.totalPages;
var pageNumber = response.pageable.pageNumber;
var numLinks = 10;
// print 'previous' link only if not on page one
var first = '';
var prev = '';
if (pageNumber > 0) {
if(pageNumber !== 0) {
first = '<li class="page-item"><a class="page-link">« First</a></li>';
}
prev = '<li class="page-item"><a class="page-link">‹ Prev</a></li>';
} else {
prev = ''; // on the page one, don't show 'previous' link
first = ''; // nor 'first page' link
}
// print 'next' link only if not on the last page
var next = '';
var last = '';
if (pageNumber < totalPages) {
if(pageNumber !== totalPages - 1) {
next = '<li class="page-item"><a class="page-link">Next ›</a></li>';
last = '<li class="page-item"><a class="page-link">Last »</a></li>';
}
} else {
next = ''; // on the last page, don't show 'next' link
last = ''; // nor 'last page' link
}
var start = pageNumber - (pageNumber % numLinks) + 1;
var end = start + numLinks - 1;
end = Math.min(totalPages, end);
var pagingLink = '';
for (var i = start; i <= end; i++) {
if (i == pageNumber + 1) {
pagingLink += '<li class="page-item active"><a class="page-link"> ' + i + ' </a></li>'; // no need to create a link to current page
} else {
pagingLink += '<li class="page-item"><a class="page-link"> ' + i + ' </a></li>';
}
}
// return the page navigation link
pagingLink = first + prev + pagingLink + next + last;
$("ul.pagination").append(pagingLink);
}
$(document).on("click", "ul.pagination li a", function() {
var data = $(this).attr('data');
let val = $(this).text();
console.log('val: ' + val);
// click on the NEXT tag
if(val.toUpperCase() === "« FIRST") {
let currentActive = $("li.active");
fetchNotes(0);
$("li.active").removeClass("active");
// add .active to next-pagination li
currentActive.next().addClass("active");
} else if(val.toUpperCase() === "LAST »") {
fetchNotes(totalPages - 1);
$("li.active").removeClass("active");
// add .active to next-pagination li
currentActive.next().addClass("active");
} else if(val.toUpperCase() === "NEXT ›") {
let activeValue = parseInt($("ul.pagination li.active").text());
if(activeValue < totalPages){
let currentActive = $("li.active");
startPage = activeValue;
fetchNotes(startPage);
// remove .active class for the old li tag
$("li.active").removeClass("active");
// add .active to next-pagination li
currentActive.next().addClass("active");
}
} else if(val.toUpperCase() === "‹ PREV") {
let activeValue = parseInt($("ul.pagination li.active").text());
if(activeValue > 1) {
// get the previous page
startPage = activeValue - 2;
fetchNotes(startPage);
let currentActive = $("li.active");
currentActive.removeClass("active");
// add .active to previous-pagination li
currentActive.prev().addClass("active");
}
} else {
startPage = parseInt(val - 1);
fetchNotes(startPage);
// add focus to the li tag
$("li.active").removeClass("active");
$(this).parent().addClass("active");
//$(this).addClass("active");
}
});
(function(){
// get first-page at initial time
fetchNotes(0);
})();
});
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap Ajax SpringBoot Pagination</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-7" style="background-color: #f6e796; margin: 5px; padding: 5px; border-radius: 5px; margin: auto;">
<div class="alert alert-warning">
<h4>SpringBoot Pagination + Bootstrap + jquery + Ajax </h4>
</div>
<table id="noteTable" class="table table-hover table-sm">
<thead class="thead-dark">
<tr>
<th>Id</th>
<th>Name</th>
<th>Prise</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<ul class="pagination justify-content-center" style="margin:10px 0; cursor: pointer;">
</ul>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.10.2/umd/popper.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="table-pagination.js"></script>
</body>
</html>