JavaRush /Java-Blog /Random-DE /Was ist Mapstruct und wie konfiguriert man es richtig für...

Was ist Mapstruct und wie konfiguriert man es richtig für Unit-Tests in SpringBoot-Anwendungen?

Veröffentlicht in der Gruppe Random-DE

Hintergrund

Hallo zusammen, meine lieben Freunde und Leser! Bevor wir den Artikel schreiben, ein kleiner Hintergrund... Ich bin kürzlich auf ein Problem bei der Arbeit mit der Mapstruct- Bibliothek gestoßen , das ich hier in meinem Telegram-Kanal kurz beschrieben habe . In den Kommentaren wurde das Problem mit dem Beitrag gelöst; mein Kollege aus dem Vorgängerprojekt hat dabei geholfen. Was ist Mapstruct und wie konfiguriert man es richtig für Unit-Tests in SpringBoot-Anwendungen?  Teil 1 - 1Danach habe ich beschlossen, einen Artikel zu diesem Thema zu schreiben, aber natürlich werden wir keine enge Sichtweise einnehmen und zunächst versuchen, uns auf den neuesten Stand zu bringen, zu verstehen, was Mapstruct ist und warum es benötigt wird, und zwar anhand eines realen Beispiels Analysieren Sie die zuvor aufgetretene Situation und wie Sie sie lösen können. Daher empfehle ich dringend, alle Berechnungen parallel zur Lektüre des Artikels durchzuführen, um alles in der Praxis zu erleben. Bevor wir beginnen, abonnieren Sie meinen Telegram-Kanal , ich sammle meine Aktivitäten dort und schreibe Gedanken über die Entwicklung in Java und IT im Allgemeinen. Gezeichnet? Großartig! Nun, jetzt lasst uns gehen!

Mapstruct, FAQ?

Ein Codegenerator für schnelle typsichere Bean-Mappings. Unsere erste Aufgabe besteht darin, herauszufinden, was Mapstruct ist und warum wir es brauchen. Im Allgemeinen können Sie darüber auf der offiziellen Website lesen. Auf der Hauptseite der Website gibt es drei Antworten auf die Fragen: Was ist das? Wofür? Wie? Auch das werden wir versuchen:

Was ist das?

Mapstruct ist eine Bibliothek, die dabei hilft, Objekte einiger Entitäten mithilfe generierten Codes, der auf Konfigurationen basiert, die über Schnittstellen beschrieben werden, abzubilden (im Allgemeinen heißt das immer: abbilden, abbilden usw.).

Wofür?

Meistens entwickeln wir mehrschichtige Anwendungen (eine Schicht für die Arbeit mit der Datenbank, eine Schicht für die Geschäftslogik, eine Schicht für die Interaktion der Anwendung mit der Außenwelt) und jede Schicht verfügt über eigene Objekte zum Speichern und Verarbeiten von Daten . Und diese Daten müssen von Schicht zu Schicht übertragen werden, indem sie von einer Entität zur anderen übertragen werden. Für diejenigen, die noch nicht mit diesem Ansatz gearbeitet haben, mag dies etwas kompliziert erscheinen. Wir haben zum Beispiel eine Entität für die Studentendatenbank. Wenn die Daten dieser Entität an die Ebene der Geschäftslogik (Dienste) weitergeleitet werden, müssen wir die Daten von der Student-Klasse an die StudentModel-Klasse übertragen. Als nächstes müssen die Daten nach all den Manipulationen an der Geschäftslogik nach außen freigegeben werden. Und dafür haben wir die Klasse StudentDto. Natürlich müssen wir Daten von der StudentModel-Klasse an StudentDto übergeben. Jedes Mal die Methoden, die übertragen werden sollen, von Hand zu schreiben, ist arbeitsintensiv. Außerdem handelt es sich hierbei um zusätzlichen Code in der Codebasis, der gepflegt werden muss. Sie können einen Fehler machen. Und Mapstruct generiert solche Methoden in der Kompilierungsphase und speichert sie in generierten Quellen.

Wie?

Anmerkungen verwenden. Wir müssen lediglich eine Annotation erstellen, die über eine Haupt-Mapper-Annotation verfügt, die der Bibliothek mitteilt, dass die Methoden in dieser Schnittstelle zum Übersetzen von einem Objekt in ein anderes verwendet werden können. Wie ich bereits über Studenten gesagt habe, handelt es sich in unserem Fall um die StudentMapper-Schnittstelle, die über mehrere Methoden zum Übertragen von Daten von einer Ebene auf eine andere verfügt:
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;
}
Für diese Klassen erstellen wir einen Mapper (im Folgenden nennen wir ihn die Schnittstelle, die beschreibt, was wir wohin übertragen möchten):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
Das Schöne an diesem Ansatz ist, dass, wenn die Namen und Typen der Felder in verschiedenen Klassen gleich sind (wie in unserem Fall), die Einstellungen für Mapstruct ausreichen, um in der Kompilierungsphase die erforderliche Implementierung basierend auf der StudentMapper-Schnittstelle zu generieren werde übersetzen. Es ist also schon klarer geworden, oder? Gehen wir noch einen Schritt weiter und analysieren anhand eines realen Beispiels die Arbeit in einer Spring Boot-Anwendung.

Ein Beispiel für die Funktionsweise von Spring Boot und Mapstruct

Als Erstes müssen wir ein Spring Boot-Projekt erstellen und Mapstruct hinzufügen. Aus diesem Grund habe ich in GitHub eine Organisation mit Vorlagen für Repositorys und einen Start für Spring Boot ist einer davon. Darauf aufbauend erstellen wir ein neues Projekt: Was ist Mapstruct und wie konfiguriert man es richtig für Unit-Tests in SpringBoot-Anwendungen?  Teil 1 - 2Als nächstes erhalten wir das Projekt . Ja, Freunde, gebt dem Projekt einen Stern, wenn ihr es nützlich findet, damit ich weiß, dass ich das nicht umsonst mache. In diesem Projekt werden wir eine Situation aufdecken, die ich bei der Arbeit erlebt und in einem Beitrag auf meinem Telegram-Kanal beschrieben habe . Für diejenigen, die sich nicht auskennen, möchte ich die Situation kurz skizzieren: Wenn wir Tests für Mapper schreiben (also für die Schnittstellenimplementierungen, über die wir zuvor gesprochen haben), möchten wir, dass die Tests so schnell wie möglich bestanden werden. Die einfachste Option mit Mappern besteht darin, beim Ausführen des Tests die SpringBootTest-Annotation zu verwenden, die den gesamten ApplicationContext der Spring Boot-Anwendung aufnimmt und den für den Test benötigten Mapper in den Test einfügt. Diese Option ist jedoch ressourcenintensiv und nimmt viel mehr Zeit in Anspruch, sodass sie für uns nicht geeignet ist. Wir müssen einen Unit-Test schreiben, der einfach den gewünschten Mapper erstellt und prüft, ob seine Methoden genau wie erwartet funktionieren. Warum müssen Tests schneller ausgeführt werden? Wenn Tests lange dauern, verlangsamt das den gesamten Entwicklungsprozess. Bis die Tests den neuen Code bestehen, kann dieser Code nicht als korrekt angesehen werden und wird nicht zum Testen verwendet, was bedeutet, dass er nicht in die Produktion übernommen wird und was bedeutet, dass der Entwickler die Arbeit noch nicht abgeschlossen hat. Es scheint, warum sollte man einen Test für eine Bibliothek schreiben, deren Funktionsfähigkeit außer Zweifel steht? Und doch müssen wir einen Test schreiben, denn wir testen, wie richtig wir den Mapper beschrieben haben und ob er das tut, was wir erwarten. Um unsere Arbeit zu erleichtern, fügen wir zunächst Lombok zu unserem Projekt hinzu, indem wir pom.xml eine weitere Abhängigkeit hinzufügen:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
In unserem Projekt müssen wir von Modellklassen (die für die Arbeit mit Geschäftslogik verwendet werden) auf DTO-Klassen umsteigen, die wir für die Kommunikation mit der Außenwelt verwenden. In unserer vereinfachten Version gehen wir davon aus, dass sich die Felder nicht ändern und unsere Mapper einfach sind. Wenn jedoch der Wunsch besteht, wäre es möglich, einen ausführlicheren Artikel darüber zu schreiben, wie man mit Mapstruct arbeitet, wie man es konfiguriert und wie man seine Vorteile nutzt. Aber dann wird dieser Artikel ziemlich lang sein. Nehmen wir an, wir haben einen Studenten mit einer Liste der Vorlesungen und Dozenten, die er besucht. Lassen Sie uns ein Modellpaket erstellen . Darauf aufbauend erstellen wir ein einfaches Modell:
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;
}
seine Vorträge
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
und Dozenten
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Und erstellen Sie ein DTO- Paket neben dem Modellpaket :
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;
}
Vorträge
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
und Dozenten
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Erstellen wir nun einen Mapper, der eine Sammlung von Vorlesungsmodellen in eine Sammlung von DTO-Vorlesungen übersetzt. Als Erstes müssen Sie Mapstruct zum Projekt hinzufügen. Dazu nutzen wir deren offizielle Website , dort ist alles beschrieben. Das heißt, wir müssen unserem Speicher eine Abhängigkeit und ein Plugin hinzufügen (wenn Sie Fragen dazu haben, was ein Speicher ist, hier Artikel1 und Artikel2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
und im Speicher im <build/>-Block. was wir noch nicht hatten:
<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>
Als Nächstes erstellen wir neben dto und model ein Mapper- Paket . Basierend auf den Klassen, die wir zuvor gezeigt haben, müssen Sie fünf weitere Mapper erstellen:
  • Mapper LectureModel <-> LectureDTO
  • Mapper List<LectureModel> <-> List<LectureDTO>
  • Mapper LecturerModel <-> LecturerDTO
  • Mapper List<LecturerModel> <-> List<LecturerDTO>
  • Mapper StudentModel <-> StudentDTO
Gehen:

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

DozentMapper

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

DozentListMapper

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);
}
Es sei gesondert darauf hingewiesen, dass wir uns bei Mappern auf andere Mapper beziehen. Dies erfolgt über das Feld „uses“ in der Mapper-Annotation, wie es auch in StudentMapper geschieht:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Hier verwenden wir zwei Mapper, um das Vorlesungsverzeichnis und das Dozentenverzeichnis korrekt abzubilden. Jetzt müssen wir unseren Code kompilieren und sehen, was da ist und wie. Dies kann mit dem Kompilierungsbefehl mvn clean erfolgen . Es stellte sich jedoch heraus, dass beim Erstellen von Mapstruct-Implementierungen unserer Mapper die Mapper-Implementierungen die Felder nicht überschrieben haben. Warum? Es stellte sich heraus, dass es nicht möglich war, die Datenanmerkung von Lombok abzurufen. Und es musste etwas getan werden... Deshalb haben wir einen neuen Abschnitt im Artikel.

Verknüpfung von Lombok und Mapstruct

Nach einigen Minuten der Suche stellte sich heraus, dass wir Lombok und Mapstruct auf eine bestimmte Weise verbinden mussten. Informationen hierzu finden Sie in der Mapstruct-Dokumentation . Nachdem wir das von den Entwicklern von Mapstruct vorgeschlagene Beispiel untersucht haben, aktualisieren wir unsere pom.xml: Fügen wir separate Versionen hinzu:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Fügen wir die fehlende Abhängigkeit hinzu:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
Und aktualisieren wir unser Compiler-Plugin, damit es Lombok und Mapstruct verbinden kann:
<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>
Danach sollte alles klappen. Kompilieren wir unser Projekt noch einmal. Aber wo finden Sie die Klassen, die Mapstruct generiert hat? Sie befinden sich in generierten Quellen: ${projectDir}/target/generated-sources/annotations/ Was ist Mapstruct und wie konfiguriert man es richtig für Unit-Tests in SpringBoot-Anwendungen?  Teil 1 - 3 Nachdem wir nun bereit sind, meine Enttäuschung über den Mapstruct-Beitrag zu erkennen, versuchen wir, Tests für Mapper zu erstellen.

Wir schreiben Tests für unsere Mapper

Ich werde einen schnellen und einfachen Test erstellen, der einen der Mapper testet, wenn wir einen Integrationstest erstellen und uns keine Gedanken über die Fertigstellungszeit machen:

VorlesungMapperTest

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());
   }
}
Hier starten wir mithilfe der SpringBootTest-Annotation den gesamten applicationContext und extrahieren daraus mithilfe der Autowired-Annotation die Klasse, die wir zum Testen benötigen. Unter dem Gesichtspunkt der Geschwindigkeit und der Einfachheit, einen Test zu schreiben, ist dies sehr gut. Der Test verläuft erfolgreich, alles ist in Ordnung. Aber wir gehen den anderen Weg und schreiben einen Unit-Test für einen Mapper, zum Beispiel 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());
   }
}
Da die von Mapstruct generierten Implementierungen zur gleichen Klasse wie unser Projekt gehören, können wir sie problemlos in unseren Tests verwenden. Alles sieht großartig aus – keine Anmerkungen, wir erstellen die Klasse, die wir brauchen, auf einfachste Weise und das war’s. Aber wenn wir den Test ausführen, werden wir verstehen, dass er abstürzt und in der Konsole eine NullPointerException auftritt ... Dies liegt daran, dass die Implementierung des LectureListMapper-Mappers wie folgt aussieht:
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;
   }
}
Wenn wir uns die NPE (kurz für NullPointerException) ansehen, erhalten wir sie aus der LectureMapper- Variable , die sich als nicht initialisiert herausstellt. Aber in unserer Implementierung haben wir keinen Konstruktor, mit dem wir die Variable initialisieren könnten. Genau aus diesem Grund hat Mapstruct den Mapper auf diese Weise implementiert! In Spring können Sie Beans auf verschiedene Weise zu Klassen hinzufügen: Sie können sie über ein Feld zusammen mit der Autowired-Annotation einfügen, wie oben beschrieben, oder Sie können sie über einen Konstruktor einfügen. Ich befand mich bei der Arbeit in einer so problematischen Situation, als ich die Testausführungszeit optimieren musste. Ich dachte, dass man nichts dagegen tun könnte und schüttete meinen Schmerz auf meinem Telegram-Kanal aus. Und dann haben sie mir in den Kommentaren geholfen und gesagt, dass es möglich sei, die Injektionsstrategie anzupassen. Die Mapper-Schnittstelle verfügt über ein Feld „injectionStrategy“ , das lediglich den Namen „InjectionStrategy“ akzeptiert , der zwei Werte hat: FIELD und CONSTRUCTOR . Da wir das nun wissen, fügen wir diese Einstellung zu unseren Mappern hinzu. Ich zeige sie am Beispiel von 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);
}
Den von mir hinzugefügten Teil habe ich fett hervorgehoben. Fügen wir diese Option für alle anderen hinzu und kompilieren Sie das Projekt neu, sodass Mapper mit einer neuen Zeile generiert werden. Sobald dies erledigt ist, sehen wir uns an, wie sich die Implementierung des Mappers für LectureListMapper geändert hat (der Teil, den wir benötigen, ist fett hervorgehoben):
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;
   }
}
Und jetzt hat Mapstruct die Mapper-Injection über den Konstruktor implementiert. Genau das wollten wir erreichen. Jetzt hört unser Test auf zu kompilieren, aktualisieren wir ihn und erhalten:
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());
   }
}
Wenn wir nun den Test ausführen, wird alles wie erwartet funktionieren, da wir in LectureListMapperImpl den benötigten LectureMapper übergeben ... Sieg! Es fällt euch nicht schwer, aber ich freue mich: Freunde, alles ist wie immer, abonniert meinen GitHub-Account , meinen Telegram-Account . Dort veröffentliche ich die Ergebnisse meiner Aktivitäten, es gibt wirklich nützliche Dinge) Ich lade Sie besonders ein, der Diskussionsgruppe des Telegram-Kanals beizutreten . Es kommt also vor, dass jemand, der eine technische Frage hat, dort eine Antwort bekommen kann. Dieses Format ist für jeden interessant, man kann lesen, wer was weiß, und Erfahrungen sammeln.

Abschluss

Im Rahmen dieses Artikels haben wir ein so notwendiges und häufig verwendetes Produkt wie Mapstruct kennengelernt. Wir haben herausgefunden, was es ist, warum und wie. Anhand eines realen Beispiels haben wir erspürt, was getan werden kann und wie es geändert werden kann. Wir haben uns auch angeschaut, wie man die Injektion von Beans über den Konstruktor einrichtet, damit Mapper ordnungsgemäß getestet werden können. Die Kollegen von Mapstruct haben den Benutzern ihres Produkts die Möglichkeit gegeben, genau zu wählen, wie sie Mapper injizieren, wofür wir ihnen zweifellos danken. ABER trotz der Tatsache, dass Spring die Injektion von Beans über den Konstruktor empfiehlt, haben die Jungs von Mapstruct die Injektion standardmäßig über das Feld festgelegt. Warum so? Keine Antwort. Ich vermute, dass es Gründe gibt, die wir einfach nicht kennen, und deshalb haben sie es so gemacht. Und um das herauszufinden, habe ich ein GitHub-Issue in ihrem offiziellen Produkt-Repository erstellt.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION