Vlastné typy v režime dlhodobého spánku a anotácia @Type

1. Prehľad

Hibernácia zjednodušuje manipuláciu s údajmi medzi SQL a JDBC mapovaním objektovo orientovaného modelu v Jave s relačným modelom v databázach. Hoci mapovanie základných tried Java je zabudované v režime dlhodobého spánku, mapovanie vlastných typov je často zložité.

V tomto tutoriále uvidíme, ako nám režim dlhodobého spánku umožňuje rozšíriť základné mapovanie typov na vlastné triedy Java. Okrem toho uvidíme tiež niekoľko bežných príkladov vlastných typov a implementujeme ich pomocou mechanizmu mapovania typov Hibernate.

2. Typy režimu dlhodobého spánku

Hibernate používa typy mapovania na prevod objektov Java na dotazy SQL na ukladanie údajov. Podobne používa typy mapovania na prevod SQL ResultSet na objekty Java pri načítaní údajov.

Režim dlhodobého spánku všeobecne kategorizuje typy na typy entít a typy hodnôt. Konkrétne sa typy entít používajú na mapovanie doménových entít špecifických pre doménu, a preto existujú nezávisle od iných typov v aplikácii. Naopak, typy hodnôt sa namiesto toho používajú na mapovanie dátových objektov a sú takmer vždy vo vlastníctve entít.

V tomto tutoriáli sa zameriame na mapovanie typov hodnôt, ktoré sa ďalej delia na:

  • Základné typy - mapovanie základných typov Java
  • Vložené - mapovanie pre zložené typy Java / POJO
  • Zbierky - mapovanie pre kolekciu základného a zloženého typu Java

3. Závislosti Maven

Ak chcete vytvoriť naše vlastné typy režimu dlhodobého spánku, budeme potrebovať závislosť režimu dlhodobého spánku:

 org.hibernate hibernate-core 5.3.6.Final 

4. Vlastné typy v režime dlhodobého spánku

Pre väčšinu používateľských domén môžeme použiť základné typy mapovania Hibernate. Existuje však veľa prípadov použitia, keď musíme implementovať vlastný typ.

Režim dlhodobého spánku pomerne uľahčuje implementáciu vlastných typov. Existujú tri prístupy k implementácii vlastného typu v režime dlhodobého spánku. Poďme si každý z nich podrobne rozobrať.

4.1. Implementácia BasicType

Implementáciou Hibernate môžeme vytvoriť vlastný základný typ BasicType alebo jedna z jeho konkrétnych implementácií, AbstractSingleColumnStandardBasicType.

Pred implementáciou nášho prvého vlastného typu si pozrime bežný prípad použitia na implementáciu základného typu. Predpokladajme, že musíme pracovať so staršou databázou, ktorá uchováva dátumy ako VARCHAR. Za normálnych okolností Hibernácia by to mapovala na String Typ Java. Tým sa vývojárom aplikácií sťažuje overovanie dátumu.

Poďme teda implementovať naše LocalDateString typu, ktorý ukladá LocalDate Typ Java ako VARCHAR:

public class LocalDateStringType extends AbstractSingleColumnStandardBasicType {public static final LocalDateStringType INSTANCE = new LocalDateStringType (); public LocalDateStringType () {super (VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE); } @Override public String getName () {return "LocalDateString"; }}

Najdôležitejšie v tomto kóde sú parametre konštruktora. Po prvé, je to inštancia SqlTypeDescriptor, čo je reprezentácia typu Hibernate SQL, ktorá je pre náš príklad VARCHAR. A druhý argument je inštanciou JavaTypeDescriptor čo predstavuje typ Java.

Teraz môžeme realizovať a LocalDateStringJavaDescriptor na ukladanie a vyberanie LocalDate ako VARCHAR:

verejná trieda LocalDateStringJavaDescriptor rozširuje AbstractTypeDescriptor {public static final LocalDateStringJavaDescriptor INSTANCE = nový LocalDateStringJavaDescriptor (); public LocalDateStringJavaDescriptor () {super (LocalDate.class, ImmutableMutabilityPlan.INSTANCE); } // ďalšie metódy}

Ďalej musíme prepísať obal a rozbaliť metódy prevodu typu Java na SQL. Začnime s rozbaliť:

@Override public X unwrap (hodnota LocalDate, typ triedy, možnosti WrapperOptions) {if (value == null) return null; if (String.class.isAssignableFrom (type)) return (X) LocalDateType.FORMATTER.format (hodnota); hádzať neznámyUbaliť (typ); }

Ďalej obal metóda:

@Override public LocalDate wrap (hodnota X, možnosti WrapperOptions) {if (value == null) return null; if (String.class.isInstance (value)) return LocalDate.from (LocalDateType.FORMATTER.parse ((CharSequence) value)); hod neznámym zabalením (value.getClass ()); }

rozbaliť () sa volá počas Pripravené vyhlásenie väzba previesť LocalDate na typ String, ktorý je namapovaný na VARCHAR. Podobne, obal() sa volá počas Sada výsledkov vyhľadávanie previesť String na Javu LocalDate.

Nakoniec môžeme náš vlastný typ použiť v našej triede entít:

@Entity @Table (name = "OfficeEmployee") verejná trieda OfficeEmployee {@Column @Type (type = "com.baeldung.hibernate.customtypes.LocalDateStringType") súkromné ​​LocalDate dateOfJoining; // ďalšie polia a metódy}

Neskôr uvidíme, ako môžeme tento typ zaregistrovať v režime dlhodobého spánku. A vo výsledku odkazujte na tento typ pomocou registračného kľúča namiesto úplného názvu triedy.

4.2. Implementácia UserType

Pri rôznych základných typoch v režime dlhodobého spánku je veľmi zriedkavé, že musíme implementovať vlastný základný typ. Oproti tomu typickejším prípadom použitia je mapovanie zložitého objektu domény Java do databázy. Takéto doménové objekty sú všeobecne uložené vo viacerých databázových stĺpcoch.

Poďme teda implementovať komplex Telefónne číslo objekt implementáciou UserType:

verejná trieda PhoneNumberType implementuje UserType {@Override public int [] sqlTypes () {return new int [] {Types.INTEGER, Types.INTEGER, Types.INTEGER}; } @Override verejná trieda returnsClass () {return PhoneNumber.class; } // ďalšie metódy} 

Tu, prepísané sqlTypes metóda vráti typy polí SQL v rovnakom poradí, v akom sú deklarované v našom Telefónne číslo trieda. Podobne vrátilClass metóda vráti našu Telefónne číslo Typ Java.

Jedinou vecou, ​​ktorú musíte urobiť, je implementovať metódy na prevod medzi typom Java a typom SQL, ako sme to urobili pre náš BasicType.

Po prvé, nullSafeGet metóda:

@Override public Object nullSafeGet (ResultSet rs, názvy reťazcov [], relácia SharedSessionContractImplementor, vlastník objektu) hodí HibernateException, SQLException {int countryCode = rs.getInt (names [0]); if (rs.wasNull ()) return null; int cityCode = rs.getInt (názvy [1]); int číslo = rs.getInt (názvy [2]); PhoneNumber employeeNumber = nové PhoneNumber (countryCode, cityCode, number); vrátiť zamestnancaČíslo; }

Ďalej nullSafeSet metóda:

@Override public void nullSafeSet (PreparedStatement st, hodnota objektu, int index, relácia SharedSessionContractImplementor) hodí HibernateException, SQLException {if (Objects.isNull (hodnota)) {st.setNull (index, Types.INTEGER); st.setNull (index + 1, Typ.INTEGER); st.setNull (index + 2, typy.INTEGER); } else {PhoneNumber employeeNumber = (PhoneNumber) hodnota; st.setInt (index, employeeNumber.getCountryCode ()); st.setInt (index + 1, employeeNumber.getCityCode ()); st.setInt (index + 2, employeeNumber.getNumber ()); }}

Na záver môžeme vyhlásiť náš zvyk PhoneNumberType v našom Zamestnanec trieda entity:

@Entity @Table (name = "OfficeEmployee") verejná trieda OfficeEmployee {@Columns (stĺpce = {@Column (name = "country_code"), @Column (name = "city_code"), @Column (name = "number")) }) @Type (type = "com.baeldung.hibernate.customtypes.PhoneNumberType") súkromné ​​telefónne číslo employeeNumber; // ďalšie polia a metódy}

4.3. Implementácia CompositeUserType

Implementácia UserType funguje dobre pre priame typy. Mapovanie zložitých typov Javy (s kolekciami a kaskádovými zloženými typmi) však vyžaduje viac prepracovanosti. Hibernate nám umožňuje mapovať tieto typy implementáciou CompositeUserType rozhranie.

Uvidíme teda toto v akcii implementáciou Typ adresy pre Zamestnanec entita, ktorú sme použili skôr:

verejná trieda AddressType implementuje CompositeUserType {@Override public String [] getPropertyNames () {vrátiť nový reťazec [] {"addressLine1", "addressLine2", "city", "country", "zipcode"}; } @Override public Type [] getPropertyTypes () {návrat nového typu [] {StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, IntegerType.INSTANCE}; } // ďalšie metódy}

V rozpore s UserTypes, ktorý mapuje index vlastností typu, CompositeType mapuje názvy našich majetkov Adresa trieda. Dolezitejsie, typ getPropertyType metóda vráti typy mapovania pre každú vlastnosť.

Ďalej musíme tiež implementovať getPropertyValue a setPropertyValue metódy mapovania Pripravené vyhlásenie a Sada výsledkov indexuje vlastnosť typu. Ako príklad zvážte getPropertyValue pre naše Typ adresy:

@ Verzia Public Object getPropertyValue (komponent Object, vlastnosť int) hodí komponent HibernateException {Address empAdd = (Address); switch (property) {case 0: return empAdd.getAddressLine1 (); prípad 1: návrat empAdd.getAddressLine2 (); prípad 2: návrat empAdd.getCity (); prípad 3: návrat empAdd.getCountry (); prípad 4: návrat Integer.valueOf (empAdd.getZipCode ()); } hodiť novú IllegalArgumentException (property + "je neplatný index vlastnosti pre typ triedy" + component.getClass (). getName ()); }

Nakoniec by sme to museli implementovať nullSafeGet a nullSafeSet metódy prevodu medzi typmi Java a SQL. Je to podobné tomu, čo sme robili predtým v našom PhoneNumberType.

Vezmite prosím na vedomie, že CompositeTypeVšeobecne sa implementujú ako alternatívny mechanizmus mapovania k Vložiteľné typy.

4.4. Parametrizácia typu

Okrem vytvárania vlastných typov Režim dlhodobého spánku nám tiež umožňuje meniť správanie typov na základe parametrov.

Predpokladajme napríklad, že musíme uložiť Plat pre naše Zamestnanec. Dôležitejšie je, že aplikácia musí previesť výšku platudo geografickej sumy v miestnej mene.

Poďme teda implementovať naše parametrizované Typ platu ktorý prijíma mena ako parameter:

verejná trieda SalaryType implementuje CompositeUserType, DynamicParameterizedType {private String localCurrency; @Override public void setParameterValues ​​(vlastnosti) {this.localCurrency = parametre.getProperty ("mena"); } // implementácie iných metód z CompositeUserType}

Upozorňujeme, že sme preskočili súbor CompositeUserType metódy z nášho príkladu zamerané na parametrizáciu. Tu sme jednoducho implementovali režim dlhodobého spánku DynamicParameterizedTypea prepísať setParameterValues ​​() metóda. Teraz Typ platu prijať a mena parameter a pred uložením prevedie ľubovoľnú sumu.

Prejdeme okolo mena ako parameter pri deklarovaní Plat:

@Entity @Table (name = "OfficeEmployee") verejná trieda OfficeEmployee {@Type (type = "com.baeldung.hibernate.customtypes.SalaryType", parametre = {@Parameter (name = "mena", hodnota = "USD") }) @Columns (stĺpce = {@Column (name = "suma"), @Column (name = "mena")}) súkromné ​​Mzdový plat; // ďalšie polia a metódy}

5. Register základného typu

Režim dlhodobého spánku udržuje mapovanie všetkých zabudovaných základných typov v systéme Windows BasicTypeRegistry. Tým pádom odpadá potreba anotovať mapovacie informácie pre tieto typy.

Hibernate nám navyše umožňuje registrovať vlastné typy, rovnako ako základné typy, v BasicTypeRegistry. Za normálnych okolností by aplikácie registrovali vlastný typ pri zavádzaní systému Windows SessionFactory. Poďme to pochopiť zaregistrovaním LocalDateString typ, ktorý sme implementovali skôr:

private static SessionFactory makeSessionFactory () {ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder () .applySettings (getProperties ()). build (); MetadataSources metadataSources = nové MetadataSources (serviceRegistry); Metadata metadata = metadataSources.getMetadataBuilder () .applyBasicType (LocalDateStringType.INSTANCE) .build (); vrátiť metadata.getSessionFactoryBuilder (). build ()} súkromné ​​statické vlastnosti getProperties () {// vrátiť vlastnosti dlhodobého spánku}

Teda ruší to obmedzenie používania plne kvalifikovaného názvu triedy v Mapovaní typov:

@Entity @Table (name = "OfficeEmployee") verejná trieda OfficeEmployee {@Column @Type (type = "LocalDateString") private LocalDate dateOfJoining; // ďalšie metódy}

Tu, LocalDateString je kľúč, ku ktorému LocalDateStringType je zmapovaný.

Prípadne môžeme definíciu preskočiť registráciu typu TypeDefs:

@TypeDef (name = "PhoneNumber", typeClass = PhoneNumberType.class, defaultForType = PhoneNumber.class) @Entity @Table (name = "OfficeEmployee") verejná trieda OfficeEmployee {@Columns (stĺpce = {@Column (name = "country_code") ), @Column (name = "city_code"), @Column (name = "number")}) private PhoneNumber employeeNumber; // ďalšie metódy}

6. Záver

V tomto tutoriáli sme diskutovali o viacerých prístupoch k definovaniu vlastného typu v režime dlhodobého spánku. Navyše, implementovali sme niekoľko vlastných typov pre našu triedu entít na základe niektorých bežných prípadov použitia, kedy môže prísť nový vlastný typ vhod.

Ako vždy sú vzorky kódu k dispozícii na GitHub.