Wer ist der optimale Java Bean Mapper?

Ich hatte ein Problem. Ich hatte einen Webservice zu implementieren.

Dabei musste ich die interne Objekthierarchie einer Anwendung auf ca. 23 Servicemethoden mappen. Zusätzlich musste der Webservice aus einem komplexen Objektgeflecht bestückt werden.

Hätte ich dieses Mapping von Hand programmiert, wäre ich Wochen damit beschäftigt gewesen. Eine Lösung dieses Problems sind die Java Bean Mapper.

In diesem Beitrag zeige ich, welche Java Bean Mapper es gibt und welche Probleme damit gelöst  bzw. aufgetreten sind.

Zusätzlich habe ich die Performanz der Java Bean Mappers miteinander verglichen.

Die Aufgabe

… ist ein typisches Problem aus der Praxis. Das Mappen von strukturierten Daten an Schnittstellen bzw. in Endpoints.

Dort werden die Daten der Schnittstelle auf die interne Objekthierarchie der Anwendung gemappt.

Dabei können bei einer umfangreichen Schnittstelle mit einem komplexen Sachverhalt recht aufwändige Objektgeflechte entstehen. Erschwerend kommt noch die Verschiedenartigkeit der aneinandergrenzenden Anwendungsschichten (Persistenz-, Domain-, Service-, REST- oder Web-API) hinzu.

Werden diese Objektgeflechte von Hand gemappt, sind Fehler vorprogrammiert. Daher wird immer die Frage nach einem automatischen Mapping gestellt.

Zur Lösung des Problems fiel meine Wahl auf den Klassiker – Dozer.

Leider stellte sich recht schnell heraus, dass die Performanz dieses Mappers nicht ausreichend für die Lösung des Problems war.

Probleme über Probleme

Bei der Suche nach alternativen Java Bean Mapper traten weitere Hindernisse auf.

  • Einige Mapper kamen mit den generierten JAXB-Klassen nicht zurecht, da die JAXB-Klassen nicht nach dem Java Bean Standard generiert werden. Die Collections haben keinen Setter Methoden und die Getter Methoden liefern immer eine Collection zurück.

    Auch die erweiterte Generierung der JAXB-Klassen mit dem XJC-Plugin jaxb2-basics half nicht weiter.

    Dieses Plugin generiert für die Collections Setter Methoden. Aber bei einem nicht gesetzten Property wird die Setter Methode mit null aufgerufen. Daran stört sich aber die Methode addAll().

  • Einige Mapper können nicht rekursiv durch die Objekthierarchie iterieren.
    Somit wurden die in Collections enthaltenen Objekte nicht gemappt – sondern nur kopiert. Dadurch landeten nicht die erwarteten Klassen in den gemappten Objekten.
    Ein Beispiel ist das Mappen einer DomainTable auf eine XmlTable. Beide Klassen haben ein Property rows. Im Falle der DomainTable ist das Property vom Typ List<DomainRow> rows; und der XmlTable vom Typ List<XmlRow> rows;. Nach dem Mappen von DomainTable auf die XmlTable befindet sich im Property Objekte vom Typ DomainRow.

Die Testimplementation

Wie hab ich den Test aufgebaut?

Der Test besteht aus zwei Testläufen, die die obengenannten Probleme berücksichtigt.

  1. Die Objekthierarchie wird vollständig mit Daten gefüllt.
    Leider können nicht alle Java Bean Mapper bei diesem Test eingeschossen werden. Die Mapper, die die obengenannten Probleme haben, wurden hier nicht berücksichtigt.
  2. Die Objekthierarchie wird teilweise mit Daten gefüllt.
    Bei diesem einfachen Test konnten alle Java Bean Mapper berücksichtigt werden. Allerdings ist die Aussagefähigkeit dieses Tests in der Praxis fraglich, da wesentliche Funktionen fehlen.

Die Links zum Quellcode finden Sie weiter unten.

Die Objekthierarchie

In diesem Beispiel werden zwei Objekthierarchien (JAXB– und JPA-Klassen) aufeinander gemappt.

Die Eingangsdatenstruktur wurde aus der WSDL (http://xmlns.frank-rahn.de/ws/test/1.0) per JAXB generiert.

Die Ausgangsdatenstruktur besteht aus JPA Entities.

In den folgenden UML-Diagrammen sind die Objekthierarchien der JAXB– und JPA-Klassen dargestellt.

Der JUnit Test

In der Klasse AbstractPerformanceTest wird in der Methode runTestWithMapper() für jeden Java Bean Mapper das Mapping durchgeführt. Für jeden Durchlauf werden die Daten erfasst. Vor jeder Messreihe werden alle Mapper 10-mal aufgerufen, um den Test bzw. die Implementationen einzuschwingen. Der eigentliche Test läuft dann 10 Minuten.

Es wird die durchschnittliche Dauer in Abhängigkeit von der Anzahl der Durchläufe ermittelt.

Zusätzlich werden folgende Kenndaten über die Dauer ermittelt.

  • Minimaler Wert
  • Maximaler Wert
  • Letzter gemessener Wert
  • Arithmetischer Mittelwert
  • Standardabweichung

Die Ergebnisse werden in einem Excel-Sheet dargestellt. Die Dauer wird in Millisekunden angegeben.

Im folgenden Klassendiagramm ist die Objekthierarchie des JUnit-Tests dargestellt.

Das Klassendiagramm für den Java Bean Mapper Test am Beispiel 'ByHand' (© Frank Rahn)

Die getesteten Java Bean Mapper mit Bewertung und Hinweise zur Implementierung

Bei der Implementierung habe ich darauf geachtet, dass keine Technik zum Einsatz kommt, die eine direkte Änderung des Quellcodes der JAXB- bzw. JPA-Klassen erzwungen hätte. Z. B. durch den Einsatz einer speziellen Annotation eines Mapper Frameworks.

Die folgenden Java Bean Mapper wurden von mir untersucht.

ByHand

Der Java Bean Mapper ByHand ist kein automatisch generierter Mapper.

Dieser Java Bean Mapper ist eine Implementierung von Hand. Hierbei wird jedes einzelne Property von einem Java Bean in ein anderes Java Bean, ggf. mit Typkonvertierung, übertragen.

Das Erstellen dieses Mappers ist sehr zeitintensiv und fehleranfällig. Eigentlich eine Aufgabe für einen dressierten Affen. Allerdings können alle fachlichen Aspekte individuell berücksichtigt werden.

Diese Art von Mappern liefert die beste Performanz.

Apache Commons BeanUtils

Dieser Java Bean Mapper ist Teil der Bibliothek Apache Commons BeanUtils der Apache Software Foundation. Dieses Projekt stellt nützliche Tools rund um den Standard JavaBeans Component API zu Verfügung.

Der Mapper besteht aus der Klasse org.apache.commons.beanutils.BeanUtils. Dieser Mapper kann das zumappende Objekt nicht rekursive bearbeiten und ist auf die Setter Methode angewiesen – ohne diese kann er die Werte nicht mappen.

Daher kann er nur für einfache und nicht tiefgreifende Mappings verwendet werden.

In diesen Fällen allerdings liefert er eine gute Performanz und ist sehr einfach eingesetzt.

Spring Framework BeanUtils

Dieser Java Bean Mapper ist Teil vom Spring Framework und wird intern vom Spring Framework selber verwendet.

Der Mapper besteht aus der Klasse org.springframework.beans.BeanUtils. Diese Klasse nutzt den Standard JavaBeans Component API.

Dieser Mapper arbeitet ähnlich wie die Apache Common BeanUtils, hat die gleichen Probleme und liefert die gleiche Performanz.

Dozer

Der Java Bean Mapper Dozer ist ein komplettes Mapping Framework.

Dozer verwendet Reflection zum Mappen der Daten und kann die Objekte rekursiv durcharbeiten.

Dozer kann über eine XML-Datei umfangreich konfiguriert werden. Die Möglichkeiten der Konfiguration dieses Mappers lassen keine Wünsche offen.

Wie beim Mapper ByHand können alle individuellen fachlichen Aspekte berücksichtigt werden. Die Konfiguration geht schnell vonstatten.

Leider hat dieser Mapper aber auch die schlechteste Performanz.

Orika

Der Java Bean Mapper Orika ist ähnlich wie Dozer ein Mapping Framework.

Im Grunde stellt Orika einen Replacment von Dozer mit gleichem Funktionsumfang dar.

Allerdings verwendet Orika die Bytecode Generierung von Javassist zum Mappen. Dadurch hat Orika, bei gleichem Funktionsumfang, eine deutlich bessere Performanz als Dozer.

MapStruct

Der Java Bean Mapper MapStruct ist auch ein Mapping Framework.

Allerdings geht dieser Mapper einen kompletten neuen Weg.

Er verwendet einen Annotation Processor (APT) zum Erzeugen von Mappern für die einzelnen Java Beans. Diese generierten Mapper sind richtige Java-Klassen – können also im Fehlerfall auch debuggt werden.

Als Ausgangsbasis dient dem Prozessor ein Interface oder eine abstrakte Basisklasse.

Falls sich für eine abstrakte Basisklasse entscheiden wird, kann diese Klasse eine individuelle Implementierungen liefern. Damit erreicht dieses Mapping den vollständigen Funktionsumfang wie der Mapper ByHand. Allerdings auch zu einem höheren Implementierungsaufwand im Gegensatz zu Dozer oder Orika.

Diese Interfaces müssen geschrieben werden. Bei einer umfangreichen Objekthierarchie benötigt das Schreiben der Interfaces seine Zeit.

Die dadurch gewonnene Performanz hebt diesen Nachteil allerdings vollständig auf. Dieser Mapper wurde nur geringfügig (im Bereich von Millisekunden) durch den Mapper ByHand geschlagen. Der wesentlich mühseliger zu programmieren ist.

ModelMapper

Die Berücksichtigung des Java Bean Mapper ModelMapper wurde in den Kommentaren gewünscht.

Dieser Mapper ähnelt den schon vorher beschriebenen Mappern Orika und Dozer und sortiert sich auch entsprechend mit der Performanz ein. Allerdings befindet sich dieser Mapper noch in der Entwicklung.

Dieser Mapper bietet eine API für spezifische Anwendungsfälle an.

JMapper Framework

Die Berücksichtigung des Java Bean Mapper JMapper Framework wurde in den Kommentaren gewünscht.

Dieser Mapper ähnelt einwenig dem Mapper MapStruct. Auch bei diesem Mapper muss zwingend eine Konfiguration erstellt werden. Beim JMapper kann zwischen mehreren Konfigurationsvarianten gewählt werden.

  • Der Verwendung von Annotationen
  • Eine XML Konfiguration
  • Der JMapper API

Ich habe mich in diesem Beispiel für die Variante API entschieden.

JMapper verwendet Bytecode Generierung von Javassist zum Mappen der Daten.

Die Performanz dieses Mappers ist beeindruckend. Er kann locker mit dem Mapper ByHand mithalten und ist einfacher umzusetzen als der Mapper MapStruct.

Weitere Features dieses Mappers sind …

  • Relationales Mapping von mehreren Klassen auf eine Klasse (Many to One) und umgekehrt (One to Many)
  • Vererbung von Konfigurationen

Das Fazit

In der folgenden Tabelle sind die Durchschnittswerte für ein Mapping mit vollständig gefüllter Objekthierarchie in Millisekunden angegeben.

Update des Beitrags mit JMapper Framework vom 27.05.2016 (v1.2.3, jdk8-03)

Arithmetischer MittelwertStandardabweichung
ByHand (v1.0)0,0160,133
Neu: JMapper Framework (v1.6.0.1)0,0150,123
MapStruct (v1.0.0.Final)0,1730,388
Orika (v1.4.6)0,5310,557
ModelMapper (v0.7.5)5,6240,964
Dozer (v5.5.1)11,1381,325

Unter den folgenden Rahmenbedingungen wurde dieser Test durchgeführt.

  • Java Version 8 (64 Bit, ORACLE)
  • Linux Ubuntu 64 Bit 14.04.04 LTS
  • Desktop-Rechner
  • Speicher 12 GB
  • Prozessor Intel Core i7 CPU 975 @ 3,33 GHz * 8

Update des Beitrags mit ModelMapper vom 28.03.2016 (v1.1.0, jdk8-02)

Arithmetischer MittelwertStandardabweichung
ByHand (v1.0)0,0230,163
MapStruct (v1.0.0.Final)0,1610,368
Orika (v1.4.6)0,5340,526
Neu: ModelMapper (v0.7.5)5,6240,956
Dozer (v5.5.1)11,2201,137

Unter den folgenden Rahmenbedingungen wurde dieser Test durchgeführt.

  • Java Version 8 (64 Bit, ORACLE)
  • Linux Ubuntu 64 Bit 14.04.04 LTS
  • Desktop-Rechner
  • Speicher 12 GB
  • Prozessor Intel Core i7 CPU 975 @ 3,33 GHz * 8

Erstellung des Beitrags am 19.07.2015 (v1.0.0, jdk8)

Arithmetischer MittelwertStandardabweichung
ByHand (v1.0)0,0160,144
MapStruct (v1.0.0.Beta4)0,1520,372
Orika (v1.4.5)0,5140,534
Dozer (v5.5.1)10,3711,152

Unter den folgenden Rahmenbedingungen wurde dieser Test durchgeführt.

  • Java Version 8 (64 Bit, ORACLE)
  • Linux Ubuntu 64 Bit 14.04.01 LTS
  • Desktop-Rechner
  • Speicher 12 GB
  • Prozessor Intel Core i7 CPU 975 @ 3,33 GHz * 8

Test am 02.08.2014 (Commit 739903, jdk7)

Arithmetischer MittelwertStandardabweichung
ByHand (v1.0)0,0190,147
MapStruct (v1.0.0.Beta2)0,2850,458
Orika (v1.4.5)0,5290,547
Dozer (v5.5.1)10,2921,146

Unter den folgenden Rahmenbedingungen wurde dieser Test durchgeführt.

  • Java Version 7 (64 Bit, OpenJDK)
  • Linux Ubuntu 64 Bit 14.04 LTS
  • Desktop-Rechner
  • Speicher 12 GB
  • Prozessor Intel Core i7 CPU 975 @ 3,33 GHz * 8

Test am 04.07.2014 (Commit FCC6F9, jdk6)

Mittelwert
ByHand (v1.0)0,071
MapStruct (v1.0.0.Beta2)0,853
Orika (v1.4.5)2,328
Dozer (v5.5.1)46,259

Unter den folgenden Rahmenbedingungen wurde dieser Test durchgeführt.

  • Java Version 6 (32 Bit, Oracle)
  • Windows 7 Enterprise 64 Bit ServicePack 1
  • VDI (virtueller Desktop)
  • Speicher 4 GB
  • Prozessor Intel Xeon CPU ES-2670 @ 2.60 GHz 2.59 GHz

Alle Tests, mit den unterschiedlichen Rahmenbedingungen, sind im GitHub-Verzeichnis zu finden.

Excel Arbeitsmappe: Das zweite Tabellenblatt enthält die Messwerte. Auf dem ersten Tabellenblatt sind die Messwerte des Einschwingvorgangs.

Die Umfrage

Umfrage vom 19.07.2015 bis zum 19.10.2015

Wie schon geschrieben, verwende ich vor allem MapStruct. Wie ist das bei Euch?

Wer ist Ihr bevorzugter Java Bean Mapper?

  • Dozer (33%, 3 Stimmen)
  • Orika (33%, 3 Stimmen)
  • MapStruct (33%, 3 Stimmen)
  • Spring Framework BeanUtils (0%, 0 Stimmen)
  • Apache Commons BeanUtils (0%, 0 Stimmen)
  • ByHand (0%, 0 Stimmen)

Teilnehmerzahl: 9 (1 Stimmen)

Loading ... Loading ...

Die Umfrage wurde am 19.10.2015 beendet.

Der Quellcode und Download

Quellcode ansehen bei GitHub:
Java Bean Mapper

Download einer ZIP-Datei von GitHub:
Java Bean Mapper

Die Maven Befehle

Eclipse Konfiguration neu erzeugen: $ mvn eclipse:clean eclipse:eclipse

Die Anwendungen bauen: $ mvn clean install

Frank Rahn

Frank Rahn ist Softwarearchitekt. Er unterstützt bei der Konzeption von Softwarearchitekturen mit Java-Technologie. Folge Sie ihm auf Facebook, Twitter oder Google+.

Benötigen Sie Unterstützung? Kontaktieren Sie ihn.

Hat Ihnen dieser Beitrag gefallen? Wir würden uns über Ihren Kommentar freuen! Bitte verwenden Sie Ihren bürgerlichen Namen und eine E-Mail-Adresse mit Gravatar.

Letzte Artikel von Frank Rahn (Alle anzeigen)

12 Kommentare
  1. Urs Stuber
    Urs Stuber says:

    Vielen Dank für Ihre Zusammenstellung!

    Welche Mapper würden Sie aktuell einer Firma guten Gewissens empfehlen im Hinblick auf die weitere Pflege? Mich irritiert beim Blick ins gitHub, dass sowohl bei Orika wie bei Dozer die meiste Entwicklung im Jahr 2013 stattfand und in letzter Zeit kaum noch Weiterentwicklungen / Bugfixes zu finden sind und keine neue Releases mehr herauskamen. Denken Sie, dass diese Mapper weiterhin gewartet werden oder ist zu befürchten, dass wenn man jetzt grösseren Entwicklungsaufwand in die Erstellung von mappings mit diesen Tools steckt, man in einer Sackgasse landen könnte („never ride a dead horse“)?

    Antworten
    • Frank Rahn
      Frank Rahn says:

      Ich würde aktuell entweder das JMapper Framework oder MapStruct verwenden.

      Bei MapStruct wird es demnächst die Version 1.1.0 geben – diese ist im Moment im Beta-Test.

      Der ModelMapper wird zur Zeit mäßig entwickelt. Bei den anderen Mappern sieht es düster aus. Wobei bei Dozer aber auch ein sehr hoher Reifegrad erreicht wurde. Der Dozer ist aber auch der häufigste genutzte und älteste Mapper.

      Antworten
      • Alessandro Vurro
        Alessandro Vurro says:

        thank you for adding JMapper between the java bean mappers.
        However, you can switch destination and source positions without rewrite the configuration, obviously if the relationship is the same:

        If you want to avoid creating multiple instances of JMapper you should use RelationalJMapper.

        Antworten
          • Ralf Treuherz
            Ralf Treuherz says:

            1) Die Aussage „Dieser Mapper ähnelt den schon vorher beschriebenen Mappern Orika und Dozer und sortiert sich auch entsprechend mit der Performanz ein.“ bezüglich ModelMapper scheint den Messergebnissen nicht zu entsprechen ( 0,531 ms für Orika vs. 5,624 ms für ModelMapper)
            2) Eigene Tests mit komplexen Objektstrukturen unterstützen die Performanz von ModelMapper, hier habe ich um die 0.5 ms gemessen.
            3) Eine Messung mit Ihrer Testsuite habe ich selber nicht vorgenommen, Wenn diese tatsächlich einen Performance-Unterschied den Faktor 10 von Orika gegenüber ModelMapper zeigt, ist die Aussage in 1) zumindest irreführend.

Dein Kommentar

Want to join the discussion?
Feel free to contribute!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.