إذا عندكم Database فيها مئات الآلاف من الـ Records، ما بيصح ترجعوا كلها للـ Client بكل request. الحل هو الـ Pagination - تقسيم البيانات على صفحات.
الـ Pagination بيعتمد على متغيرين:
مثال على Collection فيها 7 عناصر:
| الصفحة | Offset | Size | النتيجة |
|---|---|---|---|
| الأولى | 0 | 3 | عناصر 1، 2، 3 |
| الثانية | 3 | 3 | عناصر 4، 5، 6 |
| الثالثة | 6 | 3 | عنصر 7 فقط |
للانتقال لصفحة ثانية، بس بنغير الـ Offset ونترك الـ Limit ثابت.
بنضيف @RequestParam لاستقبال الـ page والـ size من الـ Client:
@GetMapping
public ResponseEntity<GlobalResponse<Page<Employee>>> findAll(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "3") int size) {
var
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
الـ defaultValue بيضمن إنه لو الـ Client ما بعث قيمة، في قيمة افتراضية دايماً.
بنعدل الـ Interface:
public interface EmployeeService {
Page<Employee> findAll(int page, int size);
}والـ Implementation:
@Override
public Page<Employee> findAll(int page, int size) {
Pageable pageable = PageRequest.of(page - 1, size);
return employeeRepository.findAll(pageable);
}نطرح 1 من الـ page لأن PageRequest بيبلش من الصفر بينما نحن بنعرض للـ Client من الواحد.
بدل ما نرجع الـ Page<T> مباشرة، بننشئ DTO خاص فينا نتحكم بشو نرجعه للـ Client:
public record PaginatedResponse<T>(
List<T> content,
int currentPage,
int totalPages,
long totalItems,
boolean hasNext,
boolean hasPrevious,
String nextPageUrl,
String previousPageUrl
) {}الـ Generic Type <T> بيخلي الـ DTO يشتغل مع أي نوع بيانات - موظفين، منتجات، طلبات، ما بيهم.
@GetMapping
public ResponseEntity<GlobalResponse<PaginatedResponse<Employee>>> findAll(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "3") int size,
HttpServletRequest request) {
Page<Employee> employees = employeeService.findAll(page, size);
String baseUrl = request.getRequestURL().toString();
String nextPageUrl = employees.hasNext()
? String.format("%s?page=%d&size=%d", baseUrl, page + 1, size)
: null;
String previousPageUrl = employees.hasPrevious()
? String.format("%s?page=%d&size=%d", baseUrl, page - 1, size)
: null;
var response = new PaginatedResponse<>(
employees.getContent(),
employees.getNumber() + 1,
employees.getTotalPages(),
employees.getTotalElements(),
employees.hasNext(),
employees.hasPrevious(),
nextPageUrl,
previousPageUrl
);
return new ResponseEntity<>(new GlobalResponse<>(response), HttpStatus.OK);
}{
"data": {
"content": [...],
"currentPage": 1,
"totalPages": 3,
"totalItems": 7,
"hasNext": true,
"hasPrevious": false,
"nextPageUrl": "http://localhost:8080/employees?page=2&size=3",
"previousPageUrl": null
}
}الـ Client بيستخدم nextPageUrl وpreviousPageUrl للتنقل بين الصفحات بدون ما يحتاج يبني الـ URL بنفسه.
لو كانت هاي آخر صفحة، بتشوف hasNext: false وال nextPageUrl: null. ولو بالصفحة الأولى، hasPrevious: false.
طبقوا نفس الـ Pagination على باقي الـ Endpoints اللي عندكم حسب الحاجة.