JavaRush /Blog Java /Random-PL /Co to jest Mapstruct i jak poprawnie go skonfigurować do ...
Roman Beekeeper
Poziom 35

Co to jest Mapstruct i jak poprawnie go skonfigurować do testów jednostkowych w aplikacjach SpringBoot

Opublikowano w grupie Random-PL

Tło

Witam wszystkich, moi drodzy przyjaciele i czytelnicy! Zanim napiszemy artykuł, trochę tła... Niedawno napotkałem problem w pracy z biblioteką Mapstruct , który pokrótce opisałem tutaj na moim kanale telegramu . Problem z postem został rozwiązany w komentarzach, pomógł mi w tym kolega z poprzedniego projektu. Co to jest Mapstruct i jak poprawnie go skonfigurować do testów jednostkowych w aplikacjach SpringBoot.  Część 1 - 1Potem zdecydowałem się napisać artykuł na ten temat, ale oczywiście nie będziemy patrzeć wąsko i najpierw spróbujemy nabrać tempa, zrozumieć, czym jest Mapstruct i dlaczego jest potrzebny, a na prawdziwym przykładzie będziemy przeanalizuj sytuację, która powstała wcześniej i jak ją rozwiązać. Dlatego zdecydowanie zalecam wykonanie wszystkich obliczeń równolegle z czytaniem artykułu, aby doświadczyć wszystkiego w praktyce. Zanim zaczniemy zasubskrybuj mój kanał telegramowy , zbieram tam moją aktywność, piszę przemyślenia na temat rozwoju w Javie i IT w ogóle. Zasubskrybowałeś? Świetnie! Cóż, teraz chodźmy!

Mapstruct, często zadawane pytania?

Generator kodu do szybkiego mapowania komponentów bean z bezpiecznym typem. Naszym pierwszym zadaniem jest dowiedzieć się, czym jest Mapstruct i dlaczego go potrzebujemy. Generalnie można o tym przeczytać na oficjalnej stronie internetowej. Na stronie głównej serwisu znajdują się trzy odpowiedzi na pytania: co to jest? Po co? Jak? Spróbujemy też to zrobić:

Co to jest?

Mapstruct to biblioteka pomagająca mapować (mapować w ogóle, jak to zawsze się mówi: mapa, mapa itp.) obiektów jednych obiektów na obiekty innych bytów przy użyciu wygenerowanego kodu w oparciu o konfiguracje opisane poprzez interfejsy.

Po co?

W przeważającej części tworzymy aplikacje wielowarstwowe (warstwa do pracy z bazą danych, warstwa logiki biznesowej, warstwa do interakcji aplikacji ze światem zewnętrznym) i każda warstwa ma własne obiekty do przechowywania i przetwarzania danych . Dane te należy przenosić z warstwy na warstwę, przesyłając z jednej jednostki do drugiej. Dla tych, którzy nie pracowali z tym podejściem, może się to wydawać nieco skomplikowane. Na przykład mamy encję dla bazy danych Studentów. Kiedy dane tej encji trafiają do warstwy logiki biznesowej (usług), musimy przenieść dane z klasy Student do klasy StudentModel. Następnie, po wszystkich manipulacjach logiką biznesową, dane należy wypuścić na zewnątrz. I do tego mamy klasę StudentDto. Musimy oczywiście przekazać dane z klasy StudentModel do StudentDto. Ręczne pisanie za każdym razem metod, które będą przenoszone, jest pracochłonne. Poza tym jest to dodatkowy kod w bazie kodu, który należy utrzymać. Możesz popełnić błąd. Mapstruct generuje takie metody na etapie kompilacji i przechowuje je w generowanych źródłach.

Jak?

Korzystanie z adnotacji. Musimy tylko utworzyć adnotację zawierającą główną adnotację Mappera, która poinformuje bibliotekę, że metod w tym interfejsie można użyć do translacji z jednego obiektu na inny. Tak jak mówiłem wcześniej o studentach, w naszym przypadku będzie to interfejs StudentMapper, który będzie miał kilka metod przesyłania danych z jednej warstwy na drugą:
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;
}
Dla tych klas tworzymy mapper (w dalszej części tak będziemy nazywać interfejs, który opisuje co i gdzie chcemy przesłać):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
Piękno tego podejścia polega na tym, że jeśli nazwy i typy pól są takie same w różnych klasach (jak w naszym przypadku), to ustawienia dla Mapstruct wystarczą, aby na etapie kompilacji wygenerować niezbędną implementację w oparciu o interfejs StudentMapper, co przetłumaczę. Zatem wszystko stało się już jaśniejsze, prawda? Pójdźmy dalej i na prawdziwym przykładzie przeanalizujmy pracę w aplikacji Spring Boot.

Przykład działania Spring Boot i Mapstruct

Pierwszą rzeczą, której potrzebujemy, jest utworzenie projektu Spring Boot i dodanie do niego Mapstruct. Do tego mam organizację w GitHubie z szablonami repozytoriów i jednym z nich jest start dla Spring Boot. Na tej podstawie tworzymy nowy projekt: Co to jest Mapstruct i jak poprawnie go skonfigurować do testów jednostkowych w aplikacjach SpringBoot.  Część 1 - 2Następnie pobieramy projekt . Tak, przyjaciele, dajcie projektowi gwiazdkę , jeśli uznacie go za przydatny, abym wiedział, że nie robię tego na próżno. W tym projekcie ujawnimy sytuację, którą spotkałem w pracy i opisałem w poście na moim kanale Telegram . Dla niewtajemniczonych pokrótce przedstawię sytuację: pisząc testy dla maperów (czyli dla tych implementacji interfejsu, o których mówiliśmy wcześniej), zależy nam na tym, aby testy przebiegły jak najszybciej. Najprostszą opcją w przypadku maperów jest użycie adnotacji SpringBootTest podczas uruchamiania testu, która pobierze cały kontekst aplikacji aplikacji Spring Boot i wstrzyknie do wnętrza testu narzędzie mapujące potrzebne do testu. Ale ta opcja wymaga dużych zasobów i zajmuje znacznie więcej czasu, więc nie jest dla nas odpowiednia. Musimy napisać test jednostkowy, który po prostu utworzy pożądany program mapujący i sprawdzi, czy jego metody działają dokładnie tak, jak tego oczekujemy. Dlaczego potrzebujesz testów, aby działać szybciej? Jeśli testy zajmują dużo czasu, spowalnia to cały proces programowania. Dopóki testy nie przekażą nowego kodu, kod ten nie może być uznany za poprawny i nie zostanie przyjęty do testów, co oznacza, że ​​nie zostanie przyjęty na produkcję i co oznacza, że ​​programista nie dokończył pracy. Wydawałoby się, po co pisać test dla biblioteki, której działanie nie budzi wątpliwości? A jednak musimy napisać test, bo sprawdzamy, jak poprawnie opisaliśmy mapera i czy robi to, czego oczekujemy. Na początek, żeby ułatwić nam pracę, dodajmy do naszego projektu Lomboka, dodając kolejną zależność do pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
W naszym projekcie będziemy musieli przejść z klas modelowych (które służą do pracy z logiką biznesową) na klasy DTO, za pomocą których komunikujemy się ze światem zewnętrznym. W naszej uproszczonej wersji założymy, że pola się nie zmieniają, a nasze mapery będą proste. Ale jeśli istnieje taka potrzeba, można napisać bardziej szczegółowy artykuł na temat pracy z Mapstruct, jak go skonfigurować i jak skorzystać z jego zalet. Ale potem, ponieważ ten artykuł będzie dość długi. Załóżmy, że mamy studenta z listą wykładów i wykładowców, na których uczęszcza. Stwórzmy pakiet modelowy . Na tej podstawie stworzymy prosty model:
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;
}
jego wykłady
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
i wykładowcy
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
I utwórz pakiet dto obok pakietu modelu :
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;
}
Wykłady
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
i wykładowcy
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Stwórzmy teraz program mapujący, który przetłumaczy zbiór modeli wykładów na zbiór wykładów DTO. Pierwszą rzeczą do zrobienia jest dodanie Mapstruct do projektu. W tym celu skorzystamy z ich oficjalnej strony internetowej , wszystko jest tam opisane. Oznacza to, że musimy dodać jedną zależność i wtyczkę do naszej pamięci (jeśli masz pytania dotyczące tego, czym jest pamięć, proszę bardzo, Artykuł1 i Artykuł2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
oraz w pamięci w bloku <build/>. których jeszcze nie mieliśmy:
<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>
Następnie utwórzmy pakiet mapujący obok dto i model . Bazując na klasach, które pokazaliśmy wcześniej, będziesz musiał stworzyć jeszcze pięć maperów:
  • Mapper LectureModel <-> LectureDTO
  • Lista maperów<WykładModel> <-> Lista<WykładDTO>
  • Maper WykładowcaModel <-> WykładowcaDTO
  • Lista map<LecturerModel> <-> Lista<LecturerDTO>
  • Mapowanie StudentModel <-> StudentDTO
Iść:

WykładMapper

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

WykładowcaMapper

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

WykładowcaListMapper

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);
}
Należy osobno zaznaczyć, że w mapperach odnosimy się do innych mapperów. Odbywa się to poprzez pole zastosowań w adnotacji Mappera, tak jak ma to miejsce w StudentMapperze:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Tutaj używamy dwóch maperów, aby poprawnie zmapować listę wykładów i listę wykładowców. Teraz musimy skompilować nasz kod i zobaczyć, co tam jest i jak. Można to zrobić za pomocą polecenia kompilacji mvn clean . Jednak jak się okazało, podczas tworzenia implementacji Mapstruct naszych mapperów, implementacje mappera nie nadpisywały pól. Dlaczego? Okazało się, że nie można było pobrać adnotacji Data z Lomboka. I coś trzeba było zrobić... Dlatego mamy nowy rozdział w artykule.

Łączenie Lomboka i Mapstructa

Po kilku minutach poszukiwań okazało się, że trzeba w określony sposób połączyć Lombok i Mapstruct. Informacje na ten temat znajdują się w dokumentacji Mapstruct . Po przeanalizowaniu przykładu zaproponowanego przez programistów z Mapstruct zaktualizujmy nasz plik pom.xml: Dodajmy osobne wersje:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Dodajmy brakującą zależność:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
Zaktualizujmy także naszą wtyczkę kompilatora, aby mogła łączyć Lombok i 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>
Po tym wszystko powinno się ułożyć. Skompilujmy nasz projekt jeszcze raz. Ale gdzie można znaleźć klasy wygenerowane przez Mapstruct? Znajdują się one w generowanych-źródłach: ${projectDir}/target/generated-sources/annotations/ Co to jest Mapstruct i jak poprawnie go skonfigurować do testów jednostkowych w aplikacjach SpringBoot.  Część 1 - 3 Teraz, gdy jesteśmy gotowi uświadomić sobie moje rozczarowanie postem Mapstruct, spróbujmy stworzyć testy dla twórców map.

Piszemy testy dla naszych mapperów

Stworzę szybki i prosty test, który przetestuje jeden z maperów w przypadku, gdy tworzymy test integracyjny i nie martwimy się czasem jego wykonania:

WykładMapperTest

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());
   }
}
Tutaj za pomocą adnotacji SpringBootTest uruchamiamy cały applicationContext i z niego za pomocą adnotacji Autowired wyodrębniamy klasę potrzebną do testów. Z punktu widzenia szybkości i łatwości pisania testu jest to bardzo dobre. Test przeszedł pomyślnie, wszystko jest w porządku. Ale pójdziemy w drugą stronę i napiszemy test jednostkowy dla programu mapującego, na przykład 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());
   }
}
Ponieważ implementacje, które generuje Mapstruct, są tej samej klasy co nasz projekt, możemy je łatwo wykorzystać w naszych testach. Wszystko wygląda świetnie – żadnych adnotacji, w najprostszy sposób tworzymy potrzebną nam klasę i tyle. Ale kiedy uruchomimy test, zrozumiemy, że ulegnie on awarii i w konsoli pojawi się wyjątek NullPointerException... Dzieje się tak, ponieważ implementacja mapera LectureListMapper wygląda następująco:
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;
   }
}
Jeśli spojrzymy na NPE (skrót od NullPointerException), otrzymamy go ze zmiennej wykładMapper , która okazuje się nie być zainicjowana. Jednak w naszej implementacji nie mamy konstruktora, za pomocą którego moglibyśmy zainicjować zmienną. To jest dokładnie powód, dla którego Mapstruct zaimplementował narzędzie mapujące w ten sposób! Wiosną możesz dodawać komponenty bean do klas na kilka sposobów, możesz je wstrzykiwać poprzez pole wraz z adnotacją Autowired, jak pokazano powyżej, lub możesz wstrzykiwać je poprzez konstruktor. Znalazłem się w bardzo problematycznej sytuacji w pracy, gdy potrzebowałem zoptymalizować czas wykonania testu. Pomyślałem, że nic nie da się z tym zrobić i wylałem swój ból na moim kanale Telegram. A potem pomogli mi w komentarzach i powiedzieli, że można dostosować strategię wtrysku. Interfejs Mappera posiada pole wstrzykiwaniaStrategy , które akceptuje po prostu nazwę InjectionStrategy , która ma dwie wartości: FIELD i CONSTRUCTOR . Teraz, wiedząc o tym, dodajmy to ustawienie do naszych maperów; pokażę to na przykładzie 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);
}
Pogrubiłem fragment, który dodałem. Dodajmy tę opcję dla wszystkich pozostałych i przekompilujmy projekt tak, aby narzędzia mapujące były generowane z nową linią. Kiedy już to zrobimy, zobaczmy jak zmieniła się implementacja modułu mapującego dla LectureListMapper (pogrubiona część, której potrzebujemy):
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;
   }
}
A teraz Mapstruct zaimplementował wstrzykiwanie mapera przez konstruktor. To jest dokładnie to, co chcieliśmy osiągnąć. Teraz nasz test przestanie się kompilować, zaktualizujmy go i uzyskajmy:
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());
   }
}
Teraz, jeśli uruchomimy test, wszystko będzie działać zgodnie z oczekiwaniami, ponieważ w LectureListMapperImpl przekazujemy LectureMapper, którego potrzebuje... Zwycięstwa! To nie jest dla ciebie trudne, ale jestem zadowolony: Przyjaciele, wszystko jest jak zwykle, zasubskrybuj moje konto GitHub , moje konto Telegram . Tam zamieszczam rezultaty moich działań, są tam naprawdę przydatne rzeczy) Szczególnie zapraszam do dołączenia do grupy dyskusyjnej kanału telegramowego . Tak się składa, że ​​jeśli ktoś ma pytanie techniczne, to może tam uzyskać odpowiedź. Ten format jest interesujący dla każdego, można przeczytać kto wie co i zdobyć doświadczenie.

Wniosek

W ramach tego artykułu zapoznaliśmy się z tak niezbędnym i często używanym produktem jak Mapstruct. Dowiedzieliśmy się, co to jest, dlaczego i jak. Na prawdziwym przykładzie czuliśmy, co można zrobić i jak można to zmienić. Przyjrzeliśmy się także, jak ustawić wstrzykiwanie ziaren przez konstruktor, aby możliwe było prawidłowe testowanie maperów. Koledzy z Mapstruct pozwolili użytkownikom swojego produktu dokładnie wybrać sposób wstrzykiwania maperów, za co niewątpliwie im dziękujemy. ALE, pomimo tego, że Spring zaleca wstrzykiwanie fasoli przez konstruktora, goście z Mapstruct domyślnie ustawili wstrzykiwanie przez pole. Dlaczego? Brak odpowiedzi. Podejrzewam, że mogą istnieć powody, o których po prostu nie wiemy, i dlatego zrobili to w ten sposób. Aby się od nich dowiedzieć, utworzyłem problem GitHub w ich oficjalnym repozytorium produktów.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION