Vodič za mapiranje s buldožerom

1. Pregled

Dozer je Mapa Java Bean u Java Bean koji rekurzivno kopira podatke s jednog objekta na drugi, atribut po atribut.

Biblioteka ne podržava samo mapiranje između imena atributa Java Beans-a, već i automatski pretvara između vrsta - ako se razlikuju.

Većina scenarija pretvorbe podržava se odmah, ali Dozer vam to također omogućuje odredite prilagođene pretvorbe putem XML-a.

2. Jednostavan primjer

Za naš prvi primjer, pretpostavimo da svi izvorni i odredišni podatkovni objekti dijele ista uobičajena imena atributa.

Ovo je najosnovnije mapiranje koje možete učiniti s Dozerom:

izvor javne klase {naziv privatnog niza; privatno int doba; javni izvor () {} javni izvor (ime niza, int age) {this.name = name; this.age = dob; } // standardni getteri i postavljači}

Zatim naša odredišna datoteka, Dest.java:

javna klasa Dest {naziv privatnog niza; privatno int doba; javno odredište () {} javno odredište (ime niza, int age) {this.name = name; this.age = dob; } // standardni getteri i postavljači}

Moramo se pobrinuti za to uključuju zadane ili nulte konstruktore argumenata, budući da Dozer koristi odraz ispod haube.

I, u svrhu izvedbe, učinimo naš mapper globalnim i stvorimo jedan objekt koji ćemo koristiti tijekom naših testova:

Mapa DozerBeanMapper; @Before public void before () baca iznimku {mapper = new DozerBeanMapper (); }

Ajmo sada pokrenuti naš prvi test kako bismo to potvrdili kad kreiramo Izvor objekt, možemo ga mapirati izravno na Dest objekt:

@Test javna praznina givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrect_ thenCorrect () {Izvor izvora = novi izvor ("Baeldung", 10); Dest dest = mapper.map (izvor, Dest.class); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

Kao što vidimo, nakon mapiranja Dozera, rezultat će biti nova instanca Dest objekt koji sadrži vrijednosti za sva polja koja imaju isto ime polja kao i Izvor objekt.

Alternativno, umjesto prolaska mapper the Dest razreda, mogli smo jednostavno stvoriti Dest objekt i prošli mapper njegova referenca:

@Test javna praznina givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrect_ thenCorrect () {Izvor izvora = novi izvor ("Baeldung", 10); Dest dest = novi Dest (); mapper.map (izvor, dest); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

3. Postavljanje Mavena

Sad kad smo osnovno razumjeli kako Dozer radi, dodajmo sljedeću ovisnost o pom.xml:

 net.sf.dozer buldožer 5.5.1 

Najnovija verzija dostupna je ovdje.

4. Primjer pretvorbe podataka

Kao što već znamo, Dozer može mapirati postojeći objekt na drugi sve dok nalazi atribute istog imena u obje klase.

Međutim, to nije uvijek slučaj; i tako, ako je bilo koji preslikani atribut različitog tipa podataka, motor za mapiranje Dozer hoće automatski izvrši pretvorbu tipa podataka.

Pogledajmo ovaj novi koncept na djelu:

javna klasa Source2 {id privatnog niza; privatni dvostruki bodovi; javni Izvor2 () {} javni Izvor2 (ID niza, dvostruki bodovi) {this.id = id; this.points = bodovi; } // standardni getteri i postavljači}

I odredišna klasa:

javna klasa Dest2 {private int id; privatne int točke; javno Dest2 () {} javno Dest2 (int id, int bodovi) {super (); this.id = id; this.points = bodovi; } // standardni getteri i postavljači}

Primijetite da su imena atributa ista, ali njihovi su tipovi podataka različiti.

U izvornoj klasi, iskaznica je Niz i bodova je dvostruko, dok u odredišnoj klasi, iskaznica i bodova su oboje cijeli brojs.

Pogledajmo sada kako Dozer ispravno obrađuje pretvorbu:

@Test javna praznina givenSourceAndDestWithDifferentFieldTypes_ whenMapsAndAutoConverts_thenCorrect () {Izvor2 izvor = novi Izvor2 ("320", 15.2); Dest2 dest = mapper.map (izvor, Dest2.class); assertEquals (dest.getId (), 320); assertEquals (dest.getPoints (), 15); }

Prošli smo “320” i 15.2, a Niz i a dvostruko u izvorni objekt i rezultat je imao 320 i 15, oba cijeli brojs u odredišnom objektu.

5. Osnovna prilagođena mapiranja putem XML-a

U svim prethodnim primjerima koje smo vidjeli, objekti podataka izvora i odredišta imaju ista imena polja, što omogućava lako mapiranje s naše strane.

Međutim, u stvarnim će se aplikacijama bezbroj puta dogoditi da dva podatkovna objekta koja mapiramo neće imati polja koja dijele zajedničko ime svojstva.

Da bismo to riješili, Dozer nam daje mogućnost stvaranja a prilagođena konfiguracija mapiranja u XML-u.

U ovoj XML datoteci možemo definirati unose mapiranja klasa koje će Dozer engine mapping koristiti za odlučivanje koji će atribut izvora preslikati u koji odredišni atribut.

Pogledajmo primjer i pokušajmo ukloniti oznake podatkovnih objekata iz aplikacije koju je izradio francuski programer, u engleski stil imenovanja naših objekata.

Imamo Osoba objekt s Ime, nadimak i dob polja:

javni razred Osoba {ime privatnog niza; private String nadimak; privatno int doba; javna osoba () {} javna osoba (ime niza, nadimak niza, int age) {super (); this.name = ime; this.nickname = nadimak; this.age = dob; } // standardni getteri i postavljači}

Predmet koji uklanjamo s oznake naziva se Personne i ima polja ne m, surnom i dob:

javni razred Personne {private String nom; privatni gudački surnom; privatno int doba; public Personne () {} public Personne (String nom, String surnom, int age) {super (); this.nom = nom; this.surnom = surnom; this.age = dob; } // standardni getteri i postavljači}

Ti predmeti stvarno postižu istu svrhu, ali mi imamo jezičnu barijeru. Kako bismo pomogli s tom preprekom, možemo koristiti Dozer za mapiranje Francuza Personne prigovoriti našem Osoba objekt.

Moramo samo stvoriti prilagođenu datoteku mapiranja koja će Dozeru pomoći da to učini, mi ćemo ga nazvati dozer_mapping.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom Ime   surnom nadimak

Ovo je najjednostavniji primjer prilagođene datoteke XML mapiranja koji možemo imati.

Za sada je dovoljno primijetiti da imamo kao naš korijenski element koji ima dijete , možemo imati toliko ove djece unutra jer postoje slučajevi parova razreda kojima je potrebno prilagođeno mapiranje.

Također primijetite kako specificiramo izvornu i odredišnu klasu unutar oznake. Nakon toga slijedi a za svaki par polja izvora i odredišta kojima je potrebno prilagođeno mapiranje.

Napokon, primijetite da nismo uključili polje dob u našoj prilagođenoj datoteci mapiranja. Francuska riječ za dob i dalje je dob, što nas dovodi do još jedne važne značajke Dozera.

Svojstva istog naziva ne trebaju biti navedena u mapiranju XML datoteke. Dozer automatski mapira sva polja s istim imenom svojstva iz izvornog objekta u odredišni objekt.

Zatim ćemo svoju prilagođenu XML datoteku smjestiti na put predavanja izravno ispod src mapu. Međutim, gdje god ga postavimo na put predavanja, Dozer će pretražiti cijelu put predavanja tražeći navedenu datoteku.

Stvorimo pomoćnu metodu za dodavanje datoteka mapiranja u našu mapper:

javna praznina configureMapper (String ... mappingFileUrls) {mapper.setMappingFiles (Arrays.asList (mappingFileUrls)); }

Ajmo sada testirati kod:

@Test javna praznina givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMaps_thenCorrect () {configureMapper ("dozer_mapping.xml"); Personne frenchAppPerson = nova Personne ("Sylvester Stallone", "Rambo", 70); Osoba englishAppPerson = mapper.map (frenchAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), frenchAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), frenchAppPerson.getAge ()); }

Kao što je prikazano u testu, DozerBeanMapper prihvaća popis prilagođenih XML datoteka za mapiranje i odlučuje kada će ih koristiti tijekom izvođenja.

Pod pretpostavkom da sada počinjemo uklanjati podatke s ovih objekata podataka između engleske i francuske aplikacije. Ne trebamo kreirati još jedno preslikavanje u XML datoteci, Dozer je dovoljno pametan da objekte mapira u oba smjera sa samo jednom konfiguracijom mapiranja:

@Test javna praznina givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMapsBidirectionally_thenCorrect () {configureMapper ("dozer_mapping.xml"); Person englishAppPerson = nova osoba ("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 ()); }

I tako ovaj primjer koristi još jednu značajku Dozera - činjenicu da motor za mapiranje Dozer je dvosmjeran, pa ako želimo mapirati odredišni objekt s izvornim objektom, ne moramo dodavati drugo mapiranje klase u XML datoteku.

Također možemo učitati prilagođenu datoteku za mapiranje izvan učiteljske staze, ako je potrebno, upotrijebite "datoteka:Prefiks u nazivu resursa.

U Windows okruženju (kao što je test u nastavku), naravno, koristit ćemo sintaksu datoteka specifičnu za Windows.

Na Linuxu možemo datoteku pohraniti pod /Dom i onda:

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

I na Mac OS-u:

configureMapper ("datoteka: /Korisnici/me/dozer_mapping.xml");

Ako izvodite jedinične testove iz github projekta (što biste trebali), možete kopirati datoteku preslikavanja na odgovarajuće mjesto i promijeniti ulaz za configureMapper metoda.

Datoteka mapiranja dostupna je u mapi test / resursi projekta GitHub:

@Test javna praznina givenMappingFileOutsideClasspath_whenMaps_thenCorrect () {configureMapper ("datoteka: E: \ dozer_mapping.xml"); Person englishAppPerson = nova osoba ("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. Zamjenski znakovi i daljnja prilagodba XML-a

Stvorimo drugu prilagođenu datoteku za mapiranje pod nazivom dozer_mapping2.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom Ime   surnom nadimak

Primijetite da smo dodali atribut zamjenski znak prema element koji prije nije bio tamo.

Prema zadanim postavkama, zamjenski znak je pravi. Govori Dozer engineu da želimo da se sva polja u izvornom objektu preslikaju na odgovarajuća odredišna polja.

Kad smo to postavili lažno, govorimo Dozeru da mapira samo polja koja smo izričito naveli u XML-u.

Dakle, u gornjoj konfiguraciji želimo samo dva polja mapirana, izostavljena dob:

@Test javna praznina givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect () {configureMapper ("dozer_mapping2.xml"); Person englishAppPerson = nova osoba ("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); }

Kao što vidimo u posljednjoj tvrdnji, odredište dob polje ostalo 0.

7. Prilagođeno mapiranje putem bilješki

Za jednostavne slučajeve mapiranja i slučajeve u kojima također imamo pristup upisu u podatkovne objekte koje bismo željeli mapirati, možda nećemo trebati koristiti XML mapiranje.

Mapiranje različito imenovanih polja putem bilješki vrlo je jednostavno i moramo napisati puno manje koda nego u XML mapiranju, ali može nam pomoći samo u jednostavnim slučajevima.

Replicirajmo svoje podatkovne objekte u Osoba2.java i Personne2.java a da uopće ne mijenja polja.

Da bismo to implementirali, trebamo samo dodati @mapper (“destinationFieldName”) bilješka na getter metode u izvornom objektu. Ovako:

@Mapping ("name") javni niz getNom () {return nom; } @Mapping ("nadimak") javni niz getSurnom () {return surnom; }

Ovaj put liječimo Personne2 kao izvor, ali nije bitno zbog dvosmjerne prirode motora buldožera.

Sada je sa svim uklonjenim kodom povezanim s XML-om naš testni kôd kraći:

@Test javna praznina givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect () {Person2 englishAppPerson = nova 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 ()); }

Također možemo testirati dvosmjernost:

@Test javna praznina givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_ thenCorrect () {Personne2 frenchAppPerson = nova 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. Prilagođeno mapiranje API-ja

U našim prethodnim primjerima gdje uklanjamo oznake podataka iz francuske aplikacije, koristili smo XML i bilješke za prilagodbu našeg mapiranja.

Druga alternativa dostupna u Dozeru, slična mapiranju napomena, je API mapiranje. Slični su jer eliminiramo XML konfiguraciju i strogo koristimo Java kôd.

U ovom slučaju koristimo BeanMappingBuilder klasa, definirana u našem najjednostavnijem slučaju ovako:

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

Kao što vidimo, imamo apstraktnu metodu, konfigurirati(), koju moramo nadjačati da bismo definirali naše konfiguracije. Tada, baš kao i naša oznake u XML-u, definiramo ih isto toliko TypeMappingBuilders kako mi zahtijevamo.

Ovi graditelji kažu Dozeru koja mapiramo od izvora do odredišta. Zatim prolazimo BeanMappingBuilder do DozerBeanMapper kao što bismo i mi, XML datoteka mapiranja, samo s drugim API-jem:

@Test javna praznina givenApiMapper_whenMaps_thenCorrect () {mapper.addMapping (graditelj); Personne frenchAppPerson = nova Personne ("Sylvester Stallone", "Rambo", 70); Osoba englishAppPerson = mapper.map (frenchAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), frenchAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), frenchAppPerson.getAge ()); }

API mapiranja je također dvosmjeran:

@Test javna praznina givenApiMapper_whenMapsBidirectionally_thenCorrect () {mapper.addMapping (graditelj); Person englishAppPerson = nova osoba ("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 ()); }

Ili možemo odabrati mapiranje samo izričito navedenih polja s ovom konfiguracijom graditelja:

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

i naše dob == 0 test se vratio:

@Test javna praznina givenApiMapper_whenMapsOnlySpecifiedFields_thenCorrect () {mapper.addMapping (builderMinusAge); Person englishAppPerson = nova osoba ("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. Prilagođeni pretvarači

Sljedeći scenarij s kojim se možemo suočiti u mapiranju je tamo gdje bismo željeli izvršiti prilagođeno mapiranje između dva objekta.

Pregledali smo scenarije u kojima se nazivi izvora i odredišta razlikuju kao u Francuskoj Personne objekt. Ovaj odjeljak rješava drugačiji problem.

Što ako objekt podataka koji uklanjamo oznake predstavlja polje datuma i vremena kao što je dugo ili Unix vrijeme ovako:

1182882159000

Ali naš vlastiti ekvivalentni objekt podataka predstavlja isto polje datuma i vremena i vrijednost u ovom ISO formatu kao što je Niz:

2007-06-26T21: 22: 39Z

Zadani pretvarač jednostavno bi mapirao dugu vrijednost u Niz ovako:

"1182882159000"

Ovo bi definitivno napalo našu aplikaciju. Pa kako to riješiti? Mi to rješavamo do dodavanje konfiguracijskog bloka u mapiranju XML datoteke i specificirajući vlastiti pretvarač.

Prvo, ponovimo udaljene aplikacije Osoba DTO s a Ime, zatim datum i vrijeme rođenja, dtob polje:

javna klasa Personne3 {ime privatnog niza; privatni dugi dtob; javni Personne3 (naziv niza, dugi dtob) {super (); this.name = ime; this.dtob = dtob; } // standardni getteri i postavljači}

i evo našeg:

javna klasa Person3 {ime privatnog niza; privatni niz dtob; javni Person3 (naziv niza, niz dtob) {super (); this.name = ime; this.dtob = dtob; } // standardni getteri i postavljači}

Primijetite razliku u tipu dtob u izvornom i odredišnom DTO-u.

Stvorimo i svoje CustomConverter za prosljeđivanje Dozeru u preslikavanju XML:

javna klasa MyCustomConvertor implementira CustomConverter {@Override javni pretvorba objekta (objekt odredište, izvor objekta, klasa arg2, klasa arg3) {if (source == null) return null; if (izvor instanceof Personne3) {Personne3 person = (Personne3) izvor; Datum datuma = novi datum (person.getDtob ()); DateFormat format = novi SimpleDateFormat ("gggg-MM-dd'T'HH: mm: ss'Z '"); Niz isoDate = format.format (datum); vrati novu Person3 (person.getName (), isoDate); } else if (izvor instance Person3) {Person3 person = (Person3) izvor; DateFormat format = novi SimpleDateFormat ("gggg-MM-dd'T'HH: mm: ss'Z '"); Datum datum = format.parse (person.getDtob ()); duga vremenska oznaka = date.getTime (); vratiti novu Personne3 (person.getName (), vremensku oznaku); }}}

Moramo samo nadvladati Pretvoriti() zatim vratimo sve što želimo da joj se vrati. Dostupni smo s izvornim i odredišnim objektima i njihovim vrstama klasa.

Primijetite kako smo se pobrinuli za dvosmjernost pretpostavljajući da izvor može biti bilo koja od dvije klase koju mapiramo.

Stvorit ćemo novu datoteku za mapiranje radi jasnosti, dozer_custom_convertor.xml:

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

Ovo je normalna datoteka mapiranja koju smo vidjeli u prethodnim odjeljcima, dodali smo samo a blok unutar kojeg možemo definirati onoliko prilagođenih pretvarača koliko nam je potrebno s njihovim odgovarajućim klasama podataka izvora i odredišta.

Isprobajmo naše novo CustomConverter kodirati:

@Test javna praznina givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_ thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; duga vremenska oznaka = novo Long ("1182882159000"); Person3 person = nova Person3 ("Rich", dateTime); Personne3 person0 = mapper.map (person, Personne3.class); assertEquals (vremenska oznaka, person0.getDtob ()); }

Također možemo testirati dvosmjernost:

@Test javna praznina givenSrcAndDestWithDifferentFieldTypes_ whenAbleToCustomConvertBidirectionally_thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; duga vremenska oznaka = novo Long ("1182882159000"); Personne3 person = nova Personne3 ("Bogata", vremenska oznaka); Person3 person0 = mapper.map (person, Person3.class); assertEquals (dateTime, person0.getDtob ()); }

10. Zaključak

U ovom uputstvu imamo uveo većinu osnova knjižnice Dozer Mapping i kako ga koristiti u našim aplikacijama.

Potpuna implementacija svih ovih primjera i isječaka koda može se naći u projektu Dozer github.