JavaRush /בלוג Java /Random-HE /מהו Mapstruct וכיצד להגדיר אותו כראוי לבדיקת יחידות ביישו...
Roman Beekeeper
רָמָה

מהו Mapstruct וכיצד להגדיר אותו כראוי לבדיקת יחידות ביישומי SpringBoot

פורסם בקבוצה

רקע כללי

שלום לכולם, חברים וקוראים יקרים שלי! לפני שנכתוב את המאמר, קצת רקע... לאחרונה נתקלתי בבעיה בעבודה עם ספריית Mapstruct , אותה תיארתי בקצרה בערוץ הטלגרם שלי כאן . בתגובות, הבעיה בפוסט נפתרה; עמיתי מהפרויקט הקודם עזר בזה. מהו Mapstruct וכיצד להגדיר אותו כראוי לבדיקת יחידות ביישומי SpringBoot.  חלק 1 - 1לאחר מכן החלטתי לכתוב מאמר בנושא זה, אך כמובן שלא נסתכל צר וננסה קודם כל להתעדכן, להבין מה זה Mapstruct ולמה זה נחוץ, ובאמצעות דוגמה אמיתית לנתח את המצב שנוצר קודם לכן וכיצד לפתור אותו. לכן אני ממליץ בחום לעשות את כל החישובים במקביל לקריאת המאמר על מנת לחוות הכל בפועל. לפני שנתחיל, הירשמו לערוץ הטלגרם שלי , אני אוסף שם את הפעילויות שלי, כותב מחשבות על פיתוח ב-Java ו-IT בכלל. נרשמת? גדול! ובכן, עכשיו בוא נלך!

Mapstruct, שאלות נפוצות?

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

מה זה?

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

בשביל מה?

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

אֵיך?

שימוש בהערות. אנחנו רק צריכים ליצור הערה עם הערת Mapper ראשית שאומרת לספרייה שניתן להשתמש בשיטות בממשק הזה כדי לתרגם מאובייקט אחד למשנהו. כפי שאמרתי קודם על תלמידים, במקרה שלנו זה יהיה ממשק StudentMapper, שיהיו לו מספר שיטות להעברת נתונים משכבה אחת לאחרת:
public class Student {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentDTO {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentModel {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}
עבור מחלקות אלו אנו יוצרים ממפה (להלן זה מה שנקרא הממשק, שמתאר מה אנו רוצים להעביר ולאן):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
היופי בגישה זו הוא שאם השמות וסוגי השדות זהים במחלקות שונות (כמו במקרה שלנו), אז ההגדרות של Mapstruct מספיקות כדי ליצור את המימוש הדרוש על בסיס ממשק StudentMapper בשלב ההידור, אשר יתרגם. אז זה כבר נעשה ברור יותר, נכון? בואו נלך רחוק יותר ונשתמש בדוגמה אמיתית כדי לנתח את העבודה באפליקציית Spring Boot.

דוגמה לעבודה של Spring Boot ו- Mapstruct

הדבר הראשון שאנו צריכים הוא ליצור פרויקט Spring Boot ולהוסיף לו Mapstruct. לצורך העניין, יש לי ארגון ב-GitHub עם תבניות למאגרים והתחלה של Spring Boot היא אחת מהן. על סמך זה, אנו יוצרים פרויקט חדש: מהו Mapstruct וכיצד להגדיר אותו כראוי לבדיקת יחידות ביישומי SpringBoot.  חלק 1 - 2לאחר מכן, נקבל את הפרויקט . כן חברים, תנו כוכב לפרויקט אם מצאתם אותו מועיל, כדי שאדע שאני לא עושה זאת לשווא. בפרויקט זה נחשוף סיטואציה שקיבלתי בעבודה ותיארתי בפוסט בערוץ הטלגרם שלי . אני אתאר בקצרה את המצב למי שלא יודע: כשאנחנו כותבים מבחנים למפות (כלומר, לאותם יישומי ממשק שדיברנו עליהם קודם), אנחנו רוצים שהבדיקות יעברו כמה שיותר מהר. האפשרות הפשוטה ביותר עם ממפים היא להשתמש בהערת SpringBootTest בעת הפעלת הבדיקה, אשר תקלוט את כל ApplicationContext של אפליקציית Spring Boot ויזריק את הממפ הדרוש לבדיקה בתוך הבדיקה. אבל האופציה הזו גוזלת משאבים ולוקחת הרבה יותר זמן ולכן היא לא מתאימה לנו. אנחנו צריכים לכתוב מבחן יחידה שפשוט יוצר את המאפר הרצוי ובודק שהשיטות שלו עובדות בדיוק כמו שאנחנו מצפים. למה אתה צריך בדיקות כדי לרוץ מהר יותר? אם הבדיקות נמשכות זמן רב, זה מאט את כל תהליך הפיתוח. עד שהבדיקות יעברו את הקוד החדש, קוד זה לא יכול להיחשב נכון ולא יילקח לבדיקה, כלומר לא יילקח לייצור ומשמעות הדבר היא שהמפתח לא סיים את העבודה. נראה, מדוע לכתוב מבחן לספריה שתפעולה אינו מוטל בספק? ובכל זאת אנחנו צריכים לכתוב מבחן, כי אנחנו בודקים עד כמה תיארנו נכון את הממפר והאם הוא עושה את מה שאנחנו מצפים. קודם כל, כדי להקל על העבודה שלנו, בואו נוסיף את Lombok לפרויקט שלנו על ידי הוספת תלות נוספת ל-pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
בפרויקט שלנו, נצטרך לעבור משיעורי מודל (שמשמשים לעבודה עם לוגיקה עסקית) לשיעורי DTO, שבהם אנו משתמשים כדי לתקשר עם העולם החיצון. בגרסה הפשוטה שלנו, נניח שהשדות לא משתנים והממפים שלנו יהיו פשוטים. אבל, אם יש רצון, אפשר יהיה לכתוב מאמר מפורט יותר על איך לעבוד עם Mapstruct, איך להגדיר אותה ואיך לנצל את היתרונות שלה. אבל אז, מכיוון שמאמר זה יהיה ארוך למדי. נניח שיש לנו סטודנט עם רשימת הרצאות ומרצים שהוא מגיע אליהם. בואו ניצור חבילת מודל . על סמך זה, ניצור מודל פשוט:
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
ההרצאות שלו
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
ומרצים
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
וצור חבילת dto לצד חבילת הדגם :
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
הרצאות
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
ומרצים
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
כעת בואו ניצור ממפה שיתרגם אוסף של מודלים של הרצאות לאוסף של הרצאות DTO. הדבר הראשון שצריך לעשות הוא להוסיף את Mapstruct לפרויקט. לשם כך, נשתמש באתר הרשמי שלהם , הכל מתואר שם. כלומר, אנחנו צריכים להוסיף תלות אחת ותוסף לזיכרון שלנו (אם יש לך שאלות לגבי מה זה זיכרון, הנה, סעיף 1 ו -2 ).
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
ובזיכרון בבלוק <build/>. שעוד לא היה לנו:
<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.5.1</version>
           <configuration>
               <source>1.8</source>
               <target>1.8</target>
               <annotationProcessorPaths>
                   <path>
                       <groupId>org.mapstruct</groupId>
                       <artifactId>mapstruct-processor</artifactId>
                       <version>1.4.2.Final</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
לאחר מכן, בואו ניצור חבילת ממפה לצד dto ו- model . בהתבסס על השיעורים שהצגנו קודם לכן, תצטרך ליצור עוד חמישה ממפים:
  • Mapper LectureModel <-> LectureDTO
  • Mapper List<LectureModel> <-> List<LectureDTO>
  • Mapper LecturerModel <-> LecturerDTO
  • רשימת Mapper<LecturerModel> <-> List<LecturerDTO>
  • Mapper StudentModel <-> StudentDTO
ללכת:

LectureMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LecturerDTO dto);
}

LectureListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LectureMapper.class)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}

LecturerMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LectureDTO dto);
}

LecturerListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LecturerModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LecturerMapper.class)
public interface LecturerListMapper {
   List<LecturerModel> toModelList(List<LecturerDTO> dloList);
   List<LecturerDTO> toDTOList(List<LecturerModel> modelList);
}

StudentMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.StudentDTO;
import com.github.romankh3.templaterepository.springboot.model.StudentModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
public interface StudentMapper {
   StudentDTO toDTO(StudentModel model);
   StudentModel toModel(StudentDTO dto);
}
יש לציין בנפרד כי במפותים אנו מתייחסים לממפים אחרים. זה נעשה באמצעות שדה השימושים בביאור Mapper, כפי שנעשה ב- StudentMapper:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
כאן אנו משתמשים בשני ממפים כדי למפות נכון את רשימת ההרצאות ואת רשימת המרצים. עכשיו אנחנו צריכים להרכיב את הקוד שלנו ולראות מה יש ואיך. ניתן לעשות זאת באמצעות הפקודה mvn clean compile . אבל, כפי שהתברר, בעת יצירת יישומי Mapstruct של הממפים שלנו, יישומי המיפוי לא החליפו את השדות. למה? התברר שלא ניתן היה לאסוף את הערת הנתונים מלומבוק. והיה צריך לעשות משהו... לכן יש לנו סעיף חדש בכתבה.

קישור בין לומבוק ומאפסטרוקטור

לאחר מספר דקות של חיפוש, התברר שאנחנו צריכים לחבר את לומבוק ומאפסטרוקטור בצורה מסוימת. יש מידע על כך בתיעוד Mapstruct . לאחר בחינת הדוגמה שהציעו המפתחים מ- Mapstruct, בואו נעדכן את pom.xml שלנו: בואו נוסיף גרסאות נפרדות:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
בואו נוסיף את התלות החסרה:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
ובואו נעדכן את תוסף המהדר שלנו כך שיוכל לחבר את Lombok ו- Mapstruct:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.5.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
       <annotationProcessorPaths>
           <path>
               <groupId>org.mapstruct</groupId>
               <artifactId>mapstruct-processor</artifactId>
               <version>${org.mapstruct.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok-mapstruct-binding</artifactId>
               <version>${lombok-mapstruct-binding.version}</version>
           </path>
       </annotationProcessorPaths>
   </configuration>
</plugin>
אחרי זה הכל אמור להסתדר. בואו נרכיב את הפרויקט שלנו שוב. אבל איפה אתה יכול למצוא את השיעורים שמאפסטרוקטור יצרה? הם נמצאים ב-generated-sources: ${projectDir}/target/generated-sources/annotations/ מהו Mapstruct וכיצד להגדיר אותו כראוי לבדיקת יחידות ביישומי SpringBoot.  חלק 1 - 3 עכשיו כשאנחנו מוכנים להבין את האכזבה שלי מהפוסט של Mapstruct, בואו ננסה ליצור בדיקות לממפים.

אנחנו כותבים מבחנים למפותים שלנו

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

LectureMapperTest

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LectureMapperTest {

   @Autowired
   private LectureMapper mapperUnderTest;

   @Test
   void shouldProperlyMapModelToDto() {
       //given
       LectureModel model = new LectureModel();
       model.setId(11L);
       model.setName("lecture name");

       //when
       LectureDTO dto = mapperUnderTest.toDTO(model);

       //then
       Assertions.assertNotNull(dto);
       Assertions.assertEquals(model.getId(), dto.getId());
       Assertions.assertEquals(model.getName(), dto.getName());
   }

   @Test
   void shouldProperlyMapDtoToModel() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(11L);
       dto.setName("lecture name");

       //when
       LectureModel model = mapperUnderTest.toModel(dto);

       //then
       Assertions.assertNotNull(model);
       Assertions.assertEquals(dto.getId(), model.getId());
       Assertions.assertEquals(dto.getName(), model.getName());
   }
}
כאן, באמצעות הערת SpringBootTest, אנו משיקים את כל האפליקציהContext ומתוכו, באמצעות הערת Autowired, אנו מחלצים את המחלקה שאנו צריכים לבדיקה. מנקודת מבט של מהירות וקלות כתיבת מבחן, זה טוב מאוד. המבחן עובר בהצלחה, הכל בסדר. אבל נלך לכיוון השני ונכתוב מבחן יחידה עבור ממפה, למשל, LectureListMapper...
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl();

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
מכיוון שהמימושים ש-Mapstruct מייצר הם באותה כיתה כמו הפרויקט שלנו, אנחנו יכולים להשתמש בהם בקלות בבדיקות שלנו. הכל נראה נהדר - בלי הערות, אנחנו יוצרים את הכיתה שאנחנו צריכים בצורה הפשוטה ביותר וזהו. אבל כשנריץ את הבדיקה, נבין שהיא תקרוס ותהיה NullPointerException במסוף... הסיבה לכך היא שהיישום של מפה LectureListMapper נראה כך:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T21:46:12+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   @Autowired
   private LectureMapper lectureMapper;

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
אם נסתכל על ה-NPE (קיצור של NullPointerException), נקבל אותו מהמשתנה lectureMapper , שמתברר שהוא לא אתחול. אבל ביישום שלנו אין לנו בנאי שאיתו נוכל לאתחל את המשתנה. זו בדיוק הסיבה שבגללה Mapstruct הטמיעה את המאפר בצורה זו! באביב, אתה יכול להוסיף שעועית לשיעורים בכמה דרכים, אתה יכול להזריק אותם דרך שדה יחד עם ההערה Autowired, כפי שנעשה למעלה, או שאתה יכול להזריק אותם דרך בנאי. מצאתי את עצמי במצב כל כך בעייתי בעבודה כשהייתי צריך לייעל את זמן ביצוע הבדיקה. חשבתי שאי אפשר לעשות כלום בנידון ושפכתי את הכאב שלי בערוץ הטלגרם שלי. ואז הם עזרו לי בהערות ואמרו שאפשר להתאים אישית את אסטרטגיית ההזרקה. לממשק Mapper יש שדה injectionStrategy , שמקבל רק את השם InjectionStrategy , שיש לו שני ערכים: FIELD ו- CONSTRUCTOR . עכשיו, כשאני יודע זאת, בואו נוסיף את ההגדרה הזו למפות שלנו; אני אראה אותה באמצעות LectureListMapper כדוגמה :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
הדגשתי במודגש את החלק שהוספתי. בואו נוסיף את האפשרות הזו עבור כל האחרים ונרכיב מחדש את הפרוייקט כך שייווצרו ממפים עם שורה חדשה. לאחר שהדבר נעשה, בוא נראה כיצד השתנה היישום של המאפר עבור LectureListMapper (הדגשה מודגשת החלק שאנו צריכים):
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T22:25:37+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   private final LectureMapper lectureMapper;

   @Autowired
   public LectureListMapperImpl(LectureMapper lectureMapper) {

       this.lectureMapper = lectureMapper;
   }

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
ועכשיו Mapstruct הטמיעה הזרקת ממפה דרך הבנאי. זה בדיוק מה שניסינו להשיג. כעת הבדיקה שלנו תפסיק להרכיב, בואו נעדכן אותה ונקבל:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl(new LectureMapperImpl());

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
כעת, אם נריץ את המבחן, הכל יעבוד כמצופה, שכן ב-LectureListMapperImpl אנו עוברים את ה-LectureMapper שהוא צריך... ניצחון! זה לא קשה לכם, אבל אני מרוצה: חברים, הכל כרגיל, הירשמו לחשבון GitHub שלי , לחשבון הטלגרם שלי . שם אני מפרסם את תוצאות הפעילות שלי, יש דברים ממש שימושיים) אני מזמין אותך במיוחד להצטרף לקבוצת הדיון של ערוץ הטלגרם . קורה שאם למישהו יש שאלה טכנית, הוא יכול לקבל שם תשובה. הפורמט הזה מעניין לכולם, אפשר לקרוא מי יודע מה ולצבור ניסיון.

סיכום

כחלק ממאמר זה, התוודענו למוצר כה הכרחי ונמצא בשימוש תדיר כמו Mapstruct. הבנו מה זה, למה ואיך. בעזרת דוגמה אמיתית, הרגשנו מה אפשר לעשות ואיך אפשר לשנות את זה. כמו כן, בדקנו כיצד להגדיר את הזרקת השעועית דרך הקונסטרוקטור, כך שניתן יהיה לבדוק כראוי ממפים. עמיתים מ- Mapstruct אפשרו למשתמשים במוצר שלהם לבחור בדיוק איך להזריק ממפים, ועל כך אין ספק שאנו מודים להם. אבל, למרות העובדה ש-Spring ממליץ להזריק שעועית דרך הקונסטרוקטור, החבר'ה מ- Mapstruct קבעו הזרקה דרך השדה כברירת מחדל. למה? אין תשובה. אני חושד שאולי יש סיבות שאנחנו פשוט לא יודעים עליהן, ובגלל זה הם עשו את זה ככה. וכדי לברר מהם, יצרתי בעיה של GitHub במאגר המוצרים הרשמי שלהם.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION