Donnerstag, 30. April 2015

Bean-Mapping mit MapStruct

Am vergangenen Wochenende habe ich auf dem MATHEMA Campus einen Vortrag über MapStruct gehalten. MapStruct ist ein Annotations-basierter Code-Generator für Mappings zwischen beliebigen Java-Beans und kann damit hervorragend zum Umkopieren der Daten zwischen den diversen Domänen-Modellen einer Anwendung (JPA, JAXB, REST, sonstige DTOs/POJOs ... ) genutzt werden.

Der generierte Mapping-Code nutzt keine Reflection, sondern Getter- und Setter-Aufrufe – und erreicht damit die Geschwindigkeit von handgeschriebenem Mapping-Code. Ganz nebenbei ist der generierte Code lesbar und verständlich. Das ist wichtig, falls der Projektverantwortliche Angst hat, ein weiteres Werkzeug ins Projekt einzubinden – zur Not stellt man den generierten Code unter Versionskontrolle und pflegt ihn später von Hand weiter (wer einmal MapStruct verwendet hat, wird das aber nicht wollen ;-).

MapStruct wird als Annotations-Prozessor eingebunden, d.h. der Mapping-Code wird beim Aufruf von javac (bzw. bei jedem Speichern z.B. in Eclipse) generiert. Das Prozessor-JAR wird dabei nur zur Compile-Zeit benötigt. Zur Laufzeit ist bei einem Komponentenmodell wie CDI gar kein JAR als Abhängigkeit nötig, ohne Komponentenmodell nur ein winziges JAR < 20 K.

Folgender Code reicht, um den Mapping-Code zwischen einer Kunde-Entity und einem Kunde-DTO generieren zu lassen:

@Mapper(componentModel = "cdi")
public interface KundeMapper {

  @Mapping(target = "geburtsdatum", format = "dd.MM.yyyy")
  KundeDTO jpaEntity2RestDto(Kunde kunde);
}

MapStruct erzeugt daraus in etwa folgenden Code:

@Generated("org.mapstruct.ap.MappingProcessor")
@ApplicationScoped
public class KundeMapperImpl implements KundeMapper {

  public KundeDTO jpaEntity2RestDto(Kunde kunde) {
    // null-Checks
    // Ziel-Objekt-Erzeugung
    // Getter-/Setter-Aufrufe
    // ...
  }
}

Der standardmäßig generierte Code ist oft schon genau das, was man benötigt. Ansonsten ist so ziemlich alles konfigurierbar: Unterschiedliche Property-Namen in Quell- und Ziel-Bean, unterschiedliche Typen, die Objekt-Fabriken zur Ziel-Objekt-Erzeugung... Zudem können vorhandene, handgeschriebene Mapper eingebunden werden. Und Objekt-Graphen werden selbstverständlich genauso gemappt wie Collections, Maps, Arrays... Für JAXB, Joda-Time und Java 8 Date&Time stehen automatische Mappings zur Verfügung.

Man merkt, dass dieser Mapper mit vielen Praxis-Problemen im Hinterkopf entwickelt wurde. Und man fragt sich relativ schnell, warum man nur so lange das Bean-Mapping von Hand oder mit Reflection-Bibliotheken durchgeführt hat ;-)

Mindestvoraussetzung für MapStruct ist Java 6 – aber da heute der letzte Tag der kostenfreien Java-7-Updates (und ab morgen nur noch Java 8 "aktuell") ist, sollte das kein Problem darstellen.

Die Folien zu meinem Vortrag finden sich hier.