„Das Objekt ist eine unvollkommene Kopie dessen, was wirklich da ist.“
Platon
MapStruct ist eine beliebte Bibliothek um Daten zwischen unterschiedlichen Datentypen zu konvertieren. Für POJO Konvertierungen wird im einfachsten Fall jedes Attribut auf ein gleichnamiges Attribut in der Ziel-Instanz gemapped. In Sonderfällen muss auf die Annotation @Mapping
zurückgegriffen werden um eine spezielle Konvertierung zu erzwingen. Häufig werden die nachteiligen Java Expressions genutzt.
@Mapper public interface AncestorMapper { @Mapping(target = "id", ignore = true) @Mapping(target = "lastname", source = "surname") @Mapping(target = "external", constant = "true") @Mapping(target = "title", expression = "java("UNKNOWN".equals(input.getTitle()) ? null : input.getTitle())") @Mapping(target = "firstname", expression = "java(convertUnknownToNull(input.getGivenName())") Ancestor map(AncestorRecord input); default String convertUnknownToNull(String value) { return "UNKNOWN".equals(value) ? null : value; }
In dem hier dargestellten MapStruct Mapper werden AncestorRecord
Instanzen in Ancestor
Entitäten konvertiert. Dabei werden die häufigsten Anwendungsfälle der @Mapping
Annotation vorgestellt.
Die erste @Mapping
Annotation verhindert das Schreiben des id
Attributes in die Entity. Die zweite Annotation hilft, wenn die Attribute in den beiden Typen unterschiedliche Namen besitzen und die dritte Annotation schreibt einen konstanten Wert in das external
Attribut der Entity. Die letzten beiden Annotationen nutzen eine Java Expression um die Attribute title und firstname
zu konvertieren. Im erdachten Anwendungsfall kann das Attribute den Wert "UNKNOWN"
enthalten. In diesem Fall soll das Ergebnis der Konvertierung null
sein.
Eine Java Expression ist ein String
Attribute in der @Mapping
Annotation. Der eigentliche Java Code beginnt nach dem Präfix "java("
und endet vor der letzten schließenden Klammer. Bei der Kompilieren baut der MapStruct Annotation Processor diesen Code in die generierte Mapper Implementierung ein.
Der Nachteil dieser Konstruktion ist offensichtlich. Erst die Kompilierung prüft die Korrektheit des Java Codes im String
Attribut. Werden Attribute geändert, die in einer Java Expression genutzt werden, dann kann kein Refactoring Tool an dieser Stelle unterstützen. Alle Java Expressions müssen händisch nachgebessert werden.
Eine Alternative zur Java Expression ist der Einsatz eines Qualifiers. Ein Qualifier ist eine Annotation, die mit @Qualifier annotiert ist.
@Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface UnknownToNull {}
In diesem Fall ist es die Annotation @UnknownToNull
mit der Mapping Methoden annotiert werden können. Der AncestorMapper
ändert sich durch den Einsatz des Qualifiers nur minimal.
@Mapper public interface AncestorMapper { @Mapping(target = "id", ignore = true) @Mapping(target = "lastname", source = "surname") @Mapping(target = "external", constant = "true") @Mapping(target = "title", qualifiedBy = "UnknownToNull.class") @Mapping(target = "firstname", source = "givenName", qualifiedBy = UnknownToNull.class) Ancestor map(AncestorRecord input); @UnknownToNull default String convertUnknownToNull(String value) { return "UNKNOWN".equals(value) ? null : value; }
Die schon vorhanden Methode convertUnknownToNull
wird nun mit @UnknownToNull
annotiert. Außerdem sind nun die beiden @Mapping
Annotationen für title
und firstname
um das Attribut qualifiedBy
ergänzt. Dies sorgt für die Verknüpfung dieser Konvertierungen mit der convertUnknownToNull
Methode. Die beiden @Mapping
Annotationen reihen sich nun viel besser bei den anderen Annotationen ein. Die unübersichtliche Java Expression muss nun nicht mehr konsultiert werden, um herauszufinden, welches Quell-Attribute konvertiert wurde. Beim firstname
Mapping ist es nun genauso einfach abzulesen, wie beim lastname
Mapping.