Content ITV PRO
This is Itvedant Content department
Business Scenario
In this lab, we will enhance the existing Medicare Doctor Search API by implementing pagination and sorting alongside the filtering features developed in the previous lab.
Pre-Lab Preparation
Phase 7 — Pagination & Sorting
What problem are we solving?
1
Module:
1) Additional Components in Project
GET /api/doctors10 doctors 100 doctors 1,000 doctors 50,000 doctors
Database - 50,000 Doctors Request page=0 size=6 -- Only 6 Doctors returned
Instead :
Understand Pagination
2
Page 0 → Doctors 1–6 Page 1 → Doctors 7–12 Page 2 → Doctors 13–18 Page 3 → Doctors 19–24 Page 4 → Doctor 25
Understand the Pagination Classes
3
We'll mainly use:
Represents:
Which page? How many records? How should records be sorted?
Creates that pagination request.
Doctor records + Current page + Page size + Total elements + Total pages + Whether last page
contains:
Add Pagination Response DTO
4
dto/ └── DoctorPageResponse.java
package com.medicare.dto;
import java.util.List;
public class DoctorPageResponse { private List<DoctorResponse> doctors;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
public DoctorPageResponse(
List<DoctorResponse> doctors,
int pageNumber,
int pageSize,
long totalElements,
int totalPages,
boolean last) {
this.doctors = doctors;
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalElements = totalElements;
this.totalPages = totalPages;
this.last = last;
}
public List<DoctorResponse> getDoctors() {
return doctors;
}
public int getPageNumber() {
return pageNumber;
} public int getPageSize() {
return pageSize;
}
public long getTotalElements() {
return totalElements;
}
public int getTotalPages() {
return totalPages;
}
public boolean isLast() {
return last;
}
}Update DoctorProfileRepository
5
package com.medicare.repository;
import java.math.BigDecimal;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.medicare.entity.DoctorProfile;
public interface DoctorProfileRepository extends JpaRepository<DoctorProfile, Long> {
boolean existsByUserId(Long userId);
@Query(""" SELECT d
FROM DoctorProfile d
JOIN d.user u
WHERE
(:specialization IS NULL
OR LOWER(d.specialization) = LOWER(:specialization))
AND
(:maxFee IS NULL
OR d.consultationFee <= :maxFee)
AND
(:name IS NULL
OR LOWER(u.name)
LIKE CONCAT(CONCAT('%', LOWER(:name)), '%'))
""") LIKE CONCAT(CONCAT('%', LOWER(:name)), '%'))
""")
Page<DoctorProfile> searchDoctors(
@Param("specialization")
String specialization,
@Param("maxFee")
BigDecimal maxFee,
@Param("name")
String name,
Pageable pageable
);
}How Pageable Works With Repository
6
Pageable pageable = PageRequest.of(page,size,sort
doctorRepository.searchDoctors( specialization,maxFee,name,pageable);
Filtering + Sorting + Limit + Offset + Total count
Decide Our Sorting Options
7
or: ?sortBy=somethingThatDoesNotExist
name specialization experienceYears consultationFee
Update DoctorService
8
public DoctorPageResponse searchDoctors(
String specialization,
BigDecimal maxFee,
String name,
int page,
int size,
String sortBy,
String direction) {
specialization = normalizeText(specialization);
name = normalizeText(name);
validatePagination(page,size);
if (maxFee != null && maxFee.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException(
"Maximum consultation fee must be greater than zero"
);
} String sortField = resolveSortField(sortBy);
Sort.Direction sortDirection = resolveSortDirection(direction);
Sort sort = Sort.by(sortDirection,sortField);
Pageable pageable = PageRequest.of(page,size,sort);
Page<DoctorProfile> doctorPage = doctorRepository.searchDoctors(
specialization, maxFee, name, pageable);
List<DoctorResponse> doctorResponses = new ArrayList<>();
for (DoctorProfile doctor : doctorPage.getContent()) {
doctorResponses.add( mapToDoctorResponse(doctor));
}
return new DoctorPageResponse(
doctorResponses,
doctorPage.getNumber(),
doctorPage.getSize(),
doctorPage.getTotalElements(),
doctorPage.getTotalPages(),
doctorPage.isLast()
);
}import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import com.medicare.dto.DoctorPageResponse;Add Pagination Validation
9
private void validatePagination( int page, int size) {
if (page < 0) {
throw new IllegalArgumentException( "Page number cannot be negative");
} if (size < 1 || size > 50) {
throw new IllegalArgumentException("Page size must be between 1 and 50");
}
}Because otherwise somebody could request: ?size=1000000 which defeats the entire purpose of pagination.
Add Sort Field Validation
10
private String resolveSortField(
String sortBy) {
if (sortBy == null || sortBy.isBlank()) { return "consultationFee";
}
return switch (sortBy) {
case "name" -> "user.name";
case "specialization" -> "specialization";
case "experienceYears" -> "experienceYears";
case "consultationFee" -> "consultationFee";
default -> throw new IllegalArgumentException("Invalid sort field");
};
}Validate Sorting Direction
11
private Sort.Direction resolveSortDirection(String direction) {
if (direction == null || direction.isBlank() || direction.equalsIgnoreCase("asc")) {
return Sort.Direction.ASC;
}
if (direction.equalsIgnoreCase("desc")) { return Sort.Direction.DESC;
}
throw new IllegalArgumentException( "Sort direction must be asc or desc");
}asc ASC Asc desc DESC
up down abc
Updated DoctorService Imports
12
Update DoctorController
13
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;@GetMapping
public ResponseEntity<List<DoctorResponse>>searchDoctors(...)@GetMapping
public ResponseEntity<DoctorPageResponse> searchDoctors(
@RequestParam(required = false)
String specialization,
@RequestParam(required = false)
BigDecimal maxFee,
@RequestParam(required = false)
String name,
@RequestParam(defaultValue = "0")
int page,
@RequestParam(defaultValue = "6")
int size,
@RequestParam(defaultValue = "consultationFee")
String sortBy,
@RequestParam(defaultValue = "asc")
String direction) { DoctorPageResponse response = doctorService.searchDoctors(
specialization, maxFee, name, page, size, sortBy, direction
);
return ResponseEntity.ok(response);
}Our Default Request
14
page = 0 size = 6 sortBy = consultationFee direction = asc
Test Basic Pagination
15
GE - http://localhost:8080/api/doctors?page=0&size=3
{
"doctors": [
{
"id": 2,
"name": "Dr. Rohan Mehta",
"specialization": "Dentist",
"experienceYears": 5,
"consultationFee": 700.00,
"photoUrl": "https://example.com/rohan.jpg"
},
{
"id": 3,
"name": "Dr. Priya Nair",
"specialization": "General Physician",
"experienceYears": 10,
"consultationFee": 900.00,
"photoUrl": "https://example.com/priya.jpg"
},
{
"id": 5,
"name": "Dr. Sneha Kulkarni",
"specialization": "Dermatologist",
"experienceYears": 7,
"consultationFee": 1000.00,
"photoUrl": "https://example.com/sneha.jpg"
} ], "pageNumber": 0, "pageSize": 3, "totalElements": 8, "totalPages": 3, "last": false }
GET - http://localhost:8080/api/doctors?page=0&size=3
{
"doctors": [
...
],
"pageNumber": 1,
"pageSize": 3,
"totalElements": 8,
"totalPages": 3,
"last": false
}GET /api/doctors?page=2&size=3
{
"pageNumber": 2,
"pageSize": 3,
"totalElements": 8,
"totalPages": 3,
"last": true
}Create DoctorResponse
16
GET - /api/doctors?sortBy=consultationFee&direction=asc
₹500 ₹700 ₹900 ₹1000
GET - /api/doctors?sortBy=consultationFee&direction=desc
₹1000 ₹900 ₹700 ₹500
Sort by Experience
17
GET /api/doctors?sortBy=experienceYears&direction=desc
Sort by Doctor Name
18
GET /api/doctors?sortBy=name&direction=asc
Filters + Pagination Together
19
GET /api/doctors?specialization=Cardiologist&page=0&size=6
Find Cardiologists ↓ Paginate Results ↓ Return First 6
Filters + Sorting + Pagination
20
GET /api/doctors?specialization=Cardiologist&maxFee=1500&name=ananya&page=0&size=6&sortBy=consultationFee&direction=asc
Test Invalid Page
21
GET /api/doctors?page=-1
400 BAD REQUESTTest Invalid Page Size
22
GET /api/doctors?size=0
400 BAD REQUESTTest Invalid Sort Property
23
GET /api/doctors?sortBy=randomField
400 BAD REQUESTInvalid sort field
Test Invalid Direction
24
GET /api/doctors?direction=up
400 BAD REQUESTSort direction must be asc or desc
What If Page Doesn't Exist?
25
GET /api/doctors?page=20&size=6
{
"doctors": [],
"pageNumber": 20,
"pageSize": 6,
"totalElements": 8,
"totalPages": 2,
"last": true
}We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You have successfully implemented pagination and sorting in the Medicare Doctor Search API using Spring Data JPA.
Checkpoint
Git Push
git push origin branchNameNext-Lab Preparation
Module:
1) Mastering Springboot Security
By Content ITV