پرش به محتوای اصلی
مسیر تخصصی جاوا و Spring
درخواست پروژهارتباط با ماراهنما
جاواپرویاد بگیر، تمرین کن، حرفه‌ای بساز
خانهدوره‌هاآموزش رایگانوبلاگمدرس‌هاخدمات
خانهچطور قابلیت صفحه‌بندی (Paging) را در اسپرینگ بوت پیاده‌سازی کنیم
وبلاگ »

چطور قابلیت صفحه‌بندی (Paging) را در اسپرینگ بوت پیاده‌سازی کنیم

در این مطلب قرار است یاد بگیریم چطور می‌توانیم دو رویکرد مختلف — Paging و Skip/Top — را در یک پروژه‌ی اسپرینگ بوت با استفاده از JPA و JOOQ پیاده‌سازی کنیم.

۴ دقیقه مطالعهوبلاگ »آموزش جاوا و انجام پروژه های برنامه نویسی
چطور قابلیت صفحه‌بندی (Paging) را در اسپرینگ بوت پیاده‌سازی کنیم
محتوای منتخب جاواپرو

خلاصه مطلب

در این مطلب قرار است یاد بگیریم چطور می‌توانیم دو رویکرد مختلف — Paging و Skip/Top — را در یک پروژه‌ی اسپرینگ بوت با استفاده از JPA و JOOQ پیاده‌سازی کنیم.
نوع محتوامقاله آموزشی
موضوعوبلاگ »
زمان مطالعه۴ دقیقه
آخرین ویرایش۲۴ شهریور ۱۴۰۴

وقتی در برنامه نقاط پایانی (endpoint) می‌سازیم که مجموعه‌ای از موجودیت‌ها (entities) را برمی‌گردانند، خیلی مهم است که صفحه‌بندی را پیاده‌سازی کنیم. در غیر این صورت ممکن است با مشکلاتی مثل طولانی شدن زمان پاسخ، مصرف بیش از حد حافظه و حتی بعضی حملات (مثل حملات انکار سرویس با درخواست‌های خیلی بزرگ) روبه‌رو شویم.

در این مطلب قرار است یاد بگیریم چطور می‌توانیم دو رویکرد مختلف — Paging و Skip/Top — را در یک پروژه‌ی اسپرینگ بوت با استفاده از JPA و JOOQ پیاده‌سازی کنیم.

صفحه‌بندی (Paging) را در اسپرینگ بوت
صفحه‌بندی (Paging) را در اسپرینگ بوت

مقایسه‌ی Paging و Skip/Top

در روش Paging، نقطه پایانی (endpoint) طوری طراحی می‌شود که دو پارامتر دریافت کند: شماره‌ی صفحه (page number) و اندازه‌ی صفحه (page size). این دو پارامتر مشخص می‌کنند کدام بخش از داده‌ها باید برگردانده شوند.

این مقاله جذاب و کاربردی در زمینه Spring Boot را از دست ندهید
این مقاله جذاب و کاربردی در زمینه Spring Boot را از دست ندهید

معمولاً در پاسخ‌های Paging علاوه بر داده‌های انتخاب‌شده، یک‌سری اطلاعات کمکی (metadata) هم وجود دارد؛ مثل اندازه‌ی صفحه، تعداد کل رکوردها و شماره‌ی صفحه‌ی فعلی.

از طرف دیگر، در روش Skip/Top دو پارامتر وجود دارد که نتیجه‌ی مشابهی به دست می‌دهند. در این حالت شما مشخص می‌کنید چند رکورد باید رد شوند (skip) و چند رکورد باید برگردند (top). به‌عنوان مثال، وقتی می‌گوییم skip 10 و top 20 یعنی ۱۰ رکورد اول کنار گذاشته شود و ۲۰ رکورد بعدی بازگردانده شود. این روش معمولاً فقط لیست داده‌ها را برمی‌گرداند و خبری از اطلاعات تکمیلی (metadata) نیست.

Controller

قبل از اینکه سراغ کد مخزن (Repository) برویم ـ جایی که اصل کار انجام می‌شود ـ بیایید نگاهی به کلاس Controller بیندازیم. این کلاس چهار endpoint دارد:

JPA همراه با صفحه‌بندی (pagination)

JPA همراه با skip و top

JOOQ همراه با صفحه‌بندی (pagination)

JOOQ همراه با skip و top

@RestController @RequestMapping("/products") @RequiredArgsConstructor public class ProductController { private final ProductService productService; private final ProductServiceJooq productServiceJooq; @GetMapping("/jpa/withPage") @ResponseStatus(HttpStatus.OK) public Page findAll( @RequestParam(name = "page") int page, @RequestParam(name = "pageSize") int size) { return productService.findByPage(page, size); } @GetMapping("/jpa/withSkipAndTop") @ResponseStatus(HttpStatus.OK) public Iterable findWithSkipAndTop( @RequestParam(name = "skip") int skip, @RequestParam(name = "top") int top) { return productService.findWithSkipAndTop(skip, top); } @GetMapping("/jooq/withPage") @ResponseStatus(HttpStatus.OK) public Page findAllJooq( @RequestParam(name = "page") int page, @RequestParam(name = "pageSize") int size) { return productServiceJooq.findWithPageable(page, size); } @GetMapping("/jooq/withSkipAndTop") @ResponseStatus(HttpStatus.OK) public Iterable findWithSkipAndTopJooq( @RequestParam(name = "skip") int skip, @RequestParam(name = "top") int top) { return productServiceJooq.findWithSkipAndTop(skip, top); } }

اینجا دو کلاس سرویس داریم: یکی برای JPA و دیگری برای JOOQ.

کاربران جاوا پرو که علاقه‌مند به یادگیری Spring Boot هستند، در دوره‌های زیر ثبت‌نام کرده‌اند:

Paging با JPA

خود JPA به‌صورت داخلی از صفحه‌بندی پشتیبانی می‌کند، بنابراین پیاده‌سازی آن خیلی ساده است. کافی است در مخزن (Repository) متدی تعریف کنید که یک پارامتر از نوع Pageable بگیرد و یک Page برگرداند.

در ادامه، کلاس سرویس متد مخزن را صدا می‌زند و از یک PageRequest که با شماره صفحه (page number) و اندازه صفحه (page size) ساخته شده، استفاده می‌کند.

@Repository public interface ProductRepository extends CrudRepository { Page findAll(Pageable pageable); }@Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; public Page findByPage(int page, int size) { return productRepository.findAll(PageRequest.of(page, size)); }}

پاسخ به این شکل خواهد بود:

{ "content": [ { "id": 26, "name": "Product 1", "description": "Description for Product 1", "price": 10.99, "image": "image1.jpg", ... }, { "id": 27, "name": "Product 2", "description": "Description for Product 2", "price": 15.49, "image": "image2.jpg", ... }, ... ], "pageable": { "pageNumber": 0, "pageSize": 10, "sort": { "empty": true, "sorted": false, "unsorted": true }, "offset": 0, "unpaged": false, "paged": true }, "last": false, "totalPages": 3, "totalElements": 25, "first": true, "size": 10, "number": 0, "sort": { "empty": true, "sorted": false, "unsorted": true }, "numberOfElements": 10, "empty": false }

Skip و Top با JPA

خود JPA به‌صورت پیش‌فرض از skip و top پشتیبانی نمی‌کند، بنابراین برای رسیدن به این قابلیت باید از راهکارهای جایگزین استفاده کنیم. یکی از گزینه‌ها این است که شماره صفحه و اندازه صفحه را بر اساس مقادیر skip و top محاسبه کنیم؛ هرچند این روش همیشه به‌طور قابل اعتماد جواب نمی‌دهد. راه‌حل بهتر این است که یک Pageable سفارشی پیاده‌سازی کنیم و با بازنویسی متدهای لازم، رفتار درست را ایجاد کنیم.

@RequiredArgsConstructor(staticName = "of") public class PageableWithSkipAndTop implements Pageable { private final int skip; private final int top; private final Sort sort = Sort.unsorted(); @Override public int getPageNumber() { return 0; } @Override public int getPageSize() { return top; } @Override public long getOffset() { return skip; } @Override public Sort getSort() { return sort; } @Override public Pageable next() { return this; } @Override public Pageable previousOrFirst() { return this; } @Override public Pageable first() { return this; } @Override public Pageable withPage(int pageNumber) { return this; } @Override public boolean hasPrevious() { return false; } }

با این کار، کلاس سرویس تا حد امکان ساده باقی می‌ماند — فقط یک نمونه از PageableWithSkipAndTop می‌سازد و یک لیست از Products برمی‌گرداند.

@Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; public List findWithSkipAndTop(int skip, int top) { return productRepository.findAll(PageableWithSkipAndTop.of(skip, top)).getContent(); } }

JOOQ با Skip و Top

از طرف دیگر، خود JOOQ به‌صورت داخلی از skip و top پشتیبانی می‌کند. فقط کافی است این عبارت‌ها را مستقیماً داخل کوئری به‌کار ببرید، همان‌طور که در پیاده‌سازی مخزن (Repository) نشان داده شده است.

@Repository @RequiredArgsConstructor public class ProductRepositoryJooq { private final DSLContext dslContext; public List findWithSkipAndTop(int skip, int top) { return dslContext .select() .from(ProductTable.PRODUCT) .offset(skip) .limit(top) .fetchinto(ProductRecord.class); } }

مخزن (Repository) از یک کلاس ProductTable استفاده می‌کند که تعریف جدول در JOOQ را در خود دارد و همچنین یک ProductRecord DTO برای نگهداری نتایج کوئری.

کلاس سرویس هم خیلی ساده است:

@Service @RequiredArgsConstructor public class ProductServiceJooq { private final ProductRepositoryJooq productRepositoryJooq; public List findWithSkipAndTop(int skip, int top) { return productRepositoryJooq.findWithSkipAndTop(skip, top); } }

JOOQ با Pagination

اینجا کمی قضیه پیچیده‌تر می‌شود. از آنجایی که JOOQ به‌صورت پیش‌فرض مثل JPA از صفحه‌بندی پشتیبانی نمی‌کند، باید از limit و OFFSET برای گرفتن رکوردهای درست استفاده کنید. علاوه بر این، نیاز است دو کوئری اجرا شود: یکی برای گرفتن تعداد کل رکوردها و دیگری برای دریافت صفحه‌ی واقعی نتایج.

@Repository @RequiredArgsConstructor public class ProductRepositoryJooq { private final DSLContext dslContext; public Page findWithPageable(int page, int size) { Pageable pageable = PageRequest.of(page, size); List records = dslContext .selectfrom(ProductTable.PRODUCT) .limit(pageable.getPageSize()) .offset((int) pageable.getOffset()) .fetchinto(ProductRecord.class); int total = dslContext.fetchCount(DSL.select().from(ProductTable.PRODUCT)); return new PageImpl(records, pageable, total); }}

خلاصه

برای پیاده‌سازی صفحه‌بندی می‌توانید از JPA یا JOOQ استفاده کنید، اما چند تفاوت مهم وجود دارد:

اگر قصد دارید از صفحه‌بندی مبتنی بر شماره صفحه استفاده کنید، JPA گزینه‌ی بهتری است.

اگر نیاز به پیاده‌سازی skip و top دارید، JOOQ مناسب‌تر است.

منبع: نوشته شده توسط Renan Schmitt

برچسب‌هاوبلاگ »مقالات جاوا »اسپرینگ بوتspring bootspringصفحه‌بندی در اسپرینگ بوتPaging در spring bootPaging در اسپرینگ بوتجاواjavaدوره spring bootدوره اسپرینگ بوتآموزش اسپرینگ بوتاستفاده از JPA
ادامه مسیر

پیشنهاد جاواپرو

موضوع را با آموزش‌های مرتبط ادامه دهید و نکته‌های کاربردی را در مسیر خود نگه دارید

مشاهده دوره‌ها
مطالب پیشنهادی

ادامه مطالعه

xدسته‌بندی نشده
۱۱ دی ۱۳۹۸ مطالعه کوتاه

x

پادکست شماره 3 – راه های کسب درآمد از برنامه نویسی

مطالعه مطلب
پادکست شماره 3 - راه های کسب درآمد از برنامه نویسیپادکست
۲۹ اردیبهشت ۱۴۰۴ مطالعه کوتاه

پادکست شماره 3 - راه های کسب درآمد از برنامه نویسی

در این پادکست برنامه نویسی به شیوه ها و راه های کسب درآمد از برنامه نویسی پرداخته ایم. یک کاغذ و قلم بردارید و به پادکست گوش بدید.

مطالعه مطلب
پادکست شماره 2 - چگونه وارد فضای برنامه نویسی شویم؟پادکست
۲۹ اردیبهشت ۱۴۰۴ مطالعه کوتاه

پادکست شماره 2 - چگونه وارد فضای برنامه نویسی شویم؟

در این پادکست برنامه نویسی یاد میگیریم که چگونه وارد فضای برنامه نویسی شویم و تخصص برنامه نویسی را به دست آوریم.

مطالعه مطلب
جاواپرومرجع تخصصی یادگیری و توسعهٔ جاوا

تجربه‌ای منظم، ساده و حرفه‌ای برای یادگیری عمیق جاوا، Spring و مهندسی نرم‌افزار؛ قابل استفاده در موبایل، تبلت و دسکتاپ

یادگیری

همه دوره‌هاآموزش رایگانمسیر یادگیریمدرس‌ها

جاواپرو

درباره ماخدمات تخصصیدرخواست پروژهبلاگ فنی

پشتیبانی

تماس با ماپرسش‌های متداولقوانین و حریم خصوصیثبت تیکت
خرید امنپرداخت فقط در درگاه رسمیپشتیبانی آموزشیمسیر ارتباط شفاف و پیگیری‌پذیردسترسی پایداروب‌اپ واکنش‌گرا روی همه دستگاه‌ها
© ۱۴۰۵ جاواپرو؛ همه حقوق محفوظ استتوسعه توسط پایاپینگ
خانهدوره‌هاسبد