שפת שאילתת מנוחה עם RSQL
רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 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. ראשית, בואו להוסיף תלות בסדנה: וגם להגדיר את הישות העיקרית אנחנו נעבוד עם כל הדוגמאות - מִשׁתַמֵשׁ: האופן בו מיוצגים ביטויי RSQL באופן פנימי הוא בצורה של צמתים ודפוס המבקרים משמש לניתוח הקלט. עם זאת, אנו הולכים ליישם את מבקר RSQL ממשק וליצור יישום מבקרים משלנו - CustomRsqlVisitor: כעת עלינו להתמודד עם התמדה ולבנות את השאילתה שלנו מכל אחד מהצמתים הללו. אנו נשתמש במפרט ה- JPA של Spring Data שהשתמשנו בו בעבר - ונעמוד על מִפרָט בונה ל בנה מפרט מכל אחד מהצמתים בהם אנו מבקרים: שים לב כיצד: לדוגמא, לשאילתה "שם == ג'ון" - יש לנו: בבניית השאילתה השתמשנו ב- מִפרָט: שימו לב כיצד המפרט משתמש בגנריות ואינו קשור ליישות ספציפית כלשהי (כגון המשתמש). הבא - הנה שלנו enum “RsqlSearchOperation“ המכיל מפעילי ברירת מחדל של מנתח rsql: נתחיל כעת לבדוק את הפעולות החדשות והגמישות שלנו באמצעות כמה תרחישים בעולם האמיתי: ראשית - בוא נתחל את הנתונים: עכשיו בואו לבדוק את הפעולות השונות: בדוגמה הבאה - נחפש משתמשים לפי שלהם ראשון ו שם משפחה: לאחר מכן, בואו נחפש משתמשים לפי שלהם שם פרטי לא "ג'ון": לאחר מכן - נחפש משתמשים עם גיל גדול מ "25”: לאחר מכן - אנו נחפש משתמשים בעזרתם שם פרטי מתחיל עם "ג'ו”: הבא - אנו נחפש משתמשים שלהם שם פרטי הוא “ג'ון"או"ג'ֵק“: לסיום - בואו נקשר את הכל עם הבקר: הנה כתובת אתר לדוגמא: והתגובה: מדריך זה המחיש כיצד לבנות שפת שאילתות / חיפוש עבור REST API מבלי להמציא מחדש את התחביר ובמקום זאת להשתמש ב- FIQL / RSQL. ה יישום מלא של מאמר זה ניתן למצוא בפרויקט GitHub - זהו פרויקט מבוסס Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.2. הכנות
cz.jirutka.rsql rsql-parser 2.1.0
משתמש בכיתה ציבורית @Entity {@Id @GeneratedValue (אסטרטגיה = GenerationType.AUTO) פרטי מזהה ארוך; פרטי מחרוזת firstName; שם משפחה פרטי מחרוזת; דוא"ל מחרוזת פרטי; גיל פרטי פרטי; }
3. לנתח את הבקשה
מחלקה ציבורית CustomRsqlVisitor מיישמת RSQLVisitor
מחלקה ציבורית 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 ())); תוצאת החזרה; }}
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 {שווים (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. בדוק יותר ממה
@ מבחן בטל פומבי 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. מסקנה
רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:
>> בדוק את הקורס תחתית התמדה רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:
>> בדוק את הקורס