Kartlegging med Orika

1. Oversikt

Orika er et Java Bean-kartleggingsrammeverk som kopierer data rekursivt fra ett objekt til et annet. Det kan være veldig nyttig når du utvikler applikasjoner med flere lag.

Når du flytter dataobjekter frem og tilbake mellom disse lagene, er det vanlig å finne ut at vi trenger å konvertere objekter fra en forekomst til en annen for å imøtekomme forskjellige APIer.

Noen måter å oppnå dette på er: hard koding av kopieringslogikken eller å implementere bønnekartleggere som Dozer. Imidlertid kan den brukes til å forenkle prosessen med å kartlegge mellom ett objektlag og et annet.

Orika bruker byte-kodegenerering for å lage raske kartleggere med minimal overhead, noe som gjør det mye raskere enn andre refleksjonsbaserte kartleggere som Dozer.

2. Enkelt eksempel

Den grunnleggende hjørnesteinen i kartleggingsrammeverket er MapperFactory klasse. Dette er klassen vi vil bruke til å konfigurere kartlegginger og få tak i MapperFacade forekomst som utfører selve kartleggingsarbeidet.

Vi lager en MapperFactory objekt som så:

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

Forutsatt at vi har et kildedataobjekt, Kilde.java, med to felt:

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

Og et lignende måldataobjekt, Dest.java:

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

Dette er den mest grunnleggende av bønnekartlegging ved bruk av Orika:

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

Som vi kan observere, har vi opprettet en Dest objekt med identiske felt som Kilde, ganske enkelt ved å kartlegge. Toveis eller omvendt kartlegging er også mulig som standard:

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

3. Maven-oppsett

For å bruke Orika-kartleggeren i våre maven-prosjekter, må vi ha orika-kjerne avhengighet i pom.xml:

 ma.glasnost.orika orika-kjerne 1.4.6 

Den siste versjonen finner du alltid her.

3. Arbeide med MapperFactory

Det generelle kartleggingsmønsteret med Orika innebærer å lage en MapperFactory objektet, og konfigurere det i tilfelle vi trenger å tilpasse standard kartleggingsatferd, og oppnå en MapperFacade objekt fra det og til slutt, faktisk kartlegging.

Vi skal følge dette mønsteret i alle eksemplene våre. Men vårt aller første eksempel viste kartleggerens standardoppførsel uten noen finjustering fra vår side.

3.1. De BoundMapperFacade vs. MapperFacade

En ting å merke seg er at vi kunne velge å bruke BoundMapperFacade over standardinnstillingen MapperFacade som er ganske treg. Dette er tilfeller der vi har et bestemt par typer å kartlegge.

Vår første test ville således bli:

@Test offentlig ugyldig 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 ()); }

Imidlertid for BoundMapperFacade for å kartlegge toveis, må vi eksplisitt kalle mapReverse metoden i stedet for kartmetoden vi har sett på for tilfellet av standard MapperFacade:

@Test offentlig ugyldig 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 feltkartlegginger

Eksemplene vi har sett på så langt involverer kilde- og destinasjonsklasser med identiske feltnavn. Dette underavsnittet tar opp saken der det er forskjell mellom de to.

Tenk på et kildeobjekt, Person , med tre felt nemlig Navn, kallenavn og alder:

offentlig klasse Person {private Strengnavn; privat streng kallenavn; privat alder; offentlig person (strengnavn, strengkallenavn, int alder) {this.name = navn; this.nickname = kallenavn; this.age = alder; } // standard getters and setters}

Så har et annet lag av applikasjonen et lignende objekt, men skrevet av en fransk programmerer. La oss si det heter Personne, med felt ingen m, surnom og alder, alt tilsvarende de ovennevnte tre:

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

Orika kan ikke automatisk løse disse forskjellene. Men vi kan bruke ClassMapBuilder API for å registrere disse unike tilordningene.

Vi har allerede brukt den før, men vi har ikke utnyttet noen av dens kraftige funksjoner ennå. Den første linjen i hver av våre foregående tester bruker standard MapperFacade brukte ClassMapBuilder API for å registrere de to klassene vi ønsket å kartlegge:

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

Vi kan også kartlegge alle felt ved hjelp av standardkonfigurasjonen, for å gjøre det tydeligere:

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

Ved å legge til avDefault () metodeanrop, konfigurerer vi allerede oppføringen til kartleggeren ved hjelp av ClassMapBuilder API.

Nå ønsker vi å kunne kartlegge Personne til Person, så vi konfigurerer også feltkartlegginger på kartleggeren ved hjelp av ClassMapBuilder API:

@Test offentlig ugyldig gittSrcAndDestWithDifferentFieldNames_whenMaps_thenCorrect () {mapperFactory.classMap (Personne.class, Person.class) .field ("nom", "name"). Field ("surnom", "nickname") .field ("age", " alder "). register (); MapperFacade mapper = mapperFactory.getMapperFacade (); Personne frenchPerson = nye 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 ()); }

Ikke glem å ringe registrere() API-metode for å registrere konfigurasjonen med MapperFactory.

Selv om bare ett felt er forskjellig, betyr det at vi må registrere oss eksplisitt når vi går ned denne ruten alle feltkartlegginger, inkludert alder som er det samme i begge objekter, ellers vil ikke det uregistrerte feltet bli kartlagt, og testen mislykkes.

Dette vil snart bli kjedelig, hva om vi bare vil kartlegge ett felt av 20, trenger vi å konfigurere alle tilknytningene deres?

Nei, ikke når vi ber kartleggeren om å bruke standard kartleggingskonfigurasjon i tilfeller der vi ikke eksplisitt har definert en kartlegging:

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

Her har vi ikke definert en kartlegging for alder felt, men likevel vil testen bestå.

3.3. Ekskluder et felt

Forutsatt at vi ønsker å ekskludere ingen m innen Personne fra kartleggingen - slik at Person objektet mottar bare nye verdier for felt som ikke er ekskludert:

@Test offentlig ugyldig gittSrcAndDest_whenCanExcludeField_thenCorrect () {mapperFactory.classMap (Personne.class, Person.class) .exclude ("nom") .field ("surnom", "nickname"). Felt ("age", "age"). registrere(); MapperFacade mapper = mapperFactory.getMapperFacade (); Personne frenchPerson = nye 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 ()); }

Legg merke til hvordan vi ekskluderer det i konfigurasjonen av MapperFactory og legg også merke til den første påstanden hvor vi forventer verdien av Navn i Person innvende å forbli null, som et resultat av at den er ekskludert i kartleggingen.

4. Kartlegging av samlinger

Noen ganger kan målobjektet ha unike attributter mens kildeobjektet bare vedlikeholder hver eiendom i en samling.

4.1. Lister og matriser

Vurder et kildedataobjekt som bare har ett felt, en liste over personens navn:

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

Vurder nå vårt destinasjonsdataobjekt som skiller seg fornavn og etternavn i separate felt:

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

La oss anta at vi er veldig sikre på at det ved indeks 0 alltid vil være fornavn av personen og i indeks 1 vil det alltid være deres etternavn.

Orika tillater oss å bruke brakettnotasjonen for å få tilgang til medlemmer av en samling:

@Test offentlig ugyldig 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"); }

Selv om i stedet for PersonnavnListe, vi hadde PersonNameArray, ville den samme testen bestått for en rekke navn.

4.2. Kart

Forutsatt at kildeobjektet vårt har et verdikart. Vi vet at det er en nøkkel i det kartet, først, hvis verdi representerer en persons fornavn i destinasjonsobjektet.

På samme måte vet vi at det er en annen nøkkel, siste, på samme kart hvis verdi representerer en persons etternavn i destinasjonsobjektet.

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

I likhet med tilfellet i forrige avsnitt bruker vi parentesnotasjon, men i stedet for å sende inn en indeks, passerer vi nøkkelen hvis verdi vi vil kartlegge til det gitte destinasjonsfeltet.

Orika godtar to måter å hente nøkkelen på, begge er representert i følgende test:

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

Vi kan bruke enten enkle eller doble anførselstegn, men vi må unnslippe de sistnevnte.

5. Kartlegg nestede felt

Ut fra eksemplene fra de foregående samlingene, anta at det er et annet dataoverføringsobjekt (DTO) inne i kildedataobjektet vårt som inneholder verdiene vi vil kartlegge.

offentlig klasse PersonContainer {private Navn navn; public PersonContainer (Name name) {this.name = name; }}
offentlig klasse Navn {privat streng fornavn; privat streng etternavn; offentlig navn (streng fornavn, streng etternavn) {this.firstName = fornavn; this.lastName = etternavn; }}

For å få tilgang til egenskapene til den nestede DTO og kartlegge dem på destinasjonsobjektet, bruker vi punktnotasjon, slik:

@Test offentlig ugyldig 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 (nytt navn ("Nick", "Canon")); PersonNameParts dest = mapper.map (src, PersonNameParts.class); assertEquals (dest.getFirstName (), "Nick"); assertEquals (dest.getLastName (), "Canon"); }

6. Kartlegge nullverdier

I noen tilfeller kan det være lurt å kontrollere om null blir kartlagt eller ignorert når de blir oppdaget. Som standard vil Orika tilordne nullverdier når det oppstår:

@Test offentlig ugyldig 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 oppførselen kan tilpasses på forskjellige nivåer, avhengig av hvor spesifikk vi ønsker å være.

6.1. Global konfigurasjon

Vi kan konfigurere kartleggeren vår til å tilordne null eller ignorere dem på globalt nivå før vi oppretter den globale MapperFactory. Husker du hvordan vi opprettet dette objektet i vårt aller første eksempel? Denne gangen legger vi til en ekstra samtale under byggeprosessen:

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

Vi kan kjøre en test for å bekrefte at nullene ikke blir kartlagt:

@Test offentlig ugyldig 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"); }

Det som skjer er at nullverdier som standard er kartlagt. Dette betyr at selv om en feltverdi i kildeobjektet er null og det tilsvarende feltets verdi i destinasjonsobjektet har en meningsfull verdi, vil det bli overskrevet.

I vårt tilfelle blir ikke destinasjonsfeltet overskrevet hvis det tilsvarende kildefeltet har et null verdi.

6.2. Lokal konfigurasjon

Kartlegging av null verdier kan kontrolleres på en ClassMapBuilder ved å bruke mapNulls (true | false) eller mapNullsInReverse (true | false) for å kontrollere kartlegging av null i motsatt retning.

Ved å sette denne verdien på en ClassMapBuilder for eksempel, alle feltkartlegginger opprettet på det samme ClassMapBuilder, etter at verdien er satt, tar den samme verdien.

La oss illustrere dette med et eksempel på en test:

@Test offentlig ugyldig givenSrcWithNullAndLocalConfigForNoNull_whenFailsToMap_ThenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .mapNulls (false) .field ("name", "name"). ).registrere(); 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"); }

Legg merke til hvordan vi kaller mapNulls rett før du registrerer deg Navn felt, vil dette føre til at alle felt som følger mapNulls kall for å bli ignorert når de har null verdi.

Toveis kartlegging godtar også tilordnede nullverdier:

@Test offentlig ugyldig givenDestWithNullReverseMappedToSource_whenMapsByDefault_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .byDefault (); MapperFacade mapper = mapperFactory.getMapperFacade (); Dest src = new 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 å ringe mapNullsInReverse og passerer inn falsk:

@Test offentlig ugyldighet gittDestWithNullReverseMappedToSourceAndLocalConfigForNoNull_whenFailsToMap_thenCorrect () {mapperFactory.classMap (Source.class, Dest.class) .field ("age", "age") .mapNullsInReverse (")." ) .registrere(); MapperFacade mapper = mapperFactory.getMapperFacade (); Dest src = new Dest (null, 10); Kilde dest = ny Kilde ("Vin", 44); mapper.map (src, dest); assertEquals (dest.getAge (), src.getAge ()); assertEquals (dest.getName (), "Vin"); }

6.3. Feltnivåkonfigurasjon

Vi kan konfigurere dette på feltnivå ved hjelp av fieldMap, som så:

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

I dette tilfellet vil konfigurasjonen bare påvirke Navn felt som vi har kalt det på feltnivå:

@Test offentlig ugyldighet gittSrcWithNullAndFieldLevelConfigForNoNull_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 tilpasset kartlegging

Så langt har vi sett på enkle tilpassede kartleggingseksempler ved hjelp av ClassMapBuilder API. Vi vil fortsatt bruke samme API, men tilpasse kartleggingen vår ved hjelp av Orikas CustomMapper klasse.

Forutsatt at vi har to dataobjekter hver med et bestemt felt kalt dtob, som representerer datoen og tidspunktet for fødselen til en person.

Ett dataobjekt representerer denne verdien som en datetime String i følgende ISO-format:

2007-06-26T21: 22: 39Z

og den andre representerer det samme som a lang skriv inn følgende unix tidsstempelformat:

1182882159000

Det er klart at ikke av tilpasningene vi har dekket så langt er tilstrekkelig til å konvertere mellom de to formatene under kartleggingsprosessen, ikke engang Orikas innebygde omformer kan håndtere jobben. Det er her vi må skrive en CustomMapper for å gjøre den nødvendige konverteringen under kartleggingen.

La oss lage vårt første dataobjekt:

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

så vårt andre dataobjekt:

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

Vi vil ikke merke hvilken kilde som er destinasjon akkurat nå som CustomMapper gjør oss i stand til å ta imot toveiskartlegging.

Her er vår konkrete implementering av CustomMapper abstrakt klasse:

klasse PersonCustomMapper utvider CustomMapper {@Override public void mapAtoB (Personne3 a, Person3 b, MappingContext context) {Date date = new Date (a.getDtob ()); DateFormat format = nytt 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 ("yyyy-MM-dd'T'HH: mm: ss'Z '"); Dato dato = format.parse (b.getDtob ()); lang tidsstempel = date.getTime (); a.setDtob (tidsstempel); }};

Legg merke til at vi har implementert metoder mapAtoB og mapBtoA. Implementering av begge deler gjør at kartleggingsfunksjonen vår er toveis.

Hver metode eksponerer dataobjektene vi kartlegger, og vi tar oss av å kopiere feltverdiene fra den ene til den andre.

Det er der vi skriver den tilpassede koden for å manipulere kildedataene i henhold til våre krav før vi skriver den til målobjektet.

La oss kjøre en test for å bekrefte at vår tilpassede kartlegger fungerer:

@Test offentlig ugyldig 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 = nye Personne3 ("Leornardo", tidsstempel); Person3 person3 = mapper.map (personne3, Person3.class); assertEquals (person3.getDtob (), dateTime); }

Legg merke til at vi fremdeles sender den tilpassede kartleggeren til Orikas kartlegger via ClassMapBuilder API, akkurat som alle andre enkle tilpasninger.

Vi kan også bekrefte at toveiskartlegging fungerer:

@Test offentlig ugyldig 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. Konklusjon

I denne artikkelen har vi utforsket de viktigste funksjonene i Orika kartleggingsrammeverk.

Det er definitivt mer avanserte funksjoner som gir oss mye mer kontroll, men i de fleste brukstilfeller vil de som er dekket her være mer enn nok.

Fullstendig prosjektkode og alle eksempler finner du i github-prosjektet mitt. Ikke glem å sjekke ut veiledningen vår om Dozer-kartleggingsrammeverket også, siden de begge løser mer eller mindre det samme problemet.


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