מדריך מהיר ל- MapStruct

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

במאמר זה נחקור את השימוש ב- MapStruct שהוא, במילים פשוטות, מיפוי Java Bean.

ממשק API זה מכיל פונקציות המפותות באופן אוטומטי בין שתי שעועית Java. עם MapStruct עלינו רק ליצור את הממשק, והספרייה תיצור באופן אוטומטי יישום קונקרטי בזמן הידור.

2. דפוס MapStruct והעברת אובייקט

עבור רוב היישומים, תבחין בהרבה קוד של Boilerplate הממיר POJOs ל- POJO אחרים.

לדוגמא, סוג נפוץ של המרה מתרחש בין ישויות מגובות התמדה ל- DTO שיוצאות לצד הלקוח.

אז זו הבעיה ש- MapStruct פותרתיצירת ידנית של ממפות שעועית גוזלת זמן. הספרייה יכול ליצור שיעורי מיפוי שעועית באופן אוטומטי.

3. מייבן

בואו נוסיף את התלות שלהלן אל Maven שלנו pom.xml:

 org.mapstruct mapstruct 1.3.1. גמר 

המהדורה היציבה האחרונה של Mapstruct והמעבד שלו זמינה במאגר המרכזי של Maven.

בואו נוסיף גם את annotationProcessorPaths לחלק התצורה של ה- תוסף maven-compiler חיבור.

ה מעבד mapstruct משמש ליצירת יישום המפות במהלך הבנייה:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-processor 1.3.1.Final 

4. מיפוי בסיסי

4.1. יצירת POJO

בואו ניצור תחילה Java POJO פשוט:

מחלקה ציבורית SimpleSource {שם מחרוזת פרטי; תיאור מחרוזת פרטי; // getters and setter} class public SimpleDestination {שם מחרוזת פרטי; תיאור מחרוזת פרטי; // גטרים וקובעים}

4.2. ממשק Mapper

ממשק ציבורי @Mapper SimpleSourceDestinationMapper {SimpleDestination sourceToDestination (מקור SimpleSource); יעד SimpleSourceToSource (יעד SimpleDestination); }

שימו לב שלא יצרנו כיתת יישום עבורנו SimpleSourceDestinationMapper - כי MapStruct יוצר את זה בשבילנו.

4.3. המפה החדשה

אנו יכולים להפעיל את עיבוד MapStruct על ידי ביצוע mvn נקי להתקין.

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

הנה המחלקה ש- MapStruct יוצרת עבורנו אוטומטית:

מחלקה ציבורית SimpleSourceDestinationMapperImpl מיישמת SimpleSourceDestinationMapper {@Override פומבי SimpleDestination sourceToDestination (מקור SimpleSource) {if (source == null) {return null; } SimpleDestination simpleDestination = SimpleDestination חדש (); simpleDestination.setName (source.getName ()); simpleDestination.setDescription (source.getDescription ()); להחזיר SimpleDestination; } @ עקוב יעד SimpleSource ציבוריToSource (יעד SimpleDestination) {אם (יעד == null) {להחזיר null; } SimpleSource simpleSource = SimpleSource חדש (); simpleSource.setName (destination.getName ()); simpleSource.setDescription (destination.getDescription ()); להחזיר simpleSource; }}

4.4. מקרה מבחן

לבסוף, עם כל מה שנוצר, בוא נכתוב מקרה מבחן יראה שהערכים ב SimpleSource להתאים ערכים ב SimpleDestination.

מחלקה ציבורית SimpleSourceDestinationMapperIntegrationTest {פרטי SimpleSourceDestinationMapper mapper = Mappers.getMapper (SimpleSourceDestinationMapper.class); @Test ציבורי בטל givenSourceToDestination_whenMaps_thenCorrect () {SimpleSource simpleSource = SimpleSource חדש (); simpleSource.setName ("מקור שם"); simpleSource.setDescription ("SourceDescription"); יעד SimpleDestination = mapper.sourceToDestination (simpleSource); assertEquals (simpleSource.getName (), destination.getName ()); assertEquals (simpleSource.getDescription (), destination.getDescription ()); } @Test ציבורי בטל givenDestinationToSource_whenMaps_thenCorrect () {יעד SimpleDestination = SimpleDestination חדש (); destination.setName ("DestinationName"); destination.setDescription ("DestinationDescription"); מקור SimpleSource = mapper.destinationToSource (יעד); assertEquals (destination.getName (), source.getName ()); assertEquals (destination.getDescription (), source.getDescription ()); }}

5. מיפוי עם הזרקת תלות

לאחר מכן, הבה נקבל מופע של מיפוי ב- MapStruct על ידי התקשרות בלבד Mappers.getMapper (YourClass.class).

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

למרבה המזל ל- MapStruct יש תמיכה מוצקה הן באביב והן ב- CDI (הקשרים והזרקת תלות).

כדי להשתמש ב- Spring IoC במפות שלנו, עלינו להוסיף את ה- componentModelמייחס ל @Mapper עם הערך אביב ועבור CDI יהיה cdi .

5.1. שנה את המפה

הוסף את הקוד הבא אל SimpleSourceDestinationMapper:

ממשק ציבורי @Mapper (componentModel = "spring") ציבורי SimpleSourceDestinationMapper

6. מיפוי שדות עם שמות שדות שונים

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

לדוגמא שלנו, ניצור שעועית חדשה בשם עוֹבֵד ו EmployeeDTO.

6.1. POJOs חדשים

מעמד ציבורי EmployeeDTO {int intimateId פרטי; פרטי מחרוזת עובד שם; // גטרים וקובעים}
עובד בכיתה ציבורית {מזהה פרטי פרטי; שם מחרוזת פרטי; // גטרים וקובעים}

6.2. ממשק Mapper

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

ב- MapStruct נוכל גם להשתמש בסימון נקודות כדי להגדיר חבר של שעועית:

ממשק ציבורי @Mapper EmployeeMapper {@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name")}) עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת ( ישות עובדים); @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName")}) עובד עובד DTOto עובד (עובד dTO dto); }

6.3. מקרה המבחן

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

@ מבחן חלל ציבורי שניתן EmployeeDTOwithDiffNameto Employee_whenMaps_thenCorrect () {EmployeeDTO dto = EmployeeDTO חדש (); dto.setEmployeeId (1); dto.setEmployeeName ("ג'ון"); ישות עובדים = ממפה.עובד DTOto עובד (dto); assertEquals (dto.getEmployeeId (), entity.getId ()); assertEquals (dto.getEmployeeName (), entity.getName ()); }

ניתן למצוא מקרי מבחן נוספים בפרויקט Github.

7. מיפוי שעועית עם שעועית לילד

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

7.1. שנה את ה- POJO

בואו נוסיף התייחסות לשעועית חדשה ל עוֹבֵד לְהִתְנַגֵד:

מעמד ציבורי EmployeeDTO {int intimateId פרטי; פרטי מחרוזת עובד שם; חטיבת DivisionDTO פרטית; // גטרים וקובעים הושמטו}
עובד בכיתה ציבורית {מזהה פרטי פרטי; שם מחרוזת פרטי; חטיבת חטיבה פרטית; // גטרים וקובעים הושמטו}
חטיבת מעמד ציבורי {מזהה פרטי פרטי; שם מחרוזת פרטי; // בונה ברירת מחדל, גטרים וקובעים הושמטו}

7.2. שנה את המפה

כאן עלינו להוסיף שיטה להמרת ה- חֲלוּקָה ל DivisionDTO ולהיפך; אם MapStruct מזהה שיש להמיר את סוג האובייקט ושיטת ההמרה קיימת באותה מחלקה, היא תשתמש בו באופן אוטומטי.

בואו נוסיף את זה למפות:

DivisionDTO divisionToDivisionDTO (ישות חטיבה); חטיבת החטיבה DTOtoDivision (DivisionDTO dto);

7.3. שנה את מקרה הבדיקה

בואו נשנה ונוסיף כמה מקרי מבחן לקיים:

@Test הציבור בטל givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect () {EmployeeDTO dto = EmployeeDTO חדש (); dto.setDivision (DivisionDTO חדש (1, "Division1")); ישות עובדים = ממפה.עובד DTOto עובד (dto); assertEquals (dto.getDivision (). getId (), entity.getDivision (). getId ()); assertEquals (dto.getDivision (). getName (), entity.getDivision (). getName ()); }

8. מיפוי עם המרת סוג

MapStruct מציע גם כמה המרות מסוג סמויות מוכנות, ולדוגמה שלנו, ננסה להמיר תאריך מחרוזת לתאריך ממשי תַאֲרִיך לְהִתְנַגֵד.

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

8.1. שנה את השעועית

הוסף תאריך התחלה לעובד שלנו:

שכבה ציבורית עובד {// שדות אחרים פרטיים תאריך startDt; // גטרים וקובעים}
מעמד ציבורי EmployeeDTO {// שדות אחרים פרטיים מחרוזת עובדStartDt; // גטרים וקובעים}

8.2. שנה את המפה

שנה את המפה וספק את פורמט תאריך לתאריך ההתחלה שלנו:

@Mappings ({@Mapping (target = "employeeId", source = "entity.id"), @Mapping (target = "employeeName", source = "entity.name"), @Mapping (target = "עובדStartDt", מקור = "entity.startDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת עובד / ת. @Mappings ({@Mapping (target = "id", source = "dto.employeeId"), @Mapping (target = "name", source = "dto.employeeName"), @Mapping (target = "startDt", source = "dto.employeeStartDt", dateFormat = "dd-MM-yyyy HH: mm: ss")}) עובד עובד DTOto עובד (EmployeeDTO dto);

8.3. שנה את מקרה הבדיקה

בואו נוסיף עוד כמה מקרה בדיקה כדי לוודא שההמרה נכונה:

סופי סטטי פרטי מחרוזת DATE_FORMAT = "dd-MM-yyyy HH: mm: ss"; @Test הציבור בטל givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect () זורק ParseException {ישות עובדים = עובד חדש (); entity.setStartDt (תאריך חדש ()); EmployeeDTO dto = mapper.employeeToEmployeeDTO (ישות); פורמט SimpleDateFormat = SimpleDateFormat חדש (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); } @Test הציבור בטל givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect () זורק ParseException {EmployeeDTO dto = EmployeeDTO חדש (); dto.setEmployeeStartDt ("01-04-2016 01:00:00"); ישות עובדים = mapper. EmployeeDTOto עובד (dto); פורמט SimpleDateFormat = SimpleDateFormat חדש (DATE_FORMAT); assertEquals (format.parse (dto.getEmployeeStartDt ()). toString (), entity.getStartDt (). toString ()); }

9. מיפוי בשיעור מופשט

לפעמים, ייתכן שנרצה להתאים אישית את המפה שלנו באופן העולה על יכולות @Mapping.

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

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

9.1. מודל בסיסי

בדוגמה זו נשתמש בכיתה הבאה:

עסקה בכיתה ציבורית {פרטי מזהה ארוך; מחרוזת פרטית uuid = UUID.randomUUID (). toString (); סה"כ BigDecimal פרטי; // גטרים סטנדרטיים}

ו- DTO תואם:

TransactionDTO בכיתה ציבורית {פרטי מחרוזת uuid; פרטי TotalInCents ארוכים; // סטרים וקובעים סטנדרטיים}

החלק המסובך כאן הוא המרת ה- BigDecimalסך הכלסכום של דולרים ל סך הכל עליות.

9.2. הגדרת ממפה

אנו יכולים להשיג זאת על ידי יצירת שלנו ממפה כמעמד מופשט:

@Mapper class class TransactionMapper {TransactionDTO public toTransactionDTO (עסקת עסקה) {TransactionDTO transactionDTO = TransactionDTO חדש (); transactionDTO.setUuid (transaction.getUuid ()); transactionDTO.setTotalInCents (transaction.getTotal () .multiply (BigDecimal חדש ("100")). longValue ()); עסקת החזר DTO; } רשימה מופשטת ציבורית toTransactionDTO (עסקאות גבייה); }

כאן יישמנו את שיטת המיפוי המותאמת אישית שלנו להמרת אובייקט יחיד.

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

9.3. נוצר תוצאה

כפי שכבר יישמנו את השיטה למפות יחיד עִסקָהלתוך TransactionDTO, אנחנו מצפים Mapstructלהשתמש בו בשיטה השנייה. הדברים הבאים ייווצרו:

@Generated class TransactionMapperImpl מרחיב את TransactionMapper {@ Override public list toTransactionDTO (עסקאות גבייה) {if (עסקאות == null) {return null; } רשימת רשימות = ArrayList חדש (); עבור (Transaction Transaction: Transactions) {list.add (toTransactionDTO (Transaction)); } רשימת החזרה; }}

כפי שאנו יכולים לראות בשורה 12, MapStruct משתמש ביישום שלנו בשיטה שהיא יצרה.

10. הערות לפני המיפוי ואחרי המיפוי

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

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

בואו נסתכל על דוגמה הממפה את תת-הסוגים של אוטו; מכונית חשמלית, ו BioDieselCar, ל CarDTO.

במהלך המיפוי נרצה למפות את מושג הסוגים אל ה- סוג דלק בשדה enum ב- DTO, ולאחר סיום המיפוי נרצה לשנות את שם ה- DTO לאותיות גדולות.

10.1. מודל בסיסי

בדוגמה זו נשתמש בשיעורים הבאים:

רכב בכיתה ציבורית {מזהה פרטי פרטי; שם מחרוזת פרטי; }

תת-סוגים של אוטו:

מעמד ציבורי BioDieselCar מרחיב את המכונית {}
מחלקה ציבורית ElectricCar מרחיבה את המכונית {}

ה CarDTO עם סוג שדה enum סוג דלק:

מחלקה ציבורית CarDTO {מזהה פרטי פרטי; שם מחרוזת פרטי; פרטי FuelType fuelType; }
פומבי סוג דלק {ELECTRIC, BIO_DIESEL}

10.2. הגדרת המפה

עכשיו בואו נמשיך וכתוב את מחלקת המפות המופשטת שלנו, המפה אוטו ל CarDTO:

@Mapper מחלקה מופשטת ציבורית CarsMapper {@BeforeMapping מוגן בטל עשיר DTOWithFuelType (מכונית רכב, @MappingTarget CarDTO carDto) {if (רכב למשל של ElectricCar) {carDto.setFuelType (FuelType.ELECTRIC); } אם (מופע רכב של BioDieselCar) {carDto.setFuelType (FuelType.BIO_DIESEL); }} @ AfterMapping מוגן חלל convertNameToUpperCase (@MappingTarget CarDTO carDto) {carDto.setName (carDto.getName (). ToUpperCase ()); } תקציר מופשט של CarDTO toCarDto (מכונית רכב); }

@MappingTarget הוא הערת פרמטר ש מאכלס את DTO למיפוי היעד ממש לפני ביצוע לוגיקת המיפויבמקרה @BeforeMapping ו מיד אחרי במקרה של @AfterMapping שיטת ביאור.

10.3. תוֹצָאָה

ה CarsMapper שהוגדר לעיל מייצרהיישום:

@Generated Class Class CarsMapperImpl מרחיב את CarsMapper {@Override CarDTO public toCarDto (Car car) {if (car == null) {return null; } CarDTO carDTO = CarDTO חדש (); להעשיר DTOWithFuelType (מכונית, carDTO); carDTO.setId (car.getId ()); carDTO.setName (car.getName ()); convertNameToUpperCase (carDTO); החזרת carDTO; }}

שים לב איך שיטות הקריאה להערות מקיפות את הגיון המיפוי ביישום.

11. תמיכה בלומבוק

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

כדי לאפשר תמיכה ב- Lombok עלינו להוסיף את התלות בנתיב מעבד ההערות. אז עכשיו יש לנו את מעבד mapstruct כמו גם את Lombok בתוסף המהדר Maven:

 org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 org.mapstruct mapstruct-processor 1.3.1.Final org.projectlombok lombok 1.18.4 

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

@ Getter @ רכב בכיתה ציבורית {מזהה פרטי פרטי; שם מחרוזת פרטי; }

ואובייקט העברת נתוני היעד:

@ Getter @ Set בכיתה ציבורית CarDTO {מזהה פרטי פרטי; שם מחרוזת פרטי; }

ממשק הממפה לכך נשאר דומה לדוגמא הקודמת שלנו:

ממשק ציבורי @Mapper CarMapper {CarMapper INSTANCE = Mappers.getMapper (CarMapper.class); CarDTO carToCarDTO (מכונית רכב); }

12. תמיכה ב defaultExpression

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

ישות המקור:

אדם בכיתה ציבורית {מזהה פרטי פרטי; שם מחרוזת פרטי; }

אובייקט העברת נתוני היעד:

ClassD PersonDTO {מזהה פרטי פרטי; שם מחרוזת פרטי; }

אם ה תְעוּדַת זֶהוּת שדה של ישות המקור הוא ריק, אנחנו רוצים ליצור אקראי תְעוּדַת זֶהוּת והקצה אותו ליעד תוך שמירה על ערכי נכס אחרים כפי שהם:

ממשק ציבורי @Mapper PersonMapper {PersonMapper INSTANCE = Mappers.getMapper (PersonMapper.class); @Mapping (target = "id", source = "person.id", defaultExpression = "java (java.util.UUID.randomUUID (). ToString ())") PersonDTO personToPersonDTO (אדם אישי); }

בואו נוסיף מקרה בדיקה לאימות ביצוע הביטוי:

@ מבחן חלל ציבורי givenPersonEntitytoPersonWithExpression_whenMaps_thenCorrect () ישות אדם = אדם חדש (); entity.setName ("Micheal"); PersonDTO personDto = PersonMapper.INSTANCE.personToPersonDTO (ישות); assertNull (entity.getId ()); assertNotNull (personDto.getId ()); assertEquals (personDto.getName (), entity.getName ()); }

13. מסקנה

מאמר זה סיפק מבוא ל- MapStruct. הצגנו את רוב היסודות של ספריית המיפוי וכיצד להשתמש בה ביישומים שלנו.

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


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