Aangepaste typen in slaapstand en de @Type-annotatie

1. Overzicht

Hibernate vereenvoudigt gegevensverwerking tussen SQL en JDBC door het objectgeoriënteerde model in Java in kaart te brengen met het relationele model in databases. Hoewel Het in kaart brengen van elementaire Java-klassen is ingebouwd in Hibernate, het in kaart brengen van aangepaste typen is vaak complex.

In deze tutorial zullen we zien hoe Hibernate ons in staat stelt om de basistypetoewijzing uit te breiden naar aangepaste Java-klassen. Daarnaast zullen we ook enkele veelvoorkomende voorbeelden van aangepaste typen zien en deze implementeren met behulp van het typetoewijzingsmechanisme van Hibernate.

2. Toewijzingstypen in slaapstand

Hibernate gebruikt toewijzingstypen om Java-objecten om te zetten in SQL-query's voor het opslaan van gegevens. Evenzo gebruikt het toewijzingstypen voor het converteren van SQL ResultSet naar Java-objecten tijdens het ophalen van gegevens.

Over het algemeen categoriseert Hibernate de typen in entiteitstypen en waardetypes. Entiteitstypen worden specifiek gebruikt om domeinspecifieke Java-entiteiten in kaart te brengen en bestaan ​​daarom onafhankelijk van andere typen in de toepassing. Waardetypes worden daarentegen gebruikt om gegevensobjecten in kaart te brengen en zijn bijna altijd eigendom van de entiteiten.

In deze tutorial zullen we ons concentreren op het in kaart brengen van waardetypes die verder worden onderverdeeld in:

  • Basistypen - Mapping voor basistypen Java
  • Embeddable - Mapping voor samengestelde java-typen / POJO's
  • Verzamelingen - Toewijzing voor een verzameling basis- en samengestelde java-typen

3. Maven afhankelijkheden

Om onze aangepaste Hibernate-typen te maken, hebben we de hibernate-core-afhankelijkheid nodig:

 org.hibernate hibernate-core 5.3.6.Final 

4. Aangepaste typen in slaapstand

We kunnen Hibernate-basistoewijzingstypen gebruiken voor de meeste gebruikersdomeinen. Er zijn echter veel gevallen waarin we een aangepast type moeten implementeren.

Hibernate maakt het relatief eenvoudiger om aangepaste typen te implementeren. Er zijn drie manieren om een ​​aangepast type in Slaapstand te implementeren. Laten we ze allemaal in detail bespreken.

4.1. Implementeren Basistype

We kunnen een aangepast basistype maken door Hibernate's te implementeren Basistype of een van zijn specifieke implementaties, AbstractSingleColumnStandardBasicType.

Laten we, voordat we ons eerste aangepaste type implementeren, een algemeen gebruik bekijken voor het implementeren van een basistype. Stel dat we moeten werken met een oude database, die datums opslaat als VARCHAR. Normaal gesproken, Hibernate zou dit toewijzen aan Draad Java-type. Daardoor wordt het valideren van datums moeilijker voor applicatieontwikkelaars.

Dus laten we onze implementeren LocalDateString type, dat opslaat LocalDate Java-type als VARCHAR:

openbare klasse LocalDateStringType breidt AbstractSingleColumnStandardBasicType {openbare statische laatste LocalDateStringType INSTANCE = nieuwe LocalDateStringType (); openbare LocalDateStringType () {super (VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE); } @Override public String getName () {retourneer "LocalDateString"; }}

Het belangrijkste in deze code zijn de constructorparameters. Ten eerste is er een voorbeeld van SqlTypeDescriptor, wat de SQL-representatie van Hibernate is, wat in ons voorbeeld VARCHAR is. En het tweede argument is een voorbeeld van JavaTypeDescriptor wat staat voor het Java-type.

Nu kunnen we implementeren een LocalDateStringJavaDescriptor voor het opslaan en ophalen LocalDate als VARCHAR:

openbare klasse LocalDateStringJavaDescriptor breidt AbstractTypeDescriptor uit {openbare statische laatste LocalDateStringJavaDescriptor INSTANCE = nieuwe LocalDateStringJavaDescriptor (); openbare LocalDateStringJavaDescriptor () {super (LocalDate.class, ImmutableMutabilityPlan.INSTANCE); } // andere methodes }

Vervolgens moeten we overschrijven wikkelen en uitpakken methoden voor het converteren van het Java-type naar SQL. Laten we beginnen met de uitpakken:

@Override openbare X uitpakken (LocalDate-waarde, Class-type, WrapperOptions-opties) {if (value == null) retourneert null; if (String.class.isAssignableFrom (type)) return (X) LocalDateType.FORMATTER.format (waarde); throw unknownUnwrap (type); }

Vervolgens de wikkelen methode:

@Override public LocalDate wrap (X-waarde, WrapperOptions-opties) {if (value == null) retourneert null; if (String.class.isInstance (waarde)) return LocalDate.from (LocalDateType.FORMATTER.parse ((CharSequence) waarde)); throw unknownWrap (value.getClass ()); }

uitpakken () wordt aangeroepen tijdens PreparedStatement bindend om te zetten LocalDate naar een String-type, dat is toegewezen aan VARCHAR. Hetzelfde, wikkelen() wordt aangeroepen tijdens ResultSet retrieval om te converteren Draad naar een Java LocalDate.

Ten slotte kunnen we ons aangepaste type gebruiken in onze Entity-klasse:

@Entity @Table (naam = "OfficeEmployee") openbare klasse OfficeEmployee {@Column @Type (type = "com.baeldung.hibernate.customtypes.LocalDateStringType") privé LocalDate dateOfJoining; // andere velden en methoden}

Later zullen we zien hoe we dit type in Hibernate kunnen registreren. En als een resultaat, verwijs naar dit type met behulp van de registratiesleutel in plaats van de volledig gekwalificeerde klassenaam.

4.2. Implementeren Gebruikerstype

Met de verscheidenheid aan basistypen in Hibernate is het zeer zeldzaam dat we een aangepast basistype moeten implementeren. Een meer typische use case is daarentegen het toewijzen van een complex Java-domeinobject aan de database. Dergelijke domeinobjecten worden doorgaans opgeslagen in meerdere databasekolommen.

Laten we dus een complex implementeren Telefoonnummer bezwaar maken door te implementeren Gebruikerstype:

openbare klasse PhoneNumberType implementeert UserType {@Override public int [] sqlTypes () {retourneer nieuwe int [] {Types.INTEGER, Types.INTEGER, Types.INTEGER}; } @Override public Class returnClass () {retourneer PhoneNumber.class; } // andere methodes } 

Hier, de overschreven sqlTypes methode retourneert de SQL-typen velden, in dezelfde volgorde als waarin ze zijn gedeclareerd in onze Telefoonnummer klasse. Evenzo returnClass methode retourneert onze Telefoonnummer Java-type.

Het enige dat u hoeft te doen, is de methoden voor het converteren tussen Java-type en SQL-type implementeren, zoals we deden voor onze Basistype.

Eerst de nullSafeGet methode:

@Override openbaar object nullSafeGet (ResultSet rs, String [] namen, SharedSessionContractImplementor-sessie, Objecteigenaar) gooit HibernateException, SQLException {int countryCode = rs.getInt (names [0]); if (rs.wasNull ()) retourneert null; int cityCode = rs.getInt (namen [1]); int nummer = rs.getInt (namen [2]); PhoneNumber employeeNumber = nieuw PhoneNumber (countryCode, cityCode, number); terugkeer employeeNumber; }

Vervolgens de nullSafeSet methode:

@Override public void nullSafeSet (PreparedStatement st, Object waarde, int index, SharedSessionContractImplementor sessie) gooit HibernateException, SQLException {if (Objects.isNull (waarde)) {st.setNull (index, Types.INTEGER); st.setNull (index + 1, Types.INTEGER); st.setNull (index + 2, Types.INTEGER); } else {PhoneNumber employeeNumber = (PhoneNumber) waarde; st.setInt (index, employeeNumber.getCountryCode ()); st.setInt (index + 1, employeeNumber.getCityCode ()); st.setInt (index + 2, employeeNumber.getNumber ()); }}

Eindelijk kunnen we onze gewoonte aangeven PhoneNumberType in onze Kantoormedewerker entiteitsklasse:

@Entity @Table (name = "OfficeEmployee") openbare klasse OfficeEmployee {@Columns (columns = {@Column (name = "country_code"), @Column (name = "city_code"), @Column (name = "number") }) @Type (type = "com.baeldung.hibernate.customtypes.PhoneNumberType") privé PhoneNumber employeeNumber; // andere velden en methoden}

4.3. Implementeren CompositeUserType

Implementeren Gebruikerstype werkt goed voor eenvoudige typen. Het in kaart brengen van complexe Java-typen (met Collections en Cascaded-samengestelde typen) heeft echter meer verfijning nodig. Met Hibernate kunnen we dergelijke typen in kaart brengen door het CompositeUserType koppel.

Laten we dit dus in actie zien door een Adrestype voor de Kantoormedewerker entiteit die we eerder hebben gebruikt:

public class AddressType implementeert CompositeUserType {@Override public String [] getPropertyNames () {retourneer nieuwe String [] {"addressLine1", "addressLine2", "city", "country", "zipcode"}; } @Override public Type [] getPropertyTypes () {retourneer nieuw Type [] {StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, StringType.INSTANCE, IntegerType.INSTANCE}; } // andere methodes }

In tegenstelling tot UserTypes, die de index van de type-eigenschappen in kaart brengt, CompositeType kaarten eigendomsnamen van onze Adres klasse. Belangrijker, het getPropertyType method retourneert de toewijzingstypen voor elke eigenschap.

Bovendien moeten we ook implementeren getPropertyValue en setPropertyValue methoden voor het in kaart brengen PreparedStatement en ResultSet indexen om eigenschap te typen. Beschouw als voorbeeld getPropertyValue voor onze Adres Type:

@Override public Object getPropertyValue (Object component, int property) gooit HibernateException {Address empAdd = (Address) component; switch (eigenschap) {case 0: return empAdd.getAddressLine1 (); geval 1: terugkeer empAdd.getAddressLine2 (); geval 2: terugkeer empAdd.getCity (); geval 3: terugkeer empAdd.getCountry (); geval 4: retourneer Integer.valueOf (empAdd.getZipCode ()); } throw new IllegalArgumentException (eigenschap + "is een ongeldige eigenschappenindex voor klassetype" + component.getClass (). getName ()); }

Ten slotte zouden we moeten implementeren nullSafeGet en nullSafeSet methoden voor conversie tussen Java- en SQL-typen. Dit is vergelijkbaar met wat we eerder in onze PhoneNumberType.

Houd er rekening mee dat CompositeType‘S worden over het algemeen geïmplementeerd als een alternatief mappingmechanisme voor Insluitbaar types.

4.4. Typ parametrering

Naast het maken van aangepaste typen, Hibernate stelt ons ook in staat om het gedrag van typen te wijzigen op basis van parameters.

Stel dat we het Salaris voor onze Kantoormedewerker. Wat nog belangrijker is, de aanvraag moet het salarisbedrag omrekenenin geografisch bedrag in lokale valuta.

Dus laten we onze geparametriseerde Salaristype die accepteert valuta als parameter:

openbare klasse SalaryType implementeert CompositeUserType, DynamicParameterizedType {private String localCurrency; @Override public void setParameterValues ​​(Eigenschappen parameters) {this.localCurrency = parameters.getProperty ("valuta"); } // andere methode-implementaties van CompositeUserType}

Houd er rekening mee dat we de CompositeUserType methoden uit ons voorbeeld om te focussen op parametrisering. Hier hebben we eenvoudig Hibernate's geïmplementeerd DynamicParameterizedType, en negeer de setParameterValues ​​() methode. Nu de Salaristype accepteer een valuta parameter en converteert elk bedrag voordat het wordt opgeslagen.

We passeren de valuta als parameter bij het declareren van de Salaris:

@Entity @Table (name = "OfficeEmployee") openbare klasse OfficeEmployee {@Type (type = "com.baeldung.hibernate.customtypes.SalaryType", parameters = {@Parameter (name = "currency", value = "USD") }) @Columns (kolommen = {@Column (naam = "bedrag"), @Column (naam = "valuta")}) privé salaris salaris; // andere velden en methoden}

5. Basistyperegistratie

Hibernate handhaaft de toewijzing van alle ingebouwde basistypen in het BasicTypeRegistry. Zodoende is het niet meer nodig om kaartinformatie voor dergelijke typen te annoteren.

Bovendien stelt Hibernate ons in staat om aangepaste typen te registreren, net als basistypen, in het BasicTypeRegistry. Normaal gesproken registreren applicaties een aangepast type tijdens het bootstrappen van het SessionFactory. Laten we dit begrijpen door het LocalDateString type dat we eerder hebben geïmplementeerd:

privé statische SessionFactory makeSessionFactory () {ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder () .applySettings (getProperties ()). build (); MetadataSources metadataSources = nieuwe MetadataSources (serviceRegistry); Metadata metadata = metadataSources.getMetadataBuilder () .applyBasicType (LocalDateStringType.INSTANCE) .build (); retourneer metadata.getSessionFactoryBuilder (). build ()} privé statische eigenschappen getProperties () {// retourneer slaapstandeigenschappen}

Dus, het neemt de beperking weg van het gebruik van de volledig gekwalificeerde klassenaam in Type mapping:

@Entity @Table (naam = "OfficeEmployee") openbare klasse OfficeEmployee {@Column @Type (type = "LocalDateString") privé LocalDate dateOfJoining; // andere methodes }

Hier, LocalDateString is de sleutel waartoe de LocalDateStringType is in kaart gebracht.

Als alternatief kunnen we typeregistratie overslaan door te definiëren TypeDefs:

@TypeDef (name = "PhoneNumber", typeClass = PhoneNumberType.class, defaultForType = PhoneNumber.class) @Entity @Table (name = "OfficeEmployee") openbare klasse OfficeEmployee {@Columns (columns = {@Column (name = "country_code" ), @Column (name = "city_code"), @Column (name = "number")}) privé PhoneNumber employeeNumber; // andere methodes }

6. Conclusie

In deze zelfstudie hebben we meerdere benaderingen besproken voor het definiëren van een aangepast type in Hibernate. Bovendien, we hebben een paar aangepaste typen geïmplementeerd voor onze entiteitsklasse op basis van enkele veelvoorkomende gebruiksscenario's waarbij een nieuw aangepast type van pas kan komen.

Zoals altijd zijn de codevoorbeelden beschikbaar op GitHub.