Java Expressions durch MapStruct Qualifier ersetzen

„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.

Leave a Comment