Kortlægning med Orika

1. Oversigt

Orika er en Java Bean kortlægningsramme, der kopierer data rekursivt fra et objekt til et andet. Det kan være meget nyttigt, når du udvikler applikationer i flere lag.

Mens vi flytter dataobjekter frem og tilbage mellem disse lag, er det almindeligt at finde ud af, at vi har brug for at konvertere objekter fra en instans til en anden for at imødekomme forskellige API'er.

Nogle måder at opnå dette på er: hårdkodning af kopieringslogikken eller til implementering af bønnekartnere som Dozer. Det kan dog bruges til at forenkle processen med kortlægning mellem et objektlag og et andet.

Orika bruger byte-kodegenerering til at oprette hurtige kortlæggere med minimal overhead, hvilket gør det meget hurtigere end andre refleksionsbaserede kortlæggere som Dozer.

2. Enkelt eksempel

Den grundlæggende hjørnesten i kortlægningsrammen er MapperFabrik klasse. Dette er den klasse, vi vil bruge til at konfigurere tilknytninger og opnå den MapperFacade instans, der udfører selve kortlægningsarbejdet.

Vi opretter en MapperFabrik objekt som sådan:

MapperFactory mapperFactory = ny DefaultMapperFactory.Builder (). Build ();

Så forudsat at vi har et kildedataobjekt, Kilde.javamed to felter:

offentlig klasse Kilde {privat strengnavn; privat int alder public Source (String name, int age) {this.name = name; this.age = alder; } // standard getters og setters}

Og et lignende destinationsdataobjekt, Dest.java:

offentlig klasse Dest {private String name; privat int alder public Dest (String name, int age) {this.name = name; this.age = alder; } // standard getters og setters}

Dette er den mest basale kortlægning af bønner ved hjælp af Orika:

@Test offentlig ugyldighed givenSrcAndDest_whenMaps_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class); MapperFacade mapper = mapperFactory.getMapperFacade (); Kilde src = ny kilde ("Baeldung", 10); Dest dest = mapper.map (src, Dest.class); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

Som vi kan se, har vi skabt en Dest objekt med identiske felter som Kilde, simpelthen ved kortlægning. Tovejs eller omvendt kortlægning er også mulig som standard:

@ Test offentlig ugyldighed givenSrcAndDest_whenMapsReverse_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .byDefault (); MapperFacade mapper = mapperFactory.getMapperFacade (); Dest src = ny Dest ("Baeldung", 10); Kilde dest = mapper.map (src, Source.class); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

3. Maven-opsætning

For at bruge Orika mapper i vores maven-projekter skal vi have orika-kerne afhængighed i pom.xml:

 ma.glasnost.orika orika-core 1.4.6 

Den nyeste version kan altid findes her.

3. Arbejde med MapperFabrik

Det generelle kortlægningsmønster med Orika indebærer oprettelse af en MapperFabrik objekt, konfigurere det i tilfælde af at vi har brug for at tilpasse standard kortlægningsadfærd og opnå en MapperFacade objekt fra det og endelig faktisk kortlægning.

Vi skal følge dette mønster i alle vores eksempler. Men vores allerførste eksempel viste kortlægningens standardadfærd uden nogen tweak fra vores side.

3.1. Det BoundMapperFacade vs. MapperFacade

En ting at bemærke er, at vi kunne vælge at bruge BoundMapperFacade over standardværdien MapperFacade hvilket er ret langsomt. Dette er tilfælde, hvor vi har et bestemt par typer at kortlægge.

Vores første test ville således blive:

@Test offentlig ugyldighed givenSrcAndDest_whenMapsUsingBoundMapper_thenCorrect () {BoundMapperFacade boundMapper = mapperFactory.getMapperFacade (Source.class, Dest.class); Source src = new Source ("baeldung", 10); Dest dest = boundMapper.map (src); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

Dog for BoundMapperFacade for at kortlægge tovejs skal vi udtrykkeligt kalde mapVend metode i stedet for kortmetoden, vi har set på i tilfælde af standard MapperFacade:

@Test offentlig ugyldighed givenSrcAndDest_whenMapsUsingBoundMapperInReverse_thenCorrect () {BoundMapperFacade boundMapper = mapperFactory.getMapperFacade (Source.class, Dest.class); Dest src = new Dest ("baeldung", 10); Kilde dest = boundMapper.mapReverse (src); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

Testen mislykkes ellers.

3.2. Konfigurer feltkortlægninger

Eksemplerne, vi hidtil har set på, involverer kilde- og destinationsklasser med identiske feltnavne. Dette underafsnit tackler det tilfælde, hvor der er en forskel mellem de to.

Overvej et kildeobjekt, Person , med tre felter nemlig navn, kaldenavn og alder:

offentlig klasse person {privat strengnavn; privat streng kaldenavn; privat int alder offentlig person (strengnavn, streng kaldenavn, int alder) {dette.navn = navn; dette. kaldenavn = kaldenavn; this.age = alder; } // standard getters og setters}

Derefter har et andet lag i applikationen et lignende objekt, men skrevet af en fransk programmør. Lad os sige, at det hedder Personne, med felter nom, surnom og alder, alt svarende til ovenstående tre:

offentlig klasse Personne {private String nom; private String surnom; privat int alder public Personne (String nom, String surnom, int age) {this.nom = nom; this.surnom = surnom; this.age = alder; } // standard getters og setters}

Orika kan ikke automatisk løse disse forskelle. Men vi kan bruge ClassMapBuilder API til at registrere disse unikke tilknytninger.

Vi har allerede brugt det før, men vi har endnu ikke brugt nogen af ​​dens kraftfulde funktioner. Den første linje i hver af vores foregående tests ved hjælp af standard MapperFacade brugte ClassMapBuilder API til at registrere de to klasser, vi ønskede at kortlægge:

mapperFactory.classMap (Source.class, Dest.class);

Vi kunne også kortlægge alle felter ved hjælp af standardkonfigurationen for at gøre det tydeligere:

mapperFactory.classMap (Source.class, Dest.class) .byDefault ()

Ved at tilføje som standard() metodeopkald, konfigurerer vi allerede kortlæggerens opførsel ved hjælp af ClassMapBuilder API.

Nu vil vi være i stand til at kortlægge Personne til Person, så vi konfigurerer også feltkortlægninger på kortlæggeren ved hjælp af ClassMapBuilder API:

@Test offentligt ugyldigt givenSrcAndDestWithDifferentFieldNames_whenMaps_thenCorrect () {mapperFactory.classMap (Personne.class, Person.class) .field ("nom", "name"). Field ("surnom", "nickname") .field ("age", " alder "). register (); MapperFacade mapper = mapperFactory.getMapperFacade (); Personne frenchPerson = ny Personne ("Claire", "cla", 25); Person englishPerson = mapper.map (franskPerson, Person.class); assertEquals (englishPerson.getName (), frenchPerson.getNom ()); assertEquals (englishPerson.getNickname (), frenchPerson.getSurnom ()); assertEquals (englishPerson.getAge (), frenchPerson.getAge ()); }

Glem ikke at ringe til Tilmeld() API-metode for at registrere konfigurationen med MapperFabrik.

Selvom kun et felt adskiller sig, betyder det, at vi går eksplicit ned, når vi går ned ad denne rute alle feltkortlægninger, herunder alder hvilket er det samme i begge objekter, ellers kortlægges det uregistrerede felt ikke, og testen mislykkedes.

Dette bliver snart kedeligt, hvad hvis vi kun vil kortlægge et felt ud af 20, skal vi konfigurere alle deres tilknytninger?

Nej, ikke når vi fortæller kortlæggeren at bruge sin standardkortkonfiguration i tilfælde, hvor vi ikke eksplicit har defineret en kortlægning:

mapperFactory.classMap (Personne.class, Person.class) .field ("nom", "name"). field ("surnom", "nickname"). byDefault (). register ();

Her har vi ikke defineret en kortlægning for alder felt, men ikke desto mindre vil testen bestå.

3.3. Ekskluder et felt

Forudsat at vi gerne vil udelukke nom felt af Personne fra kortlægningen - så at Person objekt modtager kun nye værdier for felter, der ikke er ekskluderet:

@Test offentligt ugyldigt givenSrcAndDest_whenCanExcludeField_thenCorrect () {mapperFactory.classMap (Personne.class, Person.class) .exclude ("nom") .field ("surnom", "nickname"). Felt ("age", "age"). Tilmeld(); MapperFacade mapper = mapperFactory.getMapperFacade (); Personne frenchPerson = ny Personne ("Claire", "cla", 25); Person englishPerson = mapper.map (franskPerson, Person.class); assertEquals (null, englishPerson.getName ()); assertEquals (englishPerson.getNickname (), frenchPerson.getSurnom ()); assertEquals (englishPerson.getAge (), frenchPerson.getAge ()); }

Bemærk hvordan vi udelukker det i konfigurationen af MapperFabrik og bemærk derefter også den første påstand, hvor vi forventer værdien af navn i Person modstand mod at forblive nul, som et resultat af, at det blev ekskluderet i kortlægningen.

4. Kortlægning af samlinger

Nogle gange kan destinationsobjektet have unikke attributter, mens kildeobjektet bare vedligeholder hver ejendom i en samling.

4.1. Lister og arrays

Overvej et kildedataobjekt, der kun har et felt, en liste over en persons navne:

offentlig klasse PersonNameList {private List nameList; public PersonNameList (List nameList) {this.nameList = nameList; }}

Overvej nu vores destinationsdataobjekt, der adskiller sig fornavn og efternavn i separate felter:

offentlig klasse PersonNameParts {privat streng fornavn; privat streng efternavn; public PersonNameParts (String firstName, String lastName) {this.firstName = firstName; this.lastName = efternavn; }}

Lad os antage, at vi er meget sikre på, at der ved indeks 0 altid vil være fornavn af personen og i indeks 1 vil der altid være deres efternavn.

Orika giver os mulighed for at bruge parentesnotationen til at få adgang til medlemmer af en samling:

@Test offentlig ugyldighed givenSrcWithListAndDestWithPrimitiveAttributes_whenMaps_thenCorrect () {mapperFactory.classMap (PersonNameList.class, PersonNameParts.class) .field ("nameList [0]", "firstName") .field ("nameList [1]", "lastName). (); MapperFacade mapper = mapperFactory.getMapperFacade (); List nameList = Arrays.asList (ny streng [] {"Sylvester", "Stallone"}); PersonNameList src = ny PersonNameList (nameList); PersonNameParts dest = mapper.map (src, PersonNameParts.class); assertEquals (dest.getFirstName (), "Sylvester"); assertEquals (dest.getLastName (), "Stallone"); }

Selvom i stedet for PersonnavnListe, vi havde PersonnavnArray, ville den samme test bestå for en række navne.

4.2. Kort

Forudsat at vores kildeobjekt har et kort over værdier. Vi ved, at der er en nøgle i det kort, først, hvis værdi repræsenterer en persons fornavn i vores destinationsobjekt.

Ligeledes ved vi, at der er en anden nøgle, sidst, på det samme kort, hvis værdi repræsenterer en persons efternavn i destinationsobjektet.

offentlig klasse PersonNameMap {private Map nameMap; public PersonNameMap (Map nameMap) {this.nameMap = nameMap; }}

I lighed med tilfældet i det foregående afsnit bruger vi parentesnotation, men i stedet for at passere et indeks passerer vi nøglen, hvis værdi vi vil kortlægge til det givne destinationsfelt.

Orika accepterer to måder at hente nøglen på, begge er repræsenteret i følgende test:

@Test offentlig ugyldighed givenSrcWithMapAndDestWithPrimitiveAttribute_whenMaps_thenCorrect () {mapperFactory.classMap (PersonNameMap.class, PersonNameParts.class) .field ("nameMap ['first']", "firstName") .field ("nameMap [\" last \ ") "efternavn") .register (); MapperFacade mapper = mapperFactory.getMapperFacade (); Map nameMap = ny HashMap (); nameMap.put ("første", "Leornado"); nameMap.put ("sidste", "DiCaprio"); PersonNameMap src = ny PersonNameMap (nameMap); PersonNameParts dest = mapper.map (src, PersonNameParts.class); assertEquals (dest.getFirstName (), "Leornado"); assertEquals (dest.getLastName (), "DiCaprio"); }

Vi kan bruge enten enkelt citater eller dobbelt tilbud, men vi skal undslippe de sidstnævnte.

5. Kort indlejrede felter

Efter de foregående eksempler på samlinger antager du, at inde i vores kildedataobjekt er der et andet dataoverførselsobjekt (DTO), der indeholder de værdier, vi vil kortlægge.

offentlig klasse PersonContainer {privat Navn navn; offentlig PersonContainer (navn navn) {this.name = navn; }}
offentlig klasse Navn {privat streng fornavn; privat streng efternavn; offentligt navn (streng fornavn, streng efternavn) {this.firstName = fornavn; this.lastName = efternavn; }}

For at få adgang til egenskaberne for den indlejrede DTO og kortlægge dem på vores destinationsobjekt bruger vi priknotation som sådan:

@Test offentlig ugyldighed givenSrcWithNestedFields_whenMaps_thenCorrect () {mapperFactory.classMap (PersonContainer.class, PersonNameParts.class) .field ("name.firstName", "firstName") .field ("name.lastName", "lastName"). Register () ; MapperFacade mapper = mapperFactory.getMapperFacade (); PersonContainer src = ny PersonContainer (nyt navn ("Nick", "Canon")); PersonNameParts dest = mapper.map (src, PersonNameParts.class); assertEquals (dest.getFirstName (), "Nick"); assertEquals (dest.getLastName (), "Canon"); }

6. Kortlægning af nulværdier

I nogle tilfælde vil du muligvis kontrollere, om nuller kortlægges eller ignoreres, når de opstår. Som standard kortlægger Orika nulværdier, når de opstår:

@Test offentlig ugyldighed givenSrcWithNullField_whenMapsThenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .byDefault (); MapperFacade mapper = mapperFactory.getMapperFacade (); Source src = new Source (null, 10); Dest dest = mapper.map (src, Dest.class); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

Denne adfærd kan tilpasses på forskellige niveauer afhængigt af, hvor specifik vi gerne vil være.

6.1. Global konfiguration

Vi kan konfigurere vores kortlægger til at kortlægge nul eller ignorere dem på globalt niveau, inden vi opretter det globale MapperFabrik. Husk hvordan vi skabte dette objekt i vores allerførste eksempel? Denne gang tilføjer vi et ekstra opkald under byggeprocessen:

MapperFactory mapperFactory = ny DefaultMapperFactory.Builder () .mapNulls (false) .build ();

Vi kan køre en test for at bekræfte, at nuller faktisk ikke bliver kortlagt:

@Test offentlig ugyldighed givenSrcWithNullAndGlobalConfigForNoNull_whenFailsToMap_ThenCorrect () {mapperFactory.classMap (Source.class, Dest.class); MapperFacade mapper = mapperFactory.getMapperFacade (); Source src = new Source (null, 10); Dest dest = new Dest ("Clinton", 55); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), "Clinton"); }

Hvad der sker er, at der som standard kortlægges nuller. Dette betyder, at selvom en feltværdi i kildeobjektet er nul og det tilsvarende felts værdi i destinationsobjektet har en meningsfuld værdi, den overskrives.

I vores tilfælde overskrives destinationsfeltet ikke, hvis dets tilsvarende kildefelt har en nul værdi.

6.2. Lokal konfiguration

Kortlægning af nul værdier kan kontrolleres på en ClassMapBuilder ved hjælp af mapNulls (true | false) eller mapNullsInReverse (true | false) til styring af kortlægning af nuller i omvendt retning.

Ved at indstille denne værdi til en ClassMapBuilder for eksempel alle felttilknytninger oprettet på det samme ClassMapBuilder, efter at værdien er indstillet, får den samme værdi.

Lad os illustrere dette med et eksempel på en test:

@Test offentlig ugyldighed givenSrcWithNullAndLocalConfigForNoNull_whenFailsToMap_ThenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .mapNulls (false) .field ("name", "name"). ).Tilmeld(); MapperFacade mapper = mapperFactory.getMapperFacade (); Source src = new Source (null, 10); Dest dest = ny Dest ("Clinton", 55); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), "Clinton"); }

Læg mærke til, hvordan vi kalder mapNulls lige før tilmelding navn felt, vil dette medføre, at alle felter følger mapNulls opfordrer til at blive ignoreret, når de har det nul værdi.

Tovejs kortlægning accepterer også kortlagte nulværdier:

@Test offentlig ugyldighed givenDestWithNullReverseMappedToSource_whenMapsByDefault_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .byDefault (); MapperFacade mapper = mapperFactory.getMapperFacade (); Dest src = ny Dest (null, 10); Kilde dest = ny kilde ("Vin", 44); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), src.getName ()); }

Vi kan også forhindre dette ved at ringe mapNullsInReverse og passerer ind falsk:

@Test offentligt ugyldigt givenDestWithNullReverseMappedToSourceAndLocalConfigForNoNull_whenFailsToMap_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .mapNullsInReverse ("name.", "Name". " ) .Tilmeld(); MapperFacade mapper = mapperFactory.getMapperFacade (); Dest src = ny Dest (null, 10); Kilde dest = ny kilde ("Vin", 44); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), "Vin"); }

6.3. Konfiguration af feltniveau

Vi kan konfigurere dette på feltniveau ved hjælp af fieldMap, ligesom:

mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .fieldMap ("name", "name"). mapNulls (false) .add (). byDefault (). register ( );

I dette tilfælde vil konfigurationen kun påvirke navn felt som vi har kaldt det på feltniveau:

@Test offentligt ugyldigt givenSrcWithNullAndFieldLevelConfigForNoNull_whenFailsToMap_ThenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .fieldMap ("name", "name"). MapNulls (false). ) .byDefault (). register (); MapperFacade mapper = mapperFactory.getMapperFacade (); Source src = new Source (null, 10); Dest dest = new Dest ("Clinton", 55); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), "Clinton"); }

7. Orika Custom Mapping

Indtil videre har vi set på enkle brugerdefinerede kortlægningseksempler ved hjælp af ClassMapBuilder API. Vi bruger stadig den samme API, men tilpasser vores kortlægning ved hjælp af Orikas CustomMapper klasse.

Forudsat at vi har to dataobjekter hver med et bestemt felt kaldet dtob, der repræsenterer datoen og klokkeslættet for en persons fødsel.

Et dataobjekt repræsenterer denne værdi som en datetime String i følgende ISO-format:

2007-06-26T21: 22: 39Z

og den anden repræsenterer det samme som a lang skriv følgende unix tidsstempelformat:

1182882159000

Det er klart, at ikke af de tilpasninger, vi hidtil har dækket, er tilstrækkelige til at konvertere mellem de to formater under kortlægningsprocessen, ikke engang Orikas indbyggede konverter kan håndtere jobbet. Det er her, vi skal skrive en CustomMapper at udføre den krævede konvertering under kortlægning.

Lad os oprette vores første dataobjekt:

offentlig klasse Person3 {privat strengnavn; private String dtob; offentlig person3 (strengnavn, streng dtob) {dette.navn = navn; this.dtob = dtob; }}

så vores andet dataobjekt:

offentlig klasse Personne3 {privat strengnavn; privat lang dtob; offentlig Personne3 (strengnavn, lang dtob) {dette.navn = navn; this.dtob = dtob; }}

Vi markerer ikke, hvilken kilde der er, og hvilken destination lige nu, som CustomMapper gør det muligt for os at imødekomme tovejskortlægning.

Her er vores konkrete implementering af CustomMapper abstrakt klasse:

klasse PersonCustomMapper udvider CustomMapper {@Override public void mapAtoB (Personne3 a, Person3 b, MappingContext context) {Date date = new Date (a.getDtob ()); DateFormat format = nyt SimpleDateFormat ("åååå-MM-dd'T'HH: mm: ss'Z '"); Streng isoDate = format.format (dato); b.setDtob (isoDate); } @Override public void mapBtoA (Person3 b, Personne3 a, MappingContext context) {DateFormat format = new SimpleDateFormat ("åååå-MM-dd'T'HH: mm: ss'Z '"); Dato dato = format.parse (b.getDtob ()); lang tidsstempel = date.getTime (); a.setDtob (tidsstempel); }};

Bemærk, at vi har implementeret metoder mapAtoB og mapBtoA. Implementering af begge gør vores kortlægningsfunktion tovejs.

Hver metode udsætter de dataobjekter, vi kortlægger, og vi sørger for at kopiere feltværdierne fra den ene til den anden.

Der i er, hvor vi skriver den brugerdefinerede kode for at manipulere kildedataene i henhold til vores krav, før vi skriver det til destinationsobjektet.

Lad os køre en test for at bekræfte, at vores tilpassede kortlægger fungerer:

@ Test offentlig ugyldighed givenSrcAndDest_whenCustomMapperWorks_thenCorrect () {mapperFactory.classMap (Personne3.class, Person3.class) .customize (customMapper) .register (); MapperFacade mapper = mapperFactory.getMapperFacade (); String dateTime = "2007-06-26T21: 22: 39Z"; lang tidsstempel = ny Lang ("1182882159000"); Personne3 personne3 = ny Personne3 ("Leornardo", tidsstempel); Person3 person3 = mapper.map (personne3, Person3.class); assertEquals (person3.getDtob (), dateTime); }

Bemærk, at vi stadig videregiver den brugerdefinerede kortlægger til Orikas kortlægger via ClassMapBuilder API, ligesom alle andre enkle tilpasninger.

Vi kan også bekræfte, at tovejskortlægning fungerer:

@Test offentlig ugyldighed givenSrcAndDest_whenCustomMapperWorksBidirectionally_thenCorrect () {mapperFactory.classMap (Personne3.class, Person3.class) .customize (customMapper) .register (); MapperFacade mapper = mapperFactory.getMapperFacade (); String dateTime = "2007-06-26T21: 22: 39Z"; lang tidsstempel = ny Lang ("1182882159000"); Person3 person3 = ny Person3 ("Leornardo", dateTime); Personne3 personne3 = mapper.map (person3, Personne3.class); assertEquals (person3.getDtob (), tidsstempel); }

8. Konklusion

I denne artikel har vi udforskede de vigtigste funktioner i Orika-kortlægningsrammen.

Der er bestemt mere avancerede funktioner, der giver os meget mere kontrol, men i de fleste brugstilfælde vil de, der er dækket her, være mere end nok.

Den fulde projektkode og alle eksempler findes i mit github-projekt. Glem ikke at tjekke vores tutorial om Dozer-kortlægningsrammen også, da de begge løser mere eller mindre det samme problem.


$config[zx-auto] not found$config[zx-overlay] not found