שפת שאילתת מנוחה עם RSQL

REST למעלה

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

>> בדוק את הקורס עליון התמדה

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

>> בדוק את הקורס מאמר זה הוא חלק מסדרה: • שפת שאילתות REST עם קריטריוני אביב ו- JPA

• שפת שאילתת REST עם מפרט JPA של Spring Data

• שפת שאילתת REST עם JPA ו- Querydsl Data Data

• שפת שאילתת REST - פעולות חיפוש מתקדמות

• שפת שאילתת REST - יישום או פעולה

• שפת שאילתות REST עם RSQL (מאמר נוכחי) • שפת שאילתת REST עם תמיכה ברשת Querydsl

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

במאמר החמישי הזה של הסדרה נמחיש את בניית שפת שאילתת ה- API של REST בעזרת ספרייה מגניבה - rsql-parser.

RSQL היא ערכת-על של שפת השאילתות של Feed Item (FIQL) - תחביר פילטר נקי ופשוט לעדכונים; כך שהוא מתאים באופן טבעי לממשק API של REST.

2. הכנות

ראשית, בואו להוסיף תלות בסדנה:

 cz.jirutka.rsql rsql-parser 2.1.0 

וגם להגדיר את הישות העיקרית אנחנו נעבוד עם כל הדוגמאות - מִשׁתַמֵשׁ:

משתמש בכיתה ציבורית @Entity {@Id @GeneratedValue (אסטרטגיה = GenerationType.AUTO) פרטי מזהה ארוך; פרטי מחרוזת firstName; שם משפחה פרטי מחרוזת; דוא"ל מחרוזת פרטי; גיל פרטי פרטי; }

3. לנתח את הבקשה

האופן בו מיוצגים ביטויי RSQL באופן פנימי הוא בצורה של צמתים ודפוס המבקרים משמש לניתוח הקלט.

עם זאת, אנו הולכים ליישם את מבקר RSQL ממשק וליצור יישום מבקרים משלנו - CustomRsqlVisitor:

מחלקה ציבורית CustomRsqlVisitor מיישמת RSQLVisitor {בונה פרטי GenericRsqlSpecBuilder; ציבורי CustomRsqlVisitor () {builder = GenericRsqlSpecBuilder חדש) (); } @ ביקור מפרט ציבורי @Override (AndNode node, Void param) {return builder.createSpecification (node); } @ ביקור מפרט ציבורי @Override (OrNode node, Void param) {return builder.createSpecification (node); } ביקור מפרט ציבורי @ Override ציבורי (צומת ComparisonNode, פראמים בטלים) {return builder.createSecification (node); }}

כעת עלינו להתמודד עם התמדה ולבנות את השאילתה שלנו מכל אחד מהצמתים הללו.

אנו נשתמש במפרט ה- JPA של Spring Data שהשתמשנו בו בעבר - ונעמוד על מִפרָט בונה ל בנה מפרט מכל אחד מהצמתים בהם אנו מבקרים:

מחלקה ציבורית GenericRsqlSpecBuilder {מפרט ציבורי createSpecification (צומת צומת) {if (מופע צומת של LogicalNode) {return createSpecification ((LogicalNode) node); } if (node ​​node of ComparisonNode) {return createSpecification ((ComparisonNode) node); } להחזיר אפס; } מפרט ציבורי createSpecification (LogicalNode logicalNode) {מפרט רשימה = logicalNode.getChildren () .stream () .map (node ​​-> createSpecification (node)) .filter (Objects :: nonNull) .collect (Collectors.toList ()); תוצאת מפרט = specs.get (0); אם (logicalNode.getOperator () == LogicalOperator.AND) {עבור (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .and (specs.get (i)) ; }} אחרת אם (logicalNode.getOperator () == LogicalOperator.OR) {for (int i = 1; i <specs.size (); i ++) {result = Specification.where (result) .or (specs.get ( אני)); }} להחזיר תוצאה; } מפרט ציבורי createSpecification (ComparisonNode comparisonNode) {מפרט תוצאה = Specification.where (חדש GenericRsqlSpecification (comparisonNode.getSelector (), comparisonNode.getOperator (), comparisonNode.getArguments ())); תוצאת החזרה; }}

שים לב כיצד:

  • LogicalNode הוא AND/אוֹצוֹמֶת ויש לו מספר ילדים
  • ComparisonNode אין לו ילדים והוא מחזיק את בורר, מפעיל והוויכוחים

לדוגמא, לשאילתה "שם == ג'ון" - יש לנו:

  1. בוחר: "שם"
  2. מַפעִיל: “==”
  3. ויכוחים: [john]

4. צור התאמה אישית מִפרָט

בבניית השאילתה השתמשנו ב- מִפרָט:

מחלקה ציבורית GenericRsqlSpecification מיישמת מפרט {private String property; מפעיל ComparisonOperator פרטי; טיעוני רשימה פרטית; @Override ציבורי Predicate toPredicate (שורש שורש, שאילתת CriteriaQuery, בונה CriteriaBuilder) {List args = castArguments (root); טיעון אובייקט = args.get (0); switch (RsqlSearchOperation.getSimpleOperator (operator)) {case EQUAL: {if (argument instanceof String) {return builder.like (root.get (property), argument.toString (). להחליף ('*', '%')) ; } אחרת אם (argument == null) {return builder.isNull (root.get (property)); } אחר {return builder.equal (root.get (property), טיעון); }} מקרה NOT_EQUAL: {if (argument instance of String) {return builder.notLike (root. get (property), argument.toString (). להחליף ('*', '%')); } אחרת אם (argument == null) {return builder.isNotNull (root.get (property)); } אחר {return builder.notEqual (root.get (property), טיעון); }} מקרה GREATER_THAN: {return builder.greaterThan (root. get (property), argument.toString ()); } מקרה GREATER_THAN_OR_EQUAL: {return builder.greaterThanOrEqualTo (root. get (property), argument.toString ()); } מקרה LESS_THAN: {return builder.lessThan (root. get (property), argument.toString ()); } מקרה LESS_THAN_OR_EQUAL: {build builder.lessThanOrEqualTo (root. get (property), argument.toString ()); } case IN: להחזיר root.get (property) .in (args); מקרה NOT_IN: החזר builder.not (root.get (property) .in (args)); } להחזיר אפס; } רשימה פרטית castArguments (שורש שורש סופי) {סוג סוג = root.get (מאפיין) .getJavaType (); רשימה args = arguments.stream (). מפה (arg -> {if (type.equals (Integer.class)) {return Integer.parseInt (arg);} else if (type.equals (Long.class)) {return Long.parseLong (arg);} else {return arg;}}). Collect (Collectors.toList ()); טענות חזרה; } // קונסטרוקטור סטנדרטי, גטר, סטר}

שימו לב כיצד המפרט משתמש בגנריות ואינו קשור ליישות ספציפית כלשהי (כגון המשתמש).

הבא - הנה שלנו enum “RsqlSearchOperation המכיל מפעילי ברירת מחדל של מנתח rsql:

enum הציבור RsqlSearchOperation {שווים (RSQLOperators.EQUAL), NOT_EQUAL (RSQLOperators.NOT_EQUAL), GREATER_THAN (RSQLOperators.GREATER_THAN), GREATER_THAN_OR_EQUAL (RSQLOperators.GREATER_THAN_OR_EQUAL), LESS_THAN (RSQLOperators.LESS_THAN), LESS_THAN_OR_EQUAL (RSQLOperators.LESS_THAN_OR_EQUAL), לפי (RSQLOperators. IN), NOT_IN (RSQLOperators.NOT_IN); מפעיל ComparisonOperator פרטי; פרטי RsqlSearchOperation (אופרטור ComparisonOperator) {this.operator = operator; } RsqlSearchOperation סטטי ציבורי getSimpleOperator (אופרטור ComparisonOperator) {עבור (פעולת RsqlSearchOperation: ערכים ()) {if (operation.getOperator () == operator) {פעולת החזרה; }} להחזיר null; }}

5. בדוק שאילתות חיפוש

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

ראשית - בוא נתחל את הנתונים:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (classes = {PersistenceConfig.class}) @ Transactional @ TransactionConfiguration public class RsqlTest {@ מאגר פרטי UserRepository פרטי; משתמש פרטי משתמש ג'ון; UserTom פרטי; @ לפני init בטל פומבי () {userJohn = משתמש חדש (); userJohn.setFirstName ("ג'ון"); userJohn.setLastName ("איילה"); userJohn.setEmail ("[דוא"ל מוגן]"); userJohn.setAge (22); repository.save (userJohn); userTom = משתמש חדש (); userTom.setFirstName ("טום"); userTom.setLastName ("איילה"); userTom.setEmail ("[דוא"ל מוגן]"); userTom.setAge (26); repository.save (userTom); }}

עכשיו בואו לבדוק את הפעולות השונות:

5.1. מבחן שוויון

בדוגמה הבאה - נחפש משתמשים לפי שלהם ראשון ו שם משפחה:

@Test ציבורי בטל givenFirstAndLastName_whenGettingListOfUsers_thenCorrect () {צומת rootNode = RSQLParser חדש (). ניתוח ("firstName == john; lastName == doe"); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); תוצאות רשימה = repository.findAll (spec); assertThat (userJohn, isIn (תוצאות)); assertThat (userTom, not (isIn (results))); }

5.2. שלילת מבחן

לאחר מכן, בואו נחפש משתמשים לפי שלהם שם פרטי לא "ג'ון":

@Test הציבור בטל givenFirstNameInverse_whenGettingListOfUsers_thenCorrect () {Node rootNode = RSQLParser חדש (). Parse ("firstName! = John"); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); תוצאות רשימה = repository.findAll (spec); assertThat (userTom, isIn (תוצאות)); assertThat (userJohn, not (isIn (results))); }

5.3. בדוק יותר ממה

לאחר מכן - נחפש משתמשים עם גיל גדול מ "25”:

@ מבחן בטל פומבי givenMinAge_whenGettingListOfUsers_thenCorrect () {צומת rootNode = RSQLParser חדש (). ניתוח ("גיל> 25"); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); תוצאות רשימה = repository.findAll (spec); assertThat (userTom, isIn (תוצאות)); assertThat (userJohn, not (isIn (results))); }

5.4. לבדוק כמו

לאחר מכן - אנו נחפש משתמשים בעזרתם שם פרטי מתחיל עם "ג'ו”:

@Test הציבור בטל givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect () {Node rootNode = RSQLParser חדש (). Parse ("firstName == jo *"); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); תוצאות רשימה = repository.findAll (spec); assertThat (userJohn, isIn (תוצאות)); assertThat (userTom, not (isIn (results))); }

5.5. מִבְחָן IN

הבא - אנו נחפש משתמשים שלהם שם פרטי הוא “ג'ון"או"ג'ֵק“:

@Test הציבור בטל givenListOfFirstName_whenGettingListOfUsers_thenCorrect () {Node rootNode = RSQLParser חדש (). ניתוח ("firstName = in = (john, jack)"); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); תוצאות רשימה = repository.findAll (spec); assertThat (userJohn, isIn (תוצאות)); assertThat (userTom, not (isIn (results))); }

6. UserController

לסיום - בואו נקשר את הכל עם הבקר:

@RequestMapping (method = RequestMethod.GET, value = "/ users") @ResponseBody public List findAllByRsql (@RequestParam (value = "search") חיפוש מחרוזות) {צומת rootNode = RSQLParser חדש (). ניתוח (חיפוש); מפרט מפרט = rootNode.accept (CustomRsqlVisitor חדש ()); החזר dao.findAll (spec); }

הנה כתובת אתר לדוגמא:

// localhost: 8080 / משתמשים? search = firstName == jo *; גיל <25

והתגובה:

[{"id": 1, "firstName": "john", "lastName": "doe", "email": "[email protected]", "age": 24}]

7. מסקנה

מדריך זה המחיש כיצד לבנות שפת שאילתות / חיפוש עבור REST API מבלי להמציא מחדש את התחביר ובמקום זאת להשתמש ב- FIQL / RSQL.

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

הַבָּא » שפת שאילתת מנוחה עם תמיכה ברשת Querydsl « שפת שאילתת REST קודמת - יישום או פעולה REST בתחתית

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

>> בדוק את הקורס תחתית התמדה

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

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

$config[zx-auto] not found$config[zx-overlay] not found