עמוד המנוחה במנוחה באביב

REST למעלה

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

1. סקירה כללית

הדרכה זו תתמקד ב יישום עימוד ב- API של REST, באמצעות Spring MVC ו- Spring Data.

2. עמוד כמשאב לעומת עמוד כייצוג

השאלה הראשונה בעת תכנון עימוד בהקשר של ארכיטקטורה RESTful היא האם לשקול את דף משאב בפועל או סתם ייצוג משאבים.

ההתייחסות לדף עצמו כאל משאב מציגה שורה של בעיות כגון אי היכולת לזהות באופן ייחודי משאבים בין שיחה. זאת, יחד עם העובדה שבשכבת ההתמדה, העמוד אינו ישות נכונה אלא בעל שנבנה בעת הצורך, הופכים את הבחירה לפשוטה: הדף הוא חלק מהייצוג.

השאלה הבאה בתכנון העמודים בהקשר ל- REST היא היכן לכלול את מידע ההחלפה:

  • בנתיב ה- URI: / foo / עמוד / 1
  • שאילתת URI: / foo? page = 1

יש לזכור את זה דף אינו משאב, קידוד פרטי העמוד ב- URI אינו עוד אפשרות.

אנו נשתמש בדרך הסטנדרטית לפתרון בעיה זו על ידי קידוד מידע ההחלפה בשאילתת URI.

3. הבקר

עכשיו, ליישום - בקר ה- MVC הקפיץ לעמוד הוא פשוט:

@GetMapping (params = {"page", "size"}) ציבורי רשימה findPaginated (@RequestParam ("page") דף int, @RequestParam ("size") גודל int, UriComponentsBuilder uriBuilder, תגובה HttpServletResponse) {דף resultPage = שירות .findPaginated (עמוד, גודל); if (page> resultPage.getTotalPages ()) {זרוק MyResourceNotFoundException חדש (); } eventPublisher.publishEvent (PaginatedResultsRetrievedEvent חדש (Foo.class, uriBuilder, תגובה, עמוד, resultPage.getTotalPages (), גודל); return resultPage.getContent (); }

בדוגמה זו אנו מזריקים את שני פרמטרי השאילתה, גודל ו עמוד, בשיטת הבקר באמצעות @ RequestParam.

לחלופין, היינו יכולים להשתמש ב- תומכי אובייקט, הממפה את עמוד, גודל, ו סוג פרמטרים באופן אוטומטי. בנוסף PagingAndSortingRepository ישות מספקת שיטות מחוץ לקופסה התומכות בשימוש ב- תומכי כפרמטר גם כן.

אנחנו גם מזריקים גם את תגובת Http וגם את UriComponentsBuilder כדי לעזור לגילוי - שאותו אנו מפרקים באמצעות אירוע מותאם אישית. אם זו לא מטרת ה- API, תוכל פשוט להסיר את האירוע המותאם אישית.

לסיום - שימו לב שהמוקד של מאמר זה הוא רק ה- REST ושכבת האינטרנט - כדי להעמיק יותר בחלק הגישה לנתונים של עימוד תוכלו לבדוק מאמר זה אודות Pagination with Spring Data.

4. גילוי של עמוד REST

במסגרת עימוד, סיפוק ה אילוץ של HATEOAS של REST פירושו לאפשר ללקוח ה- API לגלות את ה- הַבָּא ו קודם דפים המבוססים על העמוד הנוכחי בניווט. למטרה זו, אנחנו הולכים להשתמש ב- קישור כותרת HTTP, יחד עם "הַבָּא“, “קודמת“, “ראשון"ו"אחרון”סוגי קשרי קישור.

במנוחה, גילוי הוא עניין רוחבי, חלים לא רק על פעולות ספציפיות אלא על סוגי פעולות. לדוגמא, בכל פעם שמשאב נוצר, הלקוח אמור לגלות את ה- URI של אותו משאב. מכיוון שדרישה זו רלוונטית ליצירת משאבים כלשהם, אנו נטפל בה בנפרד.

אנו ננתק את החששות הללו באמצעות אירועים, כפי שדנו במאמר הקודם תוך התמקדות בגילוי השירות של REST. במקרה של עימוד, האירוע - PaginatedResultsRetrievedEvent - מופעל בשכבת הבקר. לאחר מכן נבצע יכולת גילוי עם מאזין מותאם אישית לאירוע זה.

בקיצור, המאזין יבדוק אם הניווט מאפשר א הַבָּא, קודם, ראשון ו אחרון עמודים. אם כן - כן הוסף את ה- URI הרלוונטיים לתגובה ככותרת HTTP של 'קישור'.

בואו נעבור שלב אחר שלב. ה UriComponentsBuilder שהועבר מהבקר מכיל רק את כתובת ה- URL הבסיסית (המארח, היציאה ונתיב ההקשר). לכן, נצטרך להוסיף את החלקים הנותרים:

void addLinkHeaderOnPagedResourceRetrieval (UriComponentsBuilder uriBuilder, HttpServletResponse response, Class clazz, int int, int totalPages, int size) {String resourceName = clazz.getSimpleName (). toString (). toLowerCase (); uriBuilder.path ("/ admin /" + resourceName); // ...}

לאחר מכן נשתמש ב- StringJoiner לשרשור כל קישור. נשתמש ב- uriBuilder כדי ליצור את ה- URI. בואו נראה איך נמשיך עם הקישור ל- הַבָּא עמוד:

StringJoiner linkHeader = StringJoiner חדש (","); if (hasNextPage (page, totalPages)) {String uriForNextPage = constructNextPageUri (uriBuilder, page, size); linkHeader.add (createLinkHeader (uriForNextPage, "הבא")); }

בואו נסתכל על ההיגיון של ה- constructNextPageUri שיטה:

מחרוזת constructNextPageUri (UriComponentsBuilder uriBuilder, דף int, גודל int) {החזר uriBuilder.replaceQueryParam (PAGE, דף + 1) .replaceQueryParam ("גודל", גודל) .build (). קוד () .toUriString (); }

נמשיך באופן דומה לשאר ה- URI שאנו רוצים לכלול.

לבסוף, נוסיף את הפלט ככותרת תגובה:

response.addHeader ("קישור", linkHeader.toString ());

שים לב שלקצרה, כללתי כאן רק דוגמת קוד חלקית ואת הקוד המלא.

5. עמוד נהיגה למבחן

ההיגיון העיקרי של עימוד וגילוי מכוסים במבחני אינטגרציה קטנים וממוקדים. כמו במאמר הקודם, נשתמש בספריה המובטחת על ידי REST לצרוך את שירות REST ולאמת את התוצאות.

אלה כמה דוגמאות למבחני שילוב עימוד; לחבילת מבחן מלאה, עיין בפרויקט GitHub (קישור בסוף המאמר):

@ מבחן ציבורי בטל כאשר ResourceAreRetrievedPaged_then200IsReceived () {תגובה תגובה = RestAssured.get (paths.getFooURL () + "? Page = 0 & size = 2"); assertThat (response.getStatusCode (), הוא (200)); } @Test הציבור בטל כאשרPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived () {String url = getFooURL () + "? Page =" + randomNumeric (5) + "& size = 2"; תגובת תגובה = RestAssured.get.get (url); assertThat (response.getStatusCode (), הוא (404)); } @Test ציבורי בטל givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources () {createResource (); תגובה תגובה = RestAssured.get (paths.getFooURL () + "? Page = 0 & size = 2"); assertFalse (response.body (). as (List.class) .isEmpty ()); }

6. יכולת גילוי של איתור במבחן נהיגה

בדיקה שהלקוח נגלה על ידי הלקוח היא פשוטה יחסית, אם כי יש הרבה אדמות לכסות.

המבחנים יתמקדו במיקום הדף הנוכחי בניווט ואת ה- URIs השונים שאפשר לגלות מכל עמדה:

@ מבחן פומבי בטל כאשר FirstPageOfResourcesAreRetrieved_thenSecondPageIsNext () {תגובה תגובה = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); מחרוזת uriToNextPage = extractURIByRel (response.getHeader ("קישור"), "הבא"); assertEquals (getFooURL () + "? page = 1 & size = 2", uriToNextPage); } @Test ציבורי בטל כאשרFirstPageOfResourcesAreRetrieved_thenNoPreviousPage () {תגובה תגובה = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); מחרוזת uriToPrevPage = extractURIByRel (response.getHeader ("קישור"), "הקודם"); assertNull (uriToPrevPage); } @Test ציבורי בטל whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious () {תגובה תגובה = RestAssured.get (getFooURL () + "? Page = 1 & size = 2"); מחרוזת uriToPrevPage = extractURIByRel (response.getHeader ("קישור"), "הקודם"); assertEquals (getFooURL () + "? page = 0 & size = 2", uriToPrevPage); } @Test ציבורי בטל כאשרLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable () {תגובה ראשונה = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); מחרוזת uriToLastPage = extractURIByRel (first.getHeader ("קישור"), "אחרון"); תגובת תגובה = RestAssured.get (uriToLastPage); מחרוזת uriToNextPage = extractURIByRel (response.getHeader ("קישור"), "הבא"); assertNull (uriToNextPage); }

שים לב שהקוד המלא ברמה נמוכה עבור extractURIByRel - אחראי על חילוץ ה- URI על ידי rel היחס נמצא כאן.

7. קבלת כל המשאבים

באותו נושא של עימוד וגילוי, הבחירה חייבת להיעשות אם לקוח רשאי לאחזר את כל המשאבים במערכת בבת אחת, או אם הלקוח צריך לבקש אותם בעמוד.

אם נעשתה הבחירה שהלקוח לא יכול לאחזר את כל המשאבים בבקשה אחת, וההחלפה אינה אופציונלית אך נדרשת, אז קיימות מספר אפשרויות לתגובה לבקשה לקבל הכל. אפשרות אחת היא להחזיר 404 (לא נמצא) והשתמש ב- קישור כותרת כדי להפוך את העמוד הראשון לגלוי:

קישור =; rel = "ראשון",; rel = "אחרון"

אפשרות נוספת היא להחזיר הפניה מחדש - 303 (ראה אחר) - לדף הראשון. דרך שמרנית יותר תהיה פשוט להחזיר ללקוח 405 (השיטה אינה מותרת) לבקשת ה- GET.

8. PEST עם REST עם טווח כותרות HTTP

דרך אחרת יחסית ליישם עימוד היא לעבוד עם ה- HTTP טווח כותרותטווח, טווח תוכן, אם-טווח, קבל טווחים - ו קודי מצב HTTP – 206 (תוכן חלקי), 413 (הישות המבוקשת גדולה מדי), 416 (הטווח המבוקש לא ניתן להשביע).

השקפה אחת על גישה זו היא כי הרחבות טווח ה- HTTP לא נועדו לעמוד, וכי הן צריכות להיות מנוהלות על ידי השרת ולא על ידי היישום. יישום עימוד מבוסס על הרחבות כותרות טווח HTTP אפשרי בכל זאת מבחינה טכנית, אם כי כמעט לא שכיח כמו היישום שנדון במאמר זה.

9. עמוד נתוני האביב REST

ב- Spring Data, אם נצטרך להחזיר כמה תוצאות ממערכת הנתונים המלאה, נוכל להשתמש בכל אחת מהן תומכי שיטת מאגר, מכיוון שהיא תמיד תחזיר א עמוד. התוצאות יוחזרו על בסיס מספר העמוד, גודל העמוד וכיוון המיון.

Spring Data REST מזהה אוטומטית פרמטרים של כתובות אתרים כמו דף, גודל, מיון וכו '

כדי להשתמש בשיטות החלפה בכל מאגר שעלינו להרחיב PagingAndSortingRepository:

ממשק ציבורי SubjectRepository מרחיב את PagingAndSortingRepository {}

אם נתקשר // localhost: 8080 / נושאים האביב מוסיף אוטומטית את דף, גודל, מיון הצעות פרמטרים עם ה- API:

"_links": {"self": {"href": "// localhost: 8080 / subject {? page, size, sort}", "templated": true}}

כברירת מחדל, גודל העמוד הוא 20 אך אנו יכולים לשנות אותו על ידי קריאה למשהו כמו // localhost: 8080 / נושאים? עמוד = 10.

אם אנו רוצים ליישם החלפה לממשק ה- API המאגר המותאם אישית שלנו עלינו להעביר תוספת תומכי פרמטר וודא ש- API מחזיר a עמוד:

@RestResource (path = "nameContains") דף ציבורי findByNameContaining (@Param ("name") שם מחרוזת, pageable p);

בכל פעם שאנחנו מוסיפים API מותאם אישית a /לחפש נקודת קצה מתווספת לקישורים שנוצרו. אז אם אנחנו מתקשרים // localhost: 8080 / נושאים / חיפוש נראה נקודת סיום המסוגלת לעמוד:

"findByNameContaining": {"href": "// localhost: 8080 / subject / search / nameContains {? name, page, size, sort}", "templated": true}

כל ממשקי ה- API שמיישמים PagingAndSortingRepository יחזיר א עמוד. אם עלינו להחזיר את רשימת התוצאות מה- עמוד, ה getContent () ממשק API של עמוד מספק את רשימת הרשומות שנאספו כתוצאה מממשק ה- API של Spring Data REST.

הקוד בסעיף זה זמין בפרויקט האביב-מנוחת נתונים.

10. המר א רשימה לתוך עמוד

נניח שיש לנו תומכי אובייקט כקלט, אך המידע שעלינו לאחזר כלול ברשימה במקום a PagingAndSortingRepository. במקרים אלה, ייתכן שנצטרך להמיר א רשימה לתוך עמוד.

לדוגמא, דמיין שיש לנו רשימה של תוצאות משירות SOAP:

רשימת רשימה = getListOfFooFromSoapService ();

עלינו לגשת לרשימה במיקומים הספציפיים שצוינו על ידי תומכי חפץ שנשלח אלינו. אז בואו נגדיר את אינדקס ההתחלה:

int התחלה = (int) pageable.getOffset ();

ואינדקס הסיום:

int end = (int) ((start + pageable.getPageSize ())> fooList.size ()? fooList.size (): (start + pageable.getPageSize ()));

לאחר שני אלה במקום, אנו יכולים ליצור עמוד להשיג את רשימת האלמנטים ביניהם:

דף עמוד = PageImpl חדש (fooList.subList (התחלה, סוף), תומכים, fooList.size ());

זהו זה! אנחנו יכולים לחזור עכשיו עמוד כתוצאה תקפה.

ושימו לב שאם אנחנו רוצים גם לתת תמיכה למיון, אנחנו צריכים למיין את הרשימה לפני רישום משנה זה.

11. מסקנה

מאמר זה המחיש כיצד ליישם את Pagination ב- REST API באמצעות Spring, ודן כיצד להגדיר ולבדוק את יכולת הגילוי.

אם ברצונך לעמוד לעומק על עימוד ברמת ההתמדה, עיין בהדרכות עימוד ה- JPA או ה- Hibernate.

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד בפרויקט GitHub - זהו פרויקט מבוסס Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.

REST תחתון

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס