พื้นหลัง
สวัสดีทุกคน เพื่อนรักและผู้อ่านของฉัน! ก่อนที่เราจะเขียนบทความ พื้นหลังเล็กน้อย... ฉันเพิ่งพบปัญหาในการทำงานกับ ไลบรารี
Mapstructซึ่งฉันได้อธิบายไว้สั้น ๆ ในช่องโทรเลขของฉัน
ที่นี่ ปัญหาเกี่ยวกับโพสต์ได้รับการแก้ไขแล้วในความคิดเห็น เพื่อนร่วมงานของฉันจากโครงการก่อนหน้านี้ช่วยในเรื่องนี้
![Mapstruct คืออะไรและจะกำหนดค่าอย่างไรให้เหมาะสมสำหรับการทดสอบหน่วยในแอปพลิเคชัน SpringBoot ตอนที่ 1 - 1]()
หลังจากนั้น ฉันตัดสินใจเขียนบทความในหัวข้อนี้ แต่แน่นอนว่าเราจะไม่ใช้มุมมองที่แคบ และจะพยายามเร่งความเร็วให้ได้ก่อน ทำความเข้าใจว่า Mapstruct คืออะไร และเหตุใดจึงจำเป็น และเราจะใช้ตัวอย่างจริง วิเคราะห์สถานการณ์ที่เกิดขึ้นก่อนหน้านี้และวิธีแก้ไข ดังนั้นฉันขอแนะนำอย่างยิ่งให้ทำการคำนวณทั้งหมดควบคู่ไปกับการอ่านบทความเพื่อสัมผัสประสบการณ์ทุกอย่างในทางปฏิบัติ
ก่อนที่เราจะเริ่ม สมัครสมาชิกช่องโทรเลขของฉันฉันรวบรวมกิจกรรมของฉันที่นั่น เขียนความคิดเกี่ยวกับการพัฒนาใน Java และไอทีโดยทั่วไป สมัครสมาชิก? ยอดเยี่ยม! เอาล่ะไปกันเลย!
Mapstruct คำถามที่พบบ่อย?
ตัวสร้างโค้ดสำหรับการแมป bean ที่รวดเร็วและปลอดภัย ภารกิจแรกของเราคือการหาว่า Mapstruct คืออะไร และเหตุใดเราจึงต้องการมัน โดยทั่วไปคุณสามารถอ่านได้บนเว็บไซต์อย่างเป็นทางการ ในหน้าหลักของเว็บไซต์มีสามคำตอบสำหรับคำถาม: มันคืออะไร? เพื่ออะไร? ยังไง? เราจะพยายามทำสิ่งนี้ด้วย:
มันคืออะไร?
Mapstruct เป็นไลบรารีที่ช่วยในการแมป (แผนที่ โดยทั่วไปนั่นคือสิ่งที่พวกเขามักจะพูด: แผนที่ แผนที่ ฯลฯ ) ออบเจ็กต์ของเอนทิตีบางตัวให้เป็นออบเจ็กต์ของเอนทิตีอื่น ๆ โดยใช้โค้ดที่สร้างขึ้นตามการกำหนดค่าที่อธิบายผ่านอินเทอร์เฟซ
เพื่ออะไร?
โดยส่วนใหญ่ เราพัฒนาแอปพลิเคชันแบบหลายเลเยอร์ (เลเยอร์สำหรับการทำงานกับฐานข้อมูล, เลเยอร์ของตรรกะทางธุรกิจ, เลเยอร์สำหรับการโต้ตอบของแอปพลิเคชันกับโลกภายนอก) และแต่ละเลเยอร์มีออบเจ็กต์ของตัวเองสำหรับจัดเก็บและประมวลผลข้อมูล . และข้อมูลนี้จำเป็นต้องถ่ายโอนจากชั้นหนึ่งไปอีกชั้นหนึ่งโดยการถ่ายโอนจากเอนทิตีหนึ่งไปยังอีกเอนทิตี สำหรับผู้ที่ยังไม่เคยใช้วิธีนี้ อาจดูซับซ้อนเล็กน้อย ตัวอย่างเช่น เรามีเอนทิตีสำหรับฐานข้อมูลนักเรียน เมื่อข้อมูลของเอนทิตีนี้ไปที่เลเยอร์ตรรกะทางธุรกิจ (บริการ) เราจำเป็นต้องถ่ายโอนข้อมูลจากคลาส Student ไปยังคลาส StudentModel ถัดไป หลังจากจัดการกับตรรกะทางธุรกิจแล้ว ข้อมูลจะต้องถูกเผยแพร่ออกไปภายนอก และสำหรับสิ่งนี้ เรามีคลาส StudentDto แน่นอนว่าเราต้องส่งข้อมูลจากคลาส StudentModel ไปยัง StudentDto การเขียนด้วยมือแต่ละครั้งวิธีการที่จะถ่ายทอดต้องใช้แรงงานเข้มข้น นอกจากนี้นี่คือโค้ดพิเศษในฐานโค้ดที่ต้องดูแลรักษา คุณสามารถทำผิดพลาดได้ และ Mapstruct จะสร้างวิธีการดังกล่าวในขั้นตอนการคอมไพล์และจัดเก็บไว้ในแหล่งที่สร้างขึ้น
ยังไง?
การใช้คำอธิบายประกอบ เราเพียงแค่ต้องสร้างคำอธิบายประกอบที่มีคำอธิบายประกอบ 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 (ต่อไปนี้คือสิ่งที่เราจะเรียกว่าอินเทอร์เฟซ ซึ่งอธิบายสิ่งที่เราต้องการถ่ายโอนและตำแหน่ง):
@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]()
ต่อไปเราจะได้
โปรเจ็กต์
ใช่เพื่อน ๆ ให้ดาวแก่โครงการถ้าคุณพบว่ามีประโยชน์ เพื่อฉันจะรู้ว่าฉันไม่ได้ทำสิ่งนี้โดยเปล่าประโยชน์ ในโครงการนี้ เราจะเปิดเผยสถานการณ์ที่ฉันได้รับในที่ทำงานและอธิบายไว้ใน
โพสต์ในช่อง Telegram ของฉัน ฉันจะสรุปสถานการณ์คร่าวๆ สำหรับผู้ที่ไม่ทราบ: เมื่อเราเขียนการทดสอบสำหรับผู้ทำแผนที่ (นั่นคือ สำหรับการใช้งานอินเทอร์เฟซที่เราพูดถึงก่อนหน้านี้) เราต้องการให้การทดสอบผ่านไปโดยเร็วที่สุด ตัวเลือกที่ง่ายที่สุดสำหรับตัวทำแผนที่คือการใช้คำอธิบายประกอบ SpringBootTest เมื่อรันการทดสอบ ซึ่งจะรับ ApplicationContext ทั้งหมดของแอปพลิเคชัน Spring Boot และฉีดตัวทำแผนที่ที่จำเป็นสำหรับการทดสอบภายในการทดสอบ แต่ตัวเลือกนี้ใช้ทรัพยากรมากและใช้เวลานานกว่ามากจึงไม่เหมาะกับเรา เราจำเป็นต้องเขียน Unit Test ที่สร้าง Mapper ที่ต้องการและตรวจสอบว่าวิธีการของมันทำงานตรงตามที่เราคาดหวังหรือไม่ เหตุใดคุณจึงต้องมีการทดสอบเพื่อให้ทำงานเร็วขึ้น หากการทดสอบใช้เวลานาน กระบวนการพัฒนาทั้งหมดจะช้าลง จนกว่าการทดสอบจะส่งต่อโค้ดใหม่ รหัสนี้จะไม่สามารถถือว่าถูกต้องและจะไม่ถูกนำไปทดสอบ ซึ่งหมายความว่าจะไม่ถูกนำไปใช้จริง และหมายความว่านักพัฒนายังทำงานไม่เสร็จ ดูเหมือนว่าทำไมต้องเขียนแบบทดสอบห้องสมุดที่การดำเนินงานไม่มีข้อสงสัย? แต่เรายังจำเป็นต้องเขียนการทดสอบ เนื่องจากเรากำลังทดสอบว่าเราอธิบายผู้ทำแผนที่ได้ถูกต้องเพียงใด และเป็นไปตามที่เราคาดหวังหรือไม่ ก่อนอื่น เพื่อให้งานของเราง่ายขึ้น มาเพิ่ม 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ถัดจาก
model package :
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;
}
ตอนนี้เรามาสร้าง Mapper ที่จะแปลคอลเลกชันแบบจำลองการบรรยายเป็นคอลเลกชันของการบรรยาย DTO สิ่งแรกที่ต้องทำคือเพิ่ม Mapstruct ให้กับโปรเจ็กต์ ในการดำเนินการนี้ เราจะใช้
เว็บไซต์อย่างเป็นทางการซึ่งมีคำอธิบายทุกอย่างไว้ที่นั่น นั่นคือเราจำเป็นต้องเพิ่มการพึ่งพาหนึ่งรายการและปลั๊กอินในหน่วยความจำของเรา (หากคุณมีคำถามเกี่ยวกับหน่วยความจำคืออะไร ไปเลย
Article1และ
Article2 ):
<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>
ต่อไป มาสร้างแพ็คเกจ
mapperถัดจาก
dtoและ
model จากคลาสที่เราแสดงไปก่อนหน้านี้ คุณจะต้องสร้างผู้ทำแผนที่เพิ่มอีกห้าคน:
- Mapper LectureModel <-> LectureDTO
- รายการผู้ทำแผนที่<LectureModel> <-> รายการ<LectureDTO>
- Mapper อาจารย์โมเดล <-> อาจารย์สทท
- รายการผู้ทำแผนที่<LecturerModel> <-> รายการ<LecturerDTO>
- Mapper StudentModel <-> StudentDTO
ไป:
การบรรยายMapper
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);
}
อาจารย์แมปเปอร์
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);
}
อาจารย์ListMapper
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คอมไพล์ แต่ปรากฏว่าเมื่อสร้างการใช้งาน Mapstruct ของผู้ทำแผนที่ของเรา การใช้งานผู้ทำแผนที่ไม่ได้เขียนทับฟิลด์ ทำไม ปรากฎว่าไม่สามารถรับคำอธิบายประกอบข้อมูลจากลอมบอกได้ และต้องทำอะไรบางอย่าง... ดังนั้นเราจึงมีหัวข้อใหม่ในบทความ
การเชื่อมโยงลอมบอกและ Mapstruct
หลังจากค้นหาไม่กี่นาที ปรากฏว่าเราต้องเชื่อมต่อลอมบอกและ 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>
และมาอัปเดตปลั๊กอินคอมไพเลอร์ของเราเพื่อให้สามารถเชื่อมต่อลอมบอกและ 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>
หลังจากนี้ทุกอย่างควรจะได้ผล มารวบรวมโครงการของเราอีกครั้ง แต่คุณจะพบคลาสที่ Mapstruct สร้างขึ้นได้ที่ไหน? พวกเขาอยู่ในแหล่งที่มาที่สร้างขึ้น:
${projectDir}/target/generated-sources/annotations/ ![Mapstruct คืออะไรและจะกำหนดค่าอย่างไรให้เหมาะสมสำหรับการทดสอบหน่วยในแอปพลิเคชัน SpringBoot ส่วนที่ 1 - 3]()
ตอนนี้เราพร้อมที่จะตระหนักถึงความผิดหวังของฉันจากโพสต์ 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LectureMapperTest {
@Autowired
private LectureMapper mapperUnderTest;
@Test
void shouldProperlyMapModelToDto() {
LectureModel model = new LectureModel();
model.setId(11L);
model.setName("lecture name");
LectureDTO dto = mapperUnderTest.toDTO(model);
Assertions.assertNotNull(dto);
Assertions.assertEquals(model.getId(), dto.getId());
Assertions.assertEquals(model.getName(), dto.getName());
}
@Test
void shouldProperlyMapDtoToModel() {
LectureDTO dto = new LectureDTO();
dto.setId(11L);
dto.setName("lecture name");
LectureModel model = mapperUnderTest.toModel(dto);
Assertions.assertNotNull(model);
Assertions.assertEquals(dto.getId(), model.getId());
Assertions.assertEquals(dto.getName(), model.getName());
}
}
ที่นี่โดยใช้คำอธิบายประกอบ SpringBootTest เราเปิดตัว applicationContext ทั้งหมด และจากนั้นโดยใช้คำอธิบายประกอบ Autowired เราแยกคลาสที่เราต้องการสำหรับการทดสอบ ในแง่ของความรวดเร็วและความสะดวกในการเขียนแบบทดสอบถือว่าดีมาก การทดสอบผ่านไปด้วยดี ทุกอย่างเรียบร้อยดี แต่เราจะไปอีกทางหนึ่งและเขียน Unit Test สำหรับผู้ทำแผนที่ เช่น 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() {
LectureDTO dto = new LectureDTO();
dto.setId(12L);
dto.setName("I'm BATMAN!");
List<LectureDTO> dtos = Collections.singletonList(dto);
List<LectureModel> models = lectureListMapper.toModelList(dtos);
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 ถึงใช้ mapper ด้วยวิธีนี้! ใน Spring คุณสามารถเพิ่ม bean ลงในคลาสได้หลายวิธี คุณสามารถแทรกถั่วผ่านฟิลด์พร้อมกับคำอธิบายประกอบแบบ Autowired ดังที่ทำข้างต้น หรือคุณสามารถฉีดถั่วผ่านตัวสร้างก็ได้ ฉันพบว่าตัวเองตกอยู่ในสถานการณ์ที่มีปัญหาในที่ทำงานเมื่อฉันต้องการปรับเวลาดำเนินการทดสอบให้เหมาะสม ฉันคิดว่าคงทำอะไรไม่ได้และระบายความเจ็บปวดของฉันลงในช่อง Telegram ของฉัน จากนั้นพวกเขาก็ช่วยฉันในการแสดงความคิดเห็นและบอกว่าสามารถปรับแต่งกลยุทธ์การฉีดได้ อินเท อร์เฟซ Mapper มี ฟิลด์
การฉีด Strategyซึ่งเพิ่งยอมรับ ชื่อ
ฉีดซึ่งมีสองค่า:
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);
}
ฉันเน้นส่วนที่ฉันเพิ่มด้วยตัวหนา มาเพิ่มตัวเลือกนี้สำหรับตัวเลือกอื่นๆ ทั้งหมดและคอมไพล์โปรเจ็กต์ใหม่เพื่อให้ผู้ทำแผนที่ถูกสร้างขึ้นด้วยบรรทัดใหม่ เมื่อเสร็จแล้ว เรามาดูกันว่าการใช้งาน mapper สำหรับ
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 ได้ดำเนินการ mapper injection ผ่าน Constructor แล้ว นี่คือสิ่งที่เราพยายามทำให้สำเร็จ ตอนนี้การทดสอบของเราจะหยุดการคอมไพล์ มาอัปเดตและรับ:
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() {
LectureDTO dto = new LectureDTO();
dto.setId(12L);
dto.setName("I'm BATMAN!");
List<LectureDTO> dtos = Collections.singletonList(dto);
List<LectureModel> models = lectureListMapper.toModelList(dtos);
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 ของฉัน ไปยังบัญชี Telegram ของ ฉัน ฉันโพสต์ผลลัพธ์กิจกรรมของฉันที่นั่นมีประโยชน์จริงๆ) ฉันขอเชิญคุณเข้าร่วมกลุ่มสนทนาของช่องโทรเลข เป็นพิเศษ มันเกิดขึ้นว่าหากใครมีคำถามทางเทคนิค พวกเขาสามารถได้รับคำตอบที่นั่น รูปแบบนี้น่าสนใจสำหรับทุกคน คุณสามารถอ่านใครจะรู้อะไรและรับประสบการณ์ได้
บทสรุป
ในส่วนหนึ่งของบทความนี้ เราได้ทำความคุ้นเคยกับผลิตภัณฑ์ที่จำเป็นและใช้บ่อยเช่น Mapstruct เราพบว่ามันคืออะไร ทำไม และอย่างไร จากตัวอย่างจริง เรารู้สึกว่าสิ่งใดสามารถทำได้และจะเปลี่ยนแปลงได้อย่างไร นอกจากนี้เรายังดูวิธีตั้งค่าการฉีด bean ผ่าน Constructor เพื่อให้สามารถทดสอบ Mappers ได้อย่างเหมาะสม เพื่อนร่วมงานจาก Mapstruct อนุญาตให้ผู้ใช้ผลิตภัณฑ์ของตนเลือกวิธีการแทรกผู้ทำแผนที่ได้อย่างแน่นอน ซึ่งเราต้องขอบคุณพวกเขาอย่างไม่ต้องสงสัย แต่ถึงแม้ว่า Spring จะแนะนำให้ฉีด bean ผ่าน Constructor แต่คนจาก Mapstruct ก็ได้ตั้งค่าการฉีดผ่านสนามเป็นค่าเริ่มต้น ทำไมเป็นอย่างนั้น? ไม่มีคำตอบ. ฉันสงสัยว่าอาจมีเหตุผลที่เราไม่รู้ และนั่นคือสาเหตุที่พวกเขาทำเช่นนี้ และเพื่อหาคำตอบจากพวกเขา ฉันจึงได้สร้าง
ปัญหา GitHubบนคลังผลิตภัณฑ์อย่างเป็นทางการของพวกเขา
GO TO FULL VERSION