Prilagođene vrste u hibernaciji i bilješka @Type

1. Pregled

Hibernate pojednostavljuje rukovanje podacima između SQL-a i JDBC-a preslikavanjem objektno orijentiranog modela u Javi s relacijskim modelom u bazama podataka. Iako mapiranje osnovnih Java klasa ugrađeno je u Hibernate, mapiranje prilagođenih tipova često je složeno.

U ovom uputstvu vidjet ćemo kako nam Hibernate omogućuje proširivanje osnovnog mapiranja tipova na prilagođene Java klase. Uz to, vidjet ćemo i neke uobičajene primjere prilagođenih tipova i implementirati ih pomoću Hibernate-ovog mehanizma mapiranja tipova.

2. Vrste mapiranja hibernacije

Hibernate koristi tipove mapiranja za pretvaranje Java objekata u SQL upite za pohranu podataka. Slično tome, koristi tipove mapiranja za pretvaranje SQL ResultSet-a u Java objekte tijekom dohvaćanja podataka.

Općenito Hibernate kategorizira tipove u vrste entiteta i vrste vrijednosti. Konkretno, tipovi entiteta koriste se za mapiranje Java entiteta specifičnih za domenu i stoga postoje neovisno o ostalim vrstama u aplikaciji. Suprotno tome, vrste vrijednosti koriste se za mapiranje objekata podataka i gotovo su uvijek u vlasništvu entiteta.

U ovom uputstvu usredotočit ćemo se na mapiranje vrsta vrijednosti koje se dalje klasificiraju u:

  • Osnovni tipovi - Mapiranje osnovnih tipova Java
  • Ugradivo - Mapiranje za kompozitne java tipove / POJO-ove
  • Zbirke - Mapiranje za zbirku osnovnog i složenog java tipa

3. Ovisnosti Mavena

Da bismo stvorili naše prilagođene vrste hibernacije, trebat će nam ovisnost o hibernaciji-core:

 org.hibernate hibernate-core 5.3.6.Final 

4. Prilagođene vrste u hibernaciji

Za većinu korisničkih domena možemo koristiti Hibernate osnovne tipove mapiranja. Međutim, postoji mnogo slučajeva korištenja, kada moramo implementirati prilagođeni tip.

Hibernate čini relativno jednostavnijom implementaciju prilagođenih vrsta. Postoje tri pristupa implementaciji prilagođenog tipa u hibernaciji. Razmotrimo detaljno svaku od njih.

4.1. Provedba BasicType

Možemo stvoriti prilagođeni osnovni tip primjenom Hibernate-a BasicType ili jedna od njegovih specifičnih implementacija, AbstractSingleColumnStandardBasicType.

Prije nego što implementiramo svoj prvi prilagođeni tip, pogledajmo uobičajeni slučaj primjene osnovnog tipa. Pretpostavimo da moramo raditi s naslijeđenom bazom podataka koja pohranjuje datume kao VARCHAR. Normalno, redovno, Hibernate bi ovo mapirao Niz Java tip. Stoga otežava provjeru datuma programerima aplikacija.

Pa provodimo svoje LocalDateString tip, koji pohranjuje LocalDate Tip Java kao VARCHAR:

javna klasa LocalDateStringType proširuje AbstractSingleColumnStandardBasicType {javni statički konačni LocalDateStringType INSTANCE = novi LocalDateStringType (); javni LocalDateStringType () {super (VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE); } @Override javni niz getName () {return "LocalDateString"; }}

Najvažnija stvar u ovom kodu su parametri konstruktora. Prvo, je instanca SqlTypeDescriptor, što je Hibernate-ov prikaz tipa SQL, što je VARCHAR za naš primjer. I, drugi argument je primjer JavaTypeDescriptor koji predstavlja tip Java.

Sad možemo provesti a LocalDateStringJavaDescriptor za spremanje i dohvaćanje LocalDate kao VARCHAR:

javna klasa LocalDateStringJavaDescriptor proširuje AbstractTypeDescriptor {javni statički konačni LocalDateStringJavaDescriptor INSTANCE = novi LocalDateStringJavaDescriptor (); javni LocalDateStringJavaDescriptor () {super (LocalDate.class, ImmutableMutabilityPlan.INSTANCE); } // ostale metode}

Dalje, moramo nadjačati zamotati i razmotati metode za pretvaranje tipa Java u SQL. Počnimo s razmotati:

@Override javno X odmotavanje (vrijednost LocalDate, vrsta klase, opcije WrapperOptions) {if (value == null) return null; if (String.class.isAssignableFrom (type)) return (X) LocalDateType.FORMATTER.format (value); baciti unknownUnwrap (type); }

Dalje, zamotati metoda:

@Override javno premotavanje LocalDate (X vrijednost, opcije WrapperOptions) {if (value == null) return null; if (String.class.isInstance (value)) return LocalDate.from (LocalDateType.FORMATTER.parse ((CharSequence) value)); baciti unknownWrap (value.getClass ()); }

razmotati() naziva se tijekom PreparedStatement obvezujući za pretvoriti LocalDate na tip String, koji se preslikava na VARCHAR. Također, zamotati () naziva se tijekom Postavi rezultat dohvat za pretvaranje Niz na Javu LocalDate.

Napokon, svoj prilagođeni tip možemo koristiti u klasi Entity:

@Entity @Table (name = "OfficeE Employee") javna klasa OfficeEeeeeeee {@Column @Type (type = "com.baeldung.hibernate.customtypes.LocalDateStringType") private LocalDate dateOfJoining; // ostala polja i metode}

Kasnije ćemo vidjeti kako možemo registrirati ovu vrstu u hibernaciji. I kao rezultat, pozivaju se na ovu vrstu pomoću registracijskog ključa umjesto potpuno kvalificiranog naziva klase.

4.2. Provedba Tip korisnika

Uz raznolikost osnovnih tipova u hibernaciji, vrlo je rijetko da trebamo implementirati prilagođeni osnovni tip. Suprotno tome, tipičniji je slučaj mapiranja složenog objekta Java domene u bazu podataka. Takvi se objekti domene obično pohranjuju u više stupaca baze podataka.

Pa provedimo kompleks Broj telefona objekt provođenjem Tip korisnika:

javna klasa PhoneNumberType implementira UserType {@Override public int [] sqlTypes () {return new int [] {Types.INTEGER, Types.INTEGER, Types.INTEGER}; } @Override javna klasa returnClass () {return PhoneNumber.class; } // ostale metode} 

Evo, nadjačano sqlVrste metoda vraća SQL vrste polja, istim redoslijedom kao što su deklarirani u našem Broj telefona razred. Slično tome, returnClass metoda vraća našu Broj telefona Java tip.

Preostalo je još implementirati metode za pretvorbu između tipa Java i SQL tipa, kao što smo to učinili za naš BasicType.

Prvo, nullSafeGet metoda:

@Override public Object nullSafeGet (ResultSet rs, String [] names, SharedSessionContractImplementor session, Object owner) baca HibernateException, SQLException {int countryCode = rs.getInt (imena [0]); if (rs.wasNull ()) return null; int cityCode = rs.getInt (imena [1]); int broj = rs.getInt (imena [2]); PhoneNumber zaposlenikNumber = novi telefonski broj (countryCode, cityCode, number); povrat zaposlenikaBroj; }

Dalje, nullSafeSet metoda:

@Override public void nullSafeSet (PreparedStatement st, vrijednost objekta, int index, SharedSessionContractImplementor session) baca HibernateException, SQLException {if (Objects.isNull (value)) {st.setNull (index, Types.INTEGER); st.setNull (indeks + 1, tipovi.INTEGER); st.setNull (indeks + 2, tipovi.INTEGER); } else {Telefonski broj zaposlenikBroj = (Broj telefona) vrijednost; st.setInt (index, workerNumber.getCountryCode ()); st.setInt (indeks + 1, staffNumber.getCityCode ()); st.setInt (indeks + 2, staffNumber.getNumber ()); }}

Napokon, možemo izjaviti svoj običaj PhoneNumberType u našem Zaposlenik u uredu klasa entiteta:

@Entity @Table (name = "OfficeE Employee") javna klasa OfficeE Employee {@Columns (stupci = {@Column (name = "country_code"), @Column (name = "city_code"), @Column (name = "number") }) @Type (type = "com.baeldung.hibernate.customtypes.PhoneNumberType") private PhoneNumber workerNumber; // ostala polja i metode}

4.3. Provedba CompositeUserType

Provedba Tip korisnika dobro radi za izravne tipove. Međutim, mapiranje složenih tipova Java (s zbirkama i kaskadnim kompozitnim vrstama) treba više sofisticiranosti. Hibernate nam omogućuje mapiranje takvih vrsta primjenom CompositeUserType sučelje.

Pa, pogledajmo to na djelu primjenom Vrsta adrese za Zaposlenik u uredu entitet koji smo ranije koristili:

javna klasa AddressType implementira CompositeUserType {@Override public String [] getPropertyNames () {return new String [] {"addressLine1", "addressLine2", "city", "country", "zipcode"}; } @Override public Type [] getPropertyTypes () {return new Type [] {StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, IntegerType.INSTANCE}; } // ostale metode}

Protivno Vrste korisnika, koji preslikava indeks svojstava tipa, CompositeType mape imena naših svojstava Adresa razred. Što je još važnije, getPropertyType metoda vraća tipove mapiranja za svako svojstvo.

Uz to, također trebamo implementirati getPropertyValue i setPropertyValue metode za mapiranje PreparedStatement i Postavi rezultat indeksira za upisivanje svojstva. Kao primjer, razmotrite getPropertyValue za naše Vrsta adrese:

@Override javni objekt Objekt getPropertyValue (komponenta objekta, svojstvo int) baca komponentu HibernateException {Adresa empAdd = (adresa); prekidač (svojstvo) {slučaj 0: povratak empAdd.getAddressLine1 (); slučaj 1: povratak empAdd.getAddressLine2 (); slučaj 2: povratak empAdd.getCity (); slučaj 3: povratak empAdd.getCountry (); slučaj 4: povratak Integer.valueOf (empAdd.getZipCode ()); } baciti novi IllegalArgumentException (svojstvo + "nevažeći je indeks svojstva za vrstu klase" + component.getClass (). getName ()); }

Konačno, trebali bismo provesti nullSafeGet i nullSafeSet metode za pretvorbu između Java i SQL tipova. Ovo je slično onome što smo radili ranije u našem PhoneNumberType.

Imajte na umu da CompositeTypeOpćenito se primjenjuju kao alternativni mehanizam mapiranja Ugradivo vrste.

4.4. Parametriranje tipa

Osim stvaranja prilagođenih vrsta, Hibernate također nam omogućuje da izmijenimo ponašanje tipova na temelju parametara.

Na primjer, pretpostavimo da trebamo pohraniti Plaća za naše Zaposlenik u uredu. Što je još važnije, aplikacija mora pretvoriti iznos plaćeu geografski iznos lokalne valute.

Dakle, primijenimo naš parametar Tip plaće koja prihvaća valuta kao parametar:

javna klasa SalaryType implementira CompositeUserType, DynamicParameterizedType {private String localCurrency; @Override public void setParameterValues ​​(parametri svojstava) {this.localCurrency = parameters.getProperty ("currency"); } // druge implementacije metoda iz CompositeUserType}

Imajte na umu da smo preskočili CompositeUserType metode iz našeg primjera usredotočiti se na parametrizaciju. Ovdje smo jednostavno implementirali Hibernate DynamicParameterizedType, i poništiti setParameterValues ​​() metoda. Sada, Tip plaće prihvatiti a valuta parametar i pretvorit će bilo koji iznos prije spremanja.

Proći ćemo valuta kao parametar dok se deklarira Plaća:

@Entity @Table (name = "OfficeEfficiee") javna klasa OfficeEfficiee {@Type (type = "com.baeldung.hibernate.customtypes.SalaryType", parametri = {@Parameter (name = "currency", value = "USD") }) @Kolumne (stupci = {@Kolona (ime = "iznos"), @Kolona (ime = "valuta")}) privatna plaća; // ostala polja i metode}

5. Registar osnovnog tipa

Hibernate održava mapiranje svih ugrađenih osnovnih tipova u BasicTypeRegistry. Dakle, eliminirajući potrebu za bilježenjem podataka o mapiranju za takve tipove.

Uz to, Hibernate nam omogućuje da registriramo prilagođene tipove, baš kao i osnovne tipove, u BasicTypeRegistry. Uobičajeno, aplikacije bi registrirale prilagođeni tip tijekom pokretanja SjednicaTvornica. Razumijemo to registracijom LocalDateString tip koji smo ranije implementirali:

privatni statički SessionFactory makeSessionFactory () {ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder () .applySettings (getProperties ()). build (); MetadataSources metadataSources = novi MetadataSources (serviceRegistry); Metapodaci metapodaci = metadataSources.getMetadataBuilder () .applyBasicType (LocalDateStringType.INSTANCE) .build (); vrati metadata.getSessionFactoryBuilder (). build ()} privatna statička svojstva getProperties () {// vrati svojstva hibernacije}

Tako, uklanja ograničenje upotrebe potpuno kvalificiranog naziva klase u mapiranju tipova:

@Entity @Table (name = "OfficeEfficiee") javna klasa OfficeEfficiee {@Column @Type (type = "LocalDateString") private LocalDate dateOfJoining; // ostale metode}

Ovdje, LocalDateString je ključ za koji LocalDateStringType je mapiran.

Alternativno, definiranjem možemo preskočiti registraciju tipa TypeDefs:

@TypeDef (name = "PhoneNumber", typeClass = PhoneNumberType.class, defaultForType = PhoneNumber.class) @Entity @Table (name = "OfficeE Employee") javna klasa OfficeE Employee {@Columns (columns = {@Column (name = "country_code" ), @Column (name = "city_code"), @Column (name = "number")}) private PhoneNumber workerNumber; // ostale metode}

6. Zaključak

U ovom uputstvu raspravljali smo o više pristupa za definiranje prilagođenog tipa u hibernaciji. Dodatno, implementirali smo nekoliko prilagođenih tipova za našu klasu entiteta na temelju nekih uobičajenih slučajeva upotrebe kada novi prilagođeni tip može dobro doći.

Kao i uvijek, uzorci koda dostupni su na GitHubu.