JavaRush /Blog Java /Random-VI /Mapstruct là gì và cách cấu hình nó đúng cách để kiểm thử...
Roman Beekeeper
Mức độ

Mapstruct là gì và cách cấu hình nó đúng cách để kiểm thử đơn vị trong ứng dụng SpringBoot

Xuất bản trong nhóm

Lý lịch

Xin chào tất cả mọi người, các bạn và độc giả thân mến của tôi! Trước khi chúng tôi viết bài, một chút thông tin cơ bản... Gần đây tôi đã gặp phải một sự cố khi làm việc với thư viện Mapstruct , vấn đề mà tôi đã mô tả ngắn gọn trong kênh telegram của mình tại đây . Vấn đề với bài đăng đã được giải quyết trong phần bình luận; đồng nghiệp của tôi từ dự án trước đã giúp giải quyết vấn đề này. Mapstruct là gì và cách cấu hình nó đúng cách để kiểm tra đơn vị trong các ứng dụng SpringBoot.  Phần 1 - 1Sau đó, tôi quyết định viết một bài về chủ đề này, nhưng tất nhiên chúng tôi sẽ không có cái nhìn hạn hẹp và trước tiên sẽ cố gắng bắt kịp tốc độ, hiểu Mapstruct là gì và tại sao lại cần thiết, đồng thời sử dụng một ví dụ thực tế, chúng tôi sẽ phân tích tình huống phát sinh trước đó và cách giải quyết. Vì vậy, tôi thực sự khuyên bạn nên thực hiện tất cả các tính toán song song với việc đọc bài viết để trải nghiệm mọi thứ trong thực tế. Trước khi chúng ta bắt đầu, hãy đăng ký kênh telegram của tôi , tôi thu thập các hoạt động của mình ở đó, viết những suy nghĩ về sự phát triển của Java và CNTT nói chung. Đã đăng ký? Tuyệt vời! Bây giờ chúng ta đi thôi!

Cấu trúc bản đồ, câu hỏi thường gặp?

Trình tạo mã để ánh xạ đậu an toàn loại nhanh. Nhiệm vụ đầu tiên của chúng ta là tìm hiểu Mapstruct là gì và tại sao chúng ta cần nó. Nói chung, bạn có thể đọc về nó trên trang web chính thức. Trên trang chính của trang web có ba câu trả lời cho các câu hỏi: nó là gì? Để làm gì? Làm sao? Chúng tôi cũng sẽ cố gắng làm điều này:

Nó là gì?

Mapstruct là một thư viện giúp ánh xạ (nói chung là bản đồ, đó là những gì họ luôn nói: bản đồ, bản đồ, v.v.) các đối tượng của một số thực thể này thành các đối tượng của các thực thể khác bằng cách sử dụng mã được tạo dựa trên các cấu hình được mô tả thông qua các giao diện.

Để làm gì?

Phần lớn, chúng tôi phát triển các ứng dụng nhiều lớp (lớp làm việc với cơ sở dữ liệu, lớp logic nghiệp vụ, lớp tương tác của ứng dụng với thế giới bên ngoài) và mỗi lớp có các đối tượng riêng để lưu trữ và xử lý dữ liệu. . Và dữ liệu này cần được chuyển từ lớp này sang lớp khác bằng cách chuyển từ thực thể này sang thực thể khác. Đối với những người chưa từng làm việc với phương pháp này, điều này có vẻ hơi phức tạp. Ví dụ: chúng tôi có một thực thể cho cơ sở dữ liệu Sinh viên. Khi dữ liệu của thực thể này đi đến lớp logic nghiệp vụ (dịch vụ), chúng ta cần chuyển dữ liệu từ lớp Sinh viên sang lớp Sinh viên. Tiếp theo, sau tất cả các thao tác với logic nghiệp vụ, dữ liệu cần được đưa ra bên ngoài. Và để làm điều này, chúng ta có lớp Sinh viênDto. Tất nhiên, chúng ta cần truyền dữ liệu từ lớp Sinh viên Model tới Sinh viênDto. Viết bằng tay mỗi lần các phương pháp sẽ được chuyển giao sẽ tốn nhiều công sức. Ngoài ra, đây là mã bổ sung trong cơ sở mã cần được duy trì. Bạn có thể phạm sai lầm. Và Mapstruct tạo ra các phương thức như vậy ở giai đoạn biên dịch và lưu trữ chúng trong các nguồn được tạo.

Làm sao?

Sử dụng chú thích. Chúng ta chỉ cần tạo một chú thích có chú thích Mapper chính cho thư viện biết rằng các phương thức trong giao diện này có thể được sử dụng để dịch từ đối tượng này sang đối tượng khác. Như tôi đã nói trước đó về học sinh, trong trường hợp của chúng ta, đây sẽ là giao diện StudentMapper, giao diện này sẽ có một số phương thức để truyền dữ liệu từ lớp này sang lớp khác:
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;
}
Đối với các lớp này, chúng tôi tạo một trình ánh xạ (sau đây đây là tên mà chúng tôi sẽ gọi là giao diện, mô tả những gì chúng tôi muốn chuyển và ở đâu):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
Cái hay của cách tiếp cận này là nếu tên và loại trường giống nhau trong các lớp khác nhau (như trong trường hợp của chúng tôi), thì cài đặt cho Mapstruct là đủ để tạo ra triển khai cần thiết dựa trên giao diện StudentMapper ở giai đoạn biên dịch, điều này Sẽ dịch. Vậy là mọi chuyện đã trở nên rõ ràng hơn rồi phải không? Hãy đi xa hơn và sử dụng một ví dụ thực tế để phân tích công việc trong ứng dụng Spring Boot.

Một ví dụ về Spring Boot và Mapstruct hoạt động

Điều đầu tiên chúng ta cần là tạo một dự án Spring Boot và thêm Mapstruct vào đó. Đối với vấn đề này, tôi có một tổ chức trong GitHub với các mẫu cho kho lưu trữ và khởi đầu cho Spring Boot là một trong số đó. Dựa vào đó, chúng ta tạo một dự án mới: Mapstruct là gì và cách cấu hình nó đúng cách để kiểm tra đơn vị trong các ứng dụng SpringBoot.  Phần 1 - 2Tiếp theo, chúng ta lấy dự án . Vâng, các bạn ơi, hãy cho dự án một ngôi sao nếu bạn thấy nó hữu ích, để tôi biết rằng mình làm điều này không phải là vô ích. Trong dự án này, chúng tôi sẽ tiết lộ một tình huống mà tôi gặp phải tại nơi làm việc và được mô tả trong một bài đăng trên kênh Telegram của tôi . Tôi sẽ phác thảo ngắn gọn tình huống cho những người chưa biết: khi chúng tôi viết bài kiểm tra cho người lập bản đồ (nghĩa là đối với những triển khai giao diện mà chúng tôi đã nói trước đó), chúng tôi muốn các bài kiểm tra vượt qua càng nhanh càng tốt. Tùy chọn đơn giản nhất với trình ánh xạ là sử dụng chú thích SpringBootTest khi chạy thử nghiệm, chú thích này sẽ lấy toàn bộ ApplicationContext của ứng dụng Spring Boot và đưa trình ánh xạ cần thiết cho thử nghiệm vào bên trong thử nghiệm. Nhưng tùy chọn này tốn nhiều tài nguyên và mất nhiều thời gian hơn nên không phù hợp với chúng tôi. Chúng ta cần viết một bài kiểm thử đơn vị chỉ đơn giản là tạo trình ánh xạ mong muốn và kiểm tra xem các phương thức của nó có hoạt động chính xác như chúng ta mong đợi hay không. Tại sao bạn cần kiểm tra để chạy nhanh hơn? Nếu việc kiểm thử mất nhiều thời gian, nó sẽ làm chậm toàn bộ quá trình phát triển. Cho đến khi các thử nghiệm vượt qua mã mới, mã này không thể được coi là chính xác và sẽ không được lấy để thử nghiệm, điều đó có nghĩa là nó sẽ không được đưa vào sản xuất và điều đó có nghĩa là nhà phát triển chưa hoàn thành công việc. Có vẻ như, tại sao lại viết bài kiểm tra cho một thư viện có hoạt động không thể nghi ngờ? Tuy nhiên, chúng tôi cần phải viết một bài kiểm tra, bởi vì chúng tôi đang kiểm tra xem chúng tôi đã mô tả trình ánh xạ chính xác đến mức nào và liệu nó có hoạt động như những gì chúng tôi mong đợi hay không. Trước hết, để làm cho công việc của chúng ta dễ dàng hơn, hãy thêm Lombok vào dự án của chúng ta bằng cách thêm một phần phụ thuộc khác vào pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
Trong dự án của chúng tôi, chúng tôi sẽ cần chuyển từ các lớp mô hình (được sử dụng để làm việc với logic nghiệp vụ) sang các lớp DTO mà chúng tôi sử dụng để giao tiếp với thế giới bên ngoài. Trong phiên bản đơn giản hóa của chúng tôi, chúng tôi sẽ giả định rằng các trường không thay đổi và trình ánh xạ của chúng tôi sẽ đơn giản. Tuy nhiên, nếu có mong muốn, bạn có thể viết một bài viết chi tiết hơn về cách làm việc với Mapstruct, cách định cấu hình và cách tận dụng các lợi ích của nó. Nhưng sau đó, vì bài viết này sẽ khá dài. Giả sử chúng ta có một sinh viên có danh sách các bài giảng và giảng viên mà anh ta theo học. Hãy tạo một gói mô hình . Dựa trên điều này, chúng ta sẽ tạo ra một mô hình đơn giản:
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;
}
bài giảng của anh ấy
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
và giảng viên
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Và tạo gói dto bên cạnh gói mô hình :
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;
}
bài giảng
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
và giảng viên
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Bây giờ, hãy tạo một trình ánh xạ sẽ dịch một tập hợp các mô hình bài giảng thành một tập hợp các bài giảng DTO. Điều đầu tiên cần làm là thêm Mapstruct vào dự án. Để làm điều này, chúng tôi sẽ sử dụng trang web chính thức của họ , mọi thứ đều được mô tả ở đó. Nghĩa là, chúng ta cần thêm một phần phụ thuộc và một plugin vào bộ nhớ của mình (nếu bạn có thắc mắc về bộ nhớ là gì, hãy xem Article1Article2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
và trong bộ nhớ trong khối <build/>. mà chúng tôi chưa có:
<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>
Tiếp theo, hãy tạo gói ánh xạ bên cạnh dtomodel . Dựa trên các lớp mà chúng tôi đã trình bày trước đó, bạn sẽ cần tạo thêm năm trình lập bản đồ:
  • Bài giảng MapperModel <-> Bài giảngDTO
  • Danh sách trình ánh xạ<LectureModel> <-> Danh sách<LectureDTO>
  • Mapper Giảng viênModel <-> Giảng viênDTO
  • Danh sách người lập bản đồ<LecturerModel> <-> Danh sách<LecturerDTO>
  • Mapper Sinh viênModel <-> Sinh viênDTO
Đi:

Bài giảngMapper

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);
}

Bài giảngDanh sáchMapper

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);
}

Giảng viênMapper

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);
}

Giảng viênDanh sáchMapper

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);
}

Người lập bản đồ sinh viên

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);
}
Cần lưu ý riêng rằng trong những người lập bản đồ, chúng tôi đề cập đến những người lập bản đồ khác. Điều này được thực hiện thông qua trường sử dụng trong chú thích của Mapper, như được thực hiện trong StudentMapper:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Ở đây chúng tôi sử dụng hai trình ánh xạ để ánh xạ chính xác danh sách bài giảng và danh sách giảng viên. Bây giờ chúng ta cần biên dịch mã của mình và xem những gì ở đó và như thế nào. Điều này có thể được thực hiện bằng lệnh mvn clean biên dịch . Tuy nhiên, hóa ra, khi tạo các triển khai Mapstruct cho người lập bản đồ của chúng tôi, việc triển khai người lập bản đồ đã không ghi đè các trường. Tại sao? Hóa ra không thể lấy Chú thích dữ liệu từ Lombok. Và phải làm gì đó... Vì vậy, chúng ta có một phần mới trong bài viết.

Liên kết Lombok và Mapstruct

Sau vài phút tìm kiếm, hóa ra chúng tôi cần kết nối Lombok và Mapstruct theo một cách nhất định. Có thông tin về điều này trong tài liệu Mapstruct . Sau khi xem xét ví dụ do các nhà phát triển Mapstruct đề xuất, hãy cập nhật pom.xml của chúng tôi: Hãy thêm các phiên bản riêng biệt:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Hãy thêm phần phụ thuộc còn thiếu:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
Và hãy cập nhật plugin trình biên dịch của chúng tôi để nó có thể kết nối Lombok và 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>
Sau này mọi thứ sẽ ổn thỏa. Hãy biên dịch lại dự án của chúng tôi. Nhưng bạn có thể tìm thấy các lớp mà Mapstruct đã tạo ở đâu? Chúng nằm trong các nguồn được tạo: ${projectDir}/target/generated-sources/annotations/ Mapstruct là gì và cách cấu hình nó đúng cách để kiểm tra đơn vị trong các ứng dụng SpringBoot.  Phần 1 - 3 Bây giờ chúng ta đã sẵn sàng để nhận ra sự thất vọng của mình về bài đăng trên Mapstruct, hãy thử tạo các thử nghiệm cho người lập bản đồ.

Chúng tôi viết bài kiểm tra cho người lập bản đồ của mình

Tôi sẽ tạo một thử nghiệm nhanh chóng và đơn giản để kiểm tra một trong những người lập bản đồ trong trường hợp chúng tôi đang tạo thử nghiệm tích hợp và đừng lo lắng về thời gian hoàn thành:

Bài giảngMapperTest

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());
   }
}
Ở đây, bằng cách sử dụng chú thích SpringBootTest, chúng tôi khởi chạy toàn bộ applicationContext và từ đó, bằng cách sử dụng chú thích Autowired, chúng tôi trích xuất lớp mà chúng tôi cần để thử nghiệm. Từ quan điểm về tốc độ và sự dễ dàng khi viết bài kiểm tra, điều này rất tốt. Bài kiểm tra đã thành công, mọi thứ đều ổn. Nhưng chúng ta sẽ đi theo một cách khác và viết một bài kiểm tra đơn vị cho một trình ánh xạ, ví dụ: LessonListMapper...
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());
   }
}
Vì các triển khai mà Mapstruct tạo ra nằm cùng lớp với dự án của chúng tôi nên chúng tôi có thể dễ dàng sử dụng chúng trong các thử nghiệm của mình. Mọi thứ đều trông tuyệt vời - không có chú thích, chúng tôi tạo lớp mà chúng tôi cần theo cách đơn giản nhất và thế là xong. Nhưng khi chạy thử nghiệm, chúng tôi sẽ hiểu rằng nó sẽ gặp sự cố và sẽ có NullPointerException trong bảng điều khiển... Điều này là do quá trình triển khai trình ánh xạ Bài giảngListMapper trông như sau:
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;
   }
}
Nếu chúng ta nhìn vào NPE (viết tắt của NullPointerException), chúng ta sẽ nhận được nó từ biến bài giảngMapper , hóa ra nó không được khởi tạo. Nhưng trong quá trình triển khai, chúng tôi không có hàm tạo để có thể khởi tạo biến. Đây chính xác là lý do tại sao Mapstruct triển khai trình ánh xạ theo cách này! Trong Spring, bạn có thể thêm đậu vào các lớp theo nhiều cách, bạn có thể đưa chúng vào một trường cùng với chú thích Autowired, như đã thực hiện ở trên hoặc bạn có thể đưa chúng vào thông qua một hàm tạo. Tôi thấy mình đang ở trong một tình huống rắc rối tại nơi làm việc khi tôi cần tối ưu hóa thời gian thực hiện kiểm thử. Tôi nghĩ rằng không thể làm gì được và trút nỗi đau lên kênh Telegram của mình. Và sau đó họ đã giúp tôi nhận xét và nói rằng có thể tùy chỉnh chiến lược tiêm. Giao diện Mapper có trường tiêmStrategy , chỉ chấp nhận tên TiêmStrategy , có hai giá trị: FIELDCONSTRUCTOR . Bây giờ, biết được điều này, hãy thêm cài đặt này vào người lập bản đồ của chúng ta; Tôi sẽ hiển thị nó bằng cách sử dụng Bài giảngListMapper làm ví dụ :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
Tôi đã in đậm phần tôi đã thêm. Hãy thêm tùy chọn này cho tất cả những tùy chọn khác và biên dịch lại dự án để những người lập bản đồ được tạo bằng một dòng mới. Khi chúng tôi thực hiện việc này, hãy xem việc triển khai trình ánh xạ cho LessonListMapper đã thay đổi như thế nào (được đánh dấu in đậm phần mà chúng tôi cần):
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;
   }
}
Và bây giờ Mapstruct đã triển khai việc đưa ánh xạ vào thông qua hàm tạo. Đây chính xác là những gì chúng tôi đang cố gắng đạt được. Bây giờ bài kiểm tra của chúng tôi sẽ ngừng biên dịch, hãy cập nhật nó và nhận:
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());
   }
}
Bây giờ, nếu chúng ta chạy thử nghiệm thì mọi thứ sẽ hoạt động như mong đợi, vì trong Bài giảngListMapperImpl, chúng ta đã vượt qua Bài giảng Mapper mà nó cần... Chiến thắng! Điều đó không khó với bạn, nhưng tôi rất vui: Các bạn, mọi thứ vẫn như bình thường, hãy đăng ký tài khoản GitHub của tôi , tài khoản Telegram của tôi . Ở đó tôi đăng kết quả hoạt động của mình, có những điều thực sự hữu ích) Tôi đặc biệt mời các bạn tham gia nhóm thảo luận của kênh telegram . Điều đó xảy ra là nếu ai đó có câu hỏi kỹ thuật, họ có thể nhận được câu trả lời ở đó. Định dạng này thú vị cho tất cả mọi người, bạn có thể đọc ai biết gì và tích lũy kinh nghiệm.

Phần kết luận

Là một phần của bài viết này, chúng ta đã làm quen với một sản phẩm cần thiết và được sử dụng thường xuyên như Mapstruct. Chúng tôi đã tìm ra nó là gì, tại sao và như thế nào. Sử dụng một ví dụ thực tế, chúng tôi cảm nhận được điều gì có thể làm được và cách thay đổi nó. Chúng tôi cũng đã xem xét cách thiết lập việc chèn đậu thông qua hàm tạo để có thể kiểm tra người lập bản đồ một cách chính xác. Các đồng nghiệp từ Mapstruct đã cho phép người dùng sản phẩm của họ chọn chính xác cách đưa người lập bản đồ vào, điều mà chúng tôi chắc chắn cảm ơn họ. NHƯNG, mặc dù thực tế là Spring khuyên bạn nên tiêm đậu thông qua hàm tạo, nhưng những người từ Mapstruct đã thiết lập việc tiêm qua trường theo mặc định. Tại sao vậy? Không có câu trả lời. Tôi nghi ngờ có thể có những lý do mà chúng ta không biết, và đó là lý do tại sao họ làm theo cách này. Và để tìm hiểu từ họ, tôi đã tạo một vấn đề GitHub trên kho sản phẩm chính thức của họ.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION