Een gids voor het in kaart brengen met Dozer

1. Overzicht

Dozer is een Java Bean naar Java Bean-mapper dat recursief gegevens van het ene object naar het andere kopieert, attribuut voor attribuut.

De bibliotheek ondersteunt niet alleen mapping tussen attribuutnamen van Java Beans, maar ook converteert automatisch tussen typen - als ze anders zijn.

De meeste conversiescenario's worden standaard ondersteund, maar met Dozer kunt u dat ook specificeer aangepaste conversies via XML.

2. Eenvoudig voorbeeld

Laten we voor ons eerste voorbeeld aannemen dat de bron- en doelgegevensobjecten allemaal dezelfde gemeenschappelijke kenmerknamen hebben.

Dit is de meest eenvoudige mapping die je met Dozer kunt doen:

openbare klasse Bron {naam van privé-tekenreeks; privé int leeftijd; openbare bron () {} openbare bron (tekenreeksnaam, int leeftijd) {this.name = naam; this.age = leeftijd; } // standaard getters en setters}

Dan ons bestemmingsbestand, Dest.java:

public class Dest {private String naam; privé int leeftijd; public Dest () {} public Dest (String naam, int leeftijd) {this.name = naam; this.age = leeftijd; } // standaard getters en setters}

We moeten ervoor zorgen neem de constructors van het standaard- of nulargument op, aangezien Dozer gebruik maakt van reflectie onder de motorkap.

En laten we voor prestatiedoeleinden onze mapper globaal maken en een enkel object maken dat we tijdens onze tests zullen gebruiken:

DozerBeanMapper mapper; @Before public void before () gooit Uitzondering {mapper = new DozerBeanMapper (); }

Laten we nu onze eerste test uitvoeren om te bevestigen dat wanneer we een Bron object, kunnen we het rechtstreeks toewijzen aan een Best voorwerp:

@Test openbare ongeldige gegevenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_ thenCorrect () {Bronbron = nieuwe bron ("Baeldung", 10); Dest dest = mapper.map (source, Dest.class); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

Zoals we kunnen zien, is het resultaat na de Dozer-mapping een nieuw exemplaar van het Best object dat waarden bevat voor alle velden met dezelfde veldnaam als de Bron voorwerp.

Als alternatief, in plaats van te passen mapper de Best klasse, hadden we gewoon de Best object en geslaagd mapper zijn referentie:

@Test openbare ongeldige gegevenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_ thenCorrect () {Bronbron = nieuwe bron ("Baeldung", 10); Dest dest = nieuwe Dest (); mapper.map (source, dest); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

3. Maven-instellingen

Nu we een basiskennis hebben van hoe Dozer werkt, gaan we de volgende afhankelijkheid toevoegen aan het pom.xml:

 net.sf.dozer dozer 5.5.1 

De laatste versie is hier beschikbaar.

4. Voorbeeld van dataconversie

Zoals we al weten, kan Dozer een bestaand object aan een ander toewijzen, zolang het attributen met dezelfde naam in beide klassen vindt.

Dat is echter niet altijd het geval; en dus, als een van de toegewezen attributen van verschillende gegevenstypen is, zal de Dozer-mapping-engine dat doen automatisch een datatype-conversie uitvoeren.

Laten we dit nieuwe concept in actie zien:

openbare klasse Source2 {privé String-id; privé dubbele punten; public Source2 () {} public Source2 (string-id, dubbele punten) {this.id = id; this.points = punten; } // standaard getters en setters}

En de bestemmingsklasse:

openbare klasse Dest2 {privé int id; privé int punten; openbare Dest2 () {} openbare Dest2 (int id, int punten) {super (); this.id = id; this.points = punten; } // standaard getters en setters}

Merk op dat de attribuutnamen hetzelfde zijn, maar hun datatypes zijn verschillend.

In de bronklasse ID kaart is een Draad en punten is een dubbele, terwijl in de bestemmingsklasse, ID kaart en punten zijn beide geheel getals.

Laten we nu eens kijken hoe Dozer de conversie correct afhandelt:

@Test openbare ongeldige gegevenSourceAndDestWithDifferentFieldTypes_ whenMapsAndAutoConverts_thenCorrect () {Source2 source = new Source2 ("320", 15.2); Dest2 dest = mapper.map (bron, Dest2.class); assertEquals (dest.getId (), 320); assertEquals (dest.getPoints (), 15); }

We zijn geslaagd “320” en 15.2, een Draad en een dubbele in het bronobject en het resultaat had 320 en 15, beide geheel getals in het bestemmingsobject.

5. Basistoewijzingen op maat via XML

In alle voorgaande voorbeelden die we hebben gezien, hebben zowel de bron- als de bestemmingsgegevensobjecten dezelfde veldnamen, wat een gemakkelijke toewijzing aan onze kant mogelijk maakt.

In echte toepassingen zullen er echter ontelbare keren zijn dat de twee gegevensobjecten die we in kaart brengen, geen velden hebben die een gemeenschappelijke eigenschapsnaam delen.

Om dit op te lossen, geeft Dozer ons een optie om een aangepaste toewijzingsconfiguratie in XML.

In dit XML-bestand kunnen we class mapping-items definiëren die de Dozer-mapping-engine zal gebruiken om te beslissen welk bronattribuut moet worden toegewezen aan welk bestemmingsattribuut.

Laten we eens kijken naar een voorbeeld, en laten we proberen data-objecten van een applicatie gebouwd door een Franse programmeur niet te markeren in een Engelse stijl voor het benoemen van onze objecten.

We hebben een Persoon object met naam, bijnaam en leeftijd velden:

openbare klasse Persoon {privé Stringnaam; private String-bijnaam; privé int leeftijd; public Person () {} public Person (String naam, String nickname, int leeftijd) {super (); this.name = naam; this.nickname = bijnaam; this.age = leeftijd; } // standaard getters en setters}

Het object dat we niet markeren, wordt genoemd Personne en heeft velden nom, som en leeftijd:

openbare klasse Personne {privé String nom; privé String surnom; privé int leeftijd; public Personne () {} public Personne (String nom, String surnom, int age) {super (); this.nom = nom; this.surnom = surnom; this.age = leeftijd; } // standaard getters en setters}

Deze objecten bereiken eigenlijk hetzelfde doel, maar we hebben een taalbarrière. Om met die barrière te helpen, kunnen we Dozer gebruiken om de Fransen in kaart te brengen Personne bezwaar maken tegen onze Persoon voorwerp.

We hoeven alleen een aangepast toewijzingsbestand te maken om Dozer hierbij te helpen, we zullen het noemen dozer_mapping.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom naam   som bijnaam

Dit is het eenvoudigste voorbeeld van een aangepast XML-toewijzingsbestand dat we kunnen hebben.

Voor nu is het genoeg om op te merken dat we dat hebben gedaan als ons wortelelement, dat een kind heeft kunnen we zoveel mogelijk van deze kinderen binnen hebben aangezien er gevallen zijn van klassenparen die aangepaste toewijzing nodig hebben.

Merk ook op hoe we de bron- en bestemmingsklassen binnen het tags. Dit wordt gevolgd door een voor elk paar bron- en bestemmingsvelden waarvoor een aangepaste toewijzing nodig is.

Merk ten slotte op dat we het veld niet hebben opgenomen leeftijd in ons aangepaste toewijzingsbestand. Het Franse woord voor leeftijd is nog steeds leeftijd, wat ons bij een ander belangrijk kenmerk van Dozer brengt.

Eigenschappen met dezelfde naam hoeven niet te worden opgegeven in het XML-toewijzingsbestand. Dozer wijst automatisch alle velden met dezelfde eigenschapsnaam van het bronobject toe aan het doelobject.

We plaatsen dan ons aangepaste XML-bestand op het klassenpad direct onder het src map. Echter, waar we het ook plaatsen op het klassenpad, Dozer zal het hele klassenpad doorzoeken op zoek naar het gespecificeerde bestand.

Laten we een hulpmethode maken om toewijzingsbestanden toe te voegen aan onze mapper:

openbare void configureMapper (String ... mappingFileUrls) {mapper.setMappingFiles (Arrays.asList (mappingFileUrls)); }

Laten we nu de code testen:

@Test openbare leegte gegevenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMaps_thenCorrect () {configureMapper ("dozer_mapping.xml"); Personne frenchAppPerson = nieuwe persoon ("Sylvester Stallone", "Rambo", 70); Persoon englishAppPerson = mapper.map (frenchAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), frenchAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), frenchAppPerson.getAge ()); }

Zoals aangetoond in de test, DozerBeanMapper accepteert een lijst met aangepaste XML-toewijzingsbestanden en beslist wanneer deze tijdens runtime moeten worden gebruikt.

Ervan uitgaande dat we deze gegevensobjecten nu ongemarkeerd gaan maken tussen onze Engelse app en de Franse app. We hoeven geen nieuwe mapping in het XML-bestand te maken, Dozer is slim genoeg om de objecten in beide richtingen in kaart te brengen met slechts één mappingconfiguratie:

@Test openbare leegte gegevenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMapsBidirectionally_thenCorrect () {configureMapper ("dozer_mapping.xml"); Persoon englishAppPerson = nieuwe persoon ("Dwayne Johnson", "The Rock", 44); Personne frenchAppPerson = mapper.map (englishAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), englishAppPerson.getAge ()); }

En dus gebruikt deze voorbeeldtest deze andere functie van Dozer - het feit dat de engine voor het in kaart brengen van de dozer is bidirectioneel, dus als we het doelobject aan het bronobject willen toewijzen, hoeven we geen andere klassetoewijzing aan het XML-bestand toe te voegen.

We kunnen ook een aangepast toewijzingsbestand van buiten het klassenpad laden, indien nodig, gebruik de "het dossier:”Voorvoegsel in de resourcenaam.

Op een Windows-omgeving (zoals de onderstaande test) gebruiken we natuurlijk de Windows-specifieke bestandssyntaxis.

Op een Linux-box kunnen we het bestand opslaan onder /huis en dan:

configureMapper ("bestand: /home/dozer_mapping.xml");

En op Mac OS:

configureMapper ("bestand: /Users/me/dozer_mapping.xml");

Als u de unit-tests uitvoert vanuit het github-project (wat u zou moeten doen), kunt u het toewijzingsbestand naar de juiste locatie kopiëren en de invoer wijzigen voor configureMapper methode.

Het toewijzingsbestand is beschikbaar in de map test / resources van het GitHub-project:

@Test openbare leegte gegevenMappingFileOutsideClasspath_whenMaps_thenCorrect () {configureMapper ("bestand: E: \ dozer_mapping.xml"); Persoon englishAppPerson = nieuwe persoon ("Marshall Bruce Mathers III", "Eminem", 43); Personne frenchAppPerson = mapper.map (englishAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), englishAppPerson.getAge ()); }

6. Jokertekens en verdere XML-aanpassing

Laten we een tweede aangepast toewijzingsbestand maken met de naam dozer_mapping2.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom naam   som bijnaam

Merk op dat we een attribuut hebben toegevoegd jokerteken naar de element dat er niet eerder was.

Standaard, jokerteken is waar. Het vertelt de Dozer-engine dat we willen dat alle velden in het bronobject worden toegewezen aan de juiste bestemmingsvelden.

Als we het instellen op vals, we vertellen Dozer om alleen velden toe te wijzen die we expliciet hebben gespecificeerd in de XML.

Dus in de bovenstaande configuratie willen we slechts twee velden in kaart brengen, weglaten leeftijd:

@Test openbare leegte gegevenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect () {configureMapper ("dozer_mapping2.xml"); Persoon englishAppPerson = nieuwe persoon ("Shawn Corey Carter", "Jay Z", 46); Personne frenchAppPerson = mapper.map (englishAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), 0); }

Zoals we kunnen zien in de laatste bewering, de bestemming leeftijd veld bleef 0.

7. Aangepaste toewijzing via annotaties

Voor eenvoudige toewijzingsgevallen en gevallen waarin we ook schrijftoegang hebben tot de data-objecten die we willen toewijzen, hoeven we mogelijk geen XML-toewijzing te gebruiken.

Het toewijzen van velden met verschillende namen via annotaties is heel eenvoudig en we hoeven veel minder code te schrijven dan bij XML-mapping, maar kan ons alleen in eenvoudige gevallen helpen.

Laten we onze data-objecten repliceren in Person2.java en Personne2.java zonder de velden helemaal te veranderen.

Om dit te implementeren hoeven we alleen @ toe te voegenmapper ("destinationFieldName") annotatie op de getter methoden in het bronobject. Zoals zo:

@Mapping ("naam") public String getNom () {return nom; } @Mapping ("nickname") public String getSurnom () {return surnom; }

Deze keer behandelen we Persoon2 als de bron, maar het maakt niet uit vanwege de bidirectionele aard van de Dozer Engine.

Nu alle XML-gerelateerde code is verwijderd, is onze testcode korter:

@Test openbare ongeldig gegevenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect () {Person2 englishAppPerson = new Person2 ("Jean-Claude Van Damme", "JCVD", 55); Personne2 frenchAppPerson = mapper.map (englishAppPerson, Personne2.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), englishAppPerson.getAge ()); }

We kunnen ook testen op bi-directionaliteit:

@Test openbare ongeldig gegevenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_ thenCorrect () {Personne2 frenchAppPerson = new Personne2 ("Jason Statham", "transporter", 49); Person2 englishAppPerson = mapper.map (frenchAppPerson, Person2.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), frenchAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), frenchAppPerson.getAge ()); }

8. Aangepaste API-toewijzing

In onze vorige voorbeelden waar we gegevensobjecten van een Franse applicatie niet-markeren, hebben we XML en annotaties gebruikt om onze mapping aan te passen.

Een ander alternatief dat beschikbaar is in Dozer, vergelijkbaar met annotatietoewijzing, is API-mapping. Ze lijken op elkaar omdat we XML-configuratie elimineren en strikt Java-code gebruiken.

In dit geval gebruiken we BeanMappingBuilder class, gedefinieerd in ons eenvoudigste geval als volgt:

BeanMappingBuilder builder = nieuwe BeanMappingBuilder () {@Override beschermde leegte configure () {mapping (Person.class, Personne.class) .fields ("name", "nom") .fields ("nickname", "surnom"); }};

Zoals we kunnen zien, hebben we een abstracte methode, configureren (), die we moeten overschrijven om onze configuraties te definiëren. Dan, net als bij ons tags in XML definiëren we er zoveel TypMappingBuilders zoals we nodig hebben.

Deze bouwers vertellen Dozer welke bron- naar bestemmingsvelden we in kaart brengen. We passeren dan de BeanMappingBuilder naar DozerBeanMapper zoals we zouden doen, het XML-toewijzingsbestand, alleen met een andere API:

@Test openbare ongeldig gegevenApiMapper_whenMaps_thenCorrect () {mapper.addMapping (builder); Personne frenchAppPerson = nieuwe persoon ("Sylvester Stallone", "Rambo", 70); Persoon englishAppPerson = mapper.map (frenchAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), frenchAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), frenchAppPerson.getAge ()); }

De mapping-API is ook bidirectioneel:

@Test openbare ongeldig gegevenApiMapper_whenMapsBidirectionally_thenCorrect () {mapper.addMapping (builder); Persoon englishAppPerson = nieuwe persoon ("Sylvester Stallone", "Rambo", 70); Personne frenchAppPerson = mapper.map (englishAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), englishAppPerson.getAge ()); }

Of we kunnen ervoor kiezen om alleen expliciet gespecificeerde velden toe te wijzen met deze builder-configuratie:

BeanMappingBuilder builderMinusAge = nieuwe BeanMappingBuilder () {@Override protected void configure () {mapping (Person.class, Personne.class) .fields ("name", "nom") .fields ("nickname", "surnom") .exclude ("leeftijd"); }};

en onze leeftijd == 0 test is terug:

@Test openbare leegte gegevenApiMapper_whenMapsOnlySpecifiedFields_thenCorrect () {mapper.addMapping (builderMinusAge); Persoon englishAppPerson = nieuwe persoon ("Sylvester Stallone", "Rambo", 70); Personne frenchAppPerson = mapper.map (englishAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), 0); }

9. Aangepaste converters

Een ander scenario dat we kunnen tegenkomen bij het in kaart brengen, is waar we zouden willen voer aangepaste toewijzing uit tussen twee objecten.

We hebben gekeken naar scenario's waarin de namen van bron- en bestemmingsvelden verschillen, zoals in het Frans Personne voorwerp. Deze sectie lost een ander probleem op.

Wat als een data-object dat we niet markeren een datum- en tijdveld vertegenwoordigt, zoals een lang of Unix-tijd zoals zo:

1182882159000

Maar ons eigen equivalente gegevensobject vertegenwoordigt hetzelfde datum- en tijdveld en dezelfde waarde in dit ISO-formaat, zoals een Draad:

2007-06-26T21: 22: 39Z

De standaardconvertor zou de lange waarde eenvoudig toewijzen aan een Draad zo:

"1182882159000"

Dit zou onze app zeker irriteren. Dus hoe lossen we dit op? We lossen het op door het toevoegen van een configuratieblok in het mapping XML-bestand en specificeren van onze eigen converter.

Laten we eerst het Persoon DTO met een naam, dan geboortedatum en -tijd, dtob veld:

openbare klasse Personne3 {private String-naam; privé lange dtob; public Personne3 (String naam, lange dtob) {super (); this.name = naam; this.dtob = dtob; } // standaard getters en setters}

en hier is onze eigen:

openbare klasse Person3 {private String-naam; privé String dtob; public Person3 (String naam, String dtob) {super (); this.name = naam; this.dtob = dtob; } // standaard getters en setters}

Let op het typeverschil van dtob in de bron- en bestemmings-DTO's.

Laten we ook onze eigen maken CustomConverter om door te geven aan Dozer in de mapping XML:

public class MyCustomConvertor implementeert CustomConverter {@Override public Object convert (Object dest, Object source, Class arg2, Class arg3) {if (source == null) return null; if (source instanceof Personne3) {Personne3 person = (Personne3) source; Datum datum = nieuwe datum (person.getDtob ()); DateFormat-indeling = nieuwe SimpleDateFormat ("jjjj-MM-dd'T'HH: mm: ss'Z '"); String isoDate = format.format (datum); retourneer nieuwe Person3 (person.getName (), isoDate); } else if (source instanceof Person3) {Person3 person = (Person3) source; DateFormat format = nieuwe SimpleDateFormat ("jjjj-MM-dd'T'HH: mm: ss'Z '"); Datum datum = format.parse (person.getDtob ()); lange tijdstempel = date.getTime (); retourneer nieuwe Personne3 (person.getName (), tijdstempel); }}}

We hoeven alleen maar te overschrijven converteren() methode en retourneer dan wat we ernaar terug willen keren. We maken gebruik van de bron- en bestemmingsobjecten en hun klassetypes.

Merk op hoe we voor bidirectionaliteit hebben gezorgd door aan te nemen dat de bron een van de twee klassen kan zijn die we in kaart brengen.

We zullen voor de duidelijkheid een nieuw toewijzingsbestand maken, dozer_custom_convertor.xml:

     com.baeldung.dozer.Personne3 com.baeldung.dozer.Person3 

Dit is het normale toewijzingsbestand dat we in voorgaande secties hebben gezien, we hebben alleen een blok waarin we zoveel aangepaste converters kunnen definiëren als we nodig hebben met hun respectievelijke bron- en bestemmingsgegevensklassen.

Laten we onze nieuwe testen CustomConverter code:

@Test openbare leegte gegevenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_ thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; long timestamp = new Long ("1182882159000"); Person3 person = new Person3 ("Rich", dateTime); Personne3 person0 = mapper.map (persoon, Personne3.class); assertEquals (timestamp, person0.getDtob ()); }

We kunnen ook testen om er zeker van te zijn dat het bidirectioneel is:

@Test openbare leegte gegevenSrcAndDestWithDifferentFieldTypes_ whenAbleToCustomConvertBidirectionally_thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; long timestamp = new Long ("1182882159000"); Personne3 person = new Personne3 ("Rich", tijdstempel); Person3 person0 = mapper.map (persoon, Person3.class); assertEquals (dateTime, person0.getDtob ()); }

10. Conclusie

In deze tutorial hebben we introduceerde de meeste basisprincipes van de Dozer Mapping-bibliotheek en hoe u het in onze applicaties kunt gebruiken.

De volledige implementatie van al deze voorbeelden en codefragmenten is te vinden in het Dozer github-project.