Dynamische mapping met slaapstand

1. Inleiding

In dit artikel zullen we enkele dynamische toewijzingsmogelijkheden van Hibernate verkennen met de @Formule, @Waar, @Filter en @Ieder annotaties.

Merk op dat hoewel Hibernate de JPA-specificatie implementeert, annotaties die hier worden beschreven alleen beschikbaar zijn in Hibernate en niet direct kunnen worden overgedragen naar andere JPA-implementaties.

2. Projectconfiguratie

Om de functies te demonstreren, hebben we alleen de slaapstand-kernbibliotheek en een ondersteunende H2-database nodig:

 org.hibernate hibernate-core 5.4.12.Finale com.h2database h2 1.4.194 

Voor de huidige versie van het slaapstand-core bibliotheek, ga naar Maven Central.

3. Berekende kolommen met @Formule

Stel dat we de waarde van een entiteitsveld willen berekenen op basis van enkele andere eigenschappen. Een manier om dit te doen is door een berekend alleen-lezen veld te definiëren in onze Java-entiteit:

@Entity public class Werknemer implementeert Serializable {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) privé Integer-id; particulier lang bruto-inkomen; particuliere int taxInPercents; openbaar lang getTaxJavaWay () {return grossIncome * taxInPercents / 100; }}

Het voor de hand liggende nadeel is dat we zouden de herberekening moeten doen elke keer dat we toegang krijgen tot dit virtuele veld door de getter.

Het zou veel gemakkelijker zijn om de reeds berekende waarde uit de database te halen. Dit kan gedaan worden met de @Formule annotatie:

@Entity public class Werknemer implementeert Serializable {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) privé Integer-id; particulier lang bruto-inkomen; particuliere int taxInPercents; @Formula ("grossIncome * taxInPercents / 100") privé lange belasting; }

Met @Formulekunnen we subquery's gebruiken, systeemeigen databasefuncties en opgeslagen procedures aanroepen en in feite alles doen dat de syntaxis van een SQL-selectieclausule voor dit veld niet verbreekt.

Hibernate is slim genoeg om de SQL die we hebben verstrekt te ontleden en de juiste tabel- en veldaliassen in te voegen. Het voorbehoud om op te letten is dat aangezien de waarde van de annotatie ruwe SQL is, dit onze mappingdatabase afhankelijk kan maken.

Houd daar ook rekening mee de waarde wordt berekend wanneer de entiteit wordt opgehaald uit de database. Daarom, wanneer we de entiteit voortzetten of bijwerken, wordt de waarde niet opnieuw berekend totdat de entiteit uit de context wordt verwijderd en opnieuw wordt geladen:

Medewerker werknemer = nieuwe werknemer (10_000L, 25); session.save (medewerker); sessie.flush (); sessie.clear (); werknemer = session.get (Employee.class, employee.getId ()); assertThat (employee.getTax ()). isEqualTo (2_500L);

4. Entiteiten filteren met @Waar

Stel dat we een aanvullende voorwaarde aan de query willen geven wanneer we een entiteit vragen.

We moeten bijvoorbeeld "zacht verwijderen" implementeren. Dit betekent dat de entiteit nooit uit de database wordt verwijderd, maar alleen gemarkeerd als verwijderd met een boolean veld.

We zouden heel voorzichtig moeten zijn met alle bestaande en toekomstige vragen in de applicatie. We zouden deze aanvullende voorwaarde aan elke vraag moeten geven. Gelukkig biedt Hibernate een manier om dit op één plek te doen:

@Entity @Where (clausule = "verwijderd = false") openbare klasse Werknemer implementeert Serializable {// ...}

De @Waar annotatie op een methode bevat een SQL-clausule die wordt toegevoegd aan elke query of subquery van deze entiteit:

employee.setDeleted (true); sessie.flush (); sessie.clear (); werknemer = session.find (Employee.class, employee.getId ()); assertThat (werknemer) .isNull ();

Zoals in het geval van @Formule annotatie, aangezien we te maken hebben met onbewerkte SQL, de @Waar voorwaarde wordt niet opnieuw geëvalueerd totdat we de entiteit naar de database spoelen en deze uit de context verwijderen.

Tot die tijd blijft de entiteit in de context en is deze toegankelijk met zoekopdrachten en zoekopdrachten van ID kaart.

De @Waar annotatie kan ook worden gebruikt voor een verzamelingsveld. Stel dat we een lijst hebben met verwijderbare telefoons:

@Entity openbare klasse Telefoon implementeert Serializable {@Id @GeneratedValue (strategie = GenerationType.IDENTITY) privé Integer-id; private boolean verwijderd; privé String-nummer; }

Dan, van de Werknemer kant kunnen we een verzameling verwijderbare telefoons als volgt:

public class Medewerker implementeert Serializable {// ... @OneToMany @JoinColumn (name = "employee_id") @Where (clausule = "verwijderd = false") privé Set phones = new HashSet (0); }

Het verschil is dat de Employee.phones verzameling zou altijd worden gefilterd, maar we konden nog steeds alle telefoons krijgen, inclusief verwijderde, via directe zoekopdracht:

employee.getPhones (). iterator (). next (). setDeleted (true); sessie.flush (); sessie.clear (); werknemer = session.find (Employee.class, employee.getId ()); assertThat (employee.getPhones ()). hasSize (1); Lijst fullPhoneList = session.createQuery ("van telefoon"). GetResultList (); assertThat (fullPhoneList) .hasSize (2);

5. Geparametriseerd filteren met @Filter

Het probleem met @Waar annotatie is dat het ons toestaat om alleen een statische query op te geven zonder parameters, en het kan niet worden uitgeschakeld of ingeschakeld op verzoek.

De @Filter annotatie werkt op dezelfde manier als @Waar, maar het kan ook worden in- of uitgeschakeld op sessieniveau, en ook geparametriseerd.

5.1. Het definiëren van de @Filter

Om te laten zien hoe @Filter werken, laten we eerst de volgende filterdefinitie toevoegen aan het Werknemer entiteit:

@FilterDef (name = "incomeLevelFilter", parameters = @ParamDef (name = "incomeLimit", type = "int")) @Filter (name = "incomeLevelFilter", condition = "grossIncome>: incomeLimit") openbare klasse Werknemer implementeert Serializable {

De @FilterDef annotatie definieert de filternaam en een set van zijn parameters die zullen deelnemen aan de query. Het type parameter is de naam van een van de Hibernate-typen (Type, UserType of CompositeUserType), in ons geval een int.

Het @FilterDef annotatie kan op het type of op pakketniveau worden geplaatst. Merk op dat het de filtervoorwaarde zelf niet specificeert (hoewel we de defaultCondition parameter).

Dit betekent dat we het filter (de naam en de set parameters) op één plaats kunnen definiëren en vervolgens de voorwaarden voor het filter op meerdere andere plaatsen anders kunnen definiëren.

Dit kan gedaan worden met de @Filter annotatie. In ons geval hebben we het voor de eenvoud in dezelfde klasse geplaatst. De syntaxis van de voorwaarde is een onbewerkte SQL met parameternamen voorafgegaan door dubbele punten.

5.2. Toegang tot gefilterde entiteiten

Een ander verschil van @Filter van @Waar is dat @Filter is standaard niet ingeschakeld. We moeten het handmatig op sessieniveau inschakelen en de parameterwaarden ervoor opgeven:

session.enableFilter ("incomeLevelFilter") .setParameter ("incomeLimit", 11_000);

Stel nu dat we de volgende drie werknemers in de database hebben:

session.save (nieuwe medewerker (10_000, 25)); session.save (nieuwe medewerker (12_000, 25)); session.save (nieuwe medewerker (15_000, 25));

Met het filter ingeschakeld, zoals hierboven weergegeven, zullen er slechts twee zichtbaar zijn door te vragen:

Lijst werknemers = session.createQuery ("van werknemer") .getResultList (); assertThat (werknemers) .hasSize (2);

Merk op dat zowel het ingeschakelde filter als de bijbehorende parameterwaarden alleen binnen de huidige sessie worden toegepast. In een nieuwe sessie zonder filter ingeschakeld, zien we alle drie de medewerkers:

session = HibernateUtil.getSessionFactory (). openSession (); werknemers = session.createQuery ("van werknemer"). getResultList (); assertThat (werknemers) .hasSize (3);

Wanneer de entiteit rechtstreeks op ID wordt opgehaald, wordt het filter ook niet toegepast:

Medewerker medewerker = session.get (Employee.class, 1); assertThat (employee.getGrossIncome ()). isEqualTo (10_000);

5.3. @Filter en caching op het tweede niveau

Als we een applicatie met hoge belasting hebben, willen we zeker de Hibernate-cache op het tweede niveau inschakelen, wat een enorm prestatievoordeel kan zijn. Daar moeten we rekening mee houden de @Filter annotatie speelt niet goed met caching.

De cache op het tweede niveau houdt alleen volledige ongefilterde collecties bij. Als dit niet het geval was, konden we een verzameling in de ene sessie lezen met filter ingeschakeld, en vervolgens dezelfde gefilterde verzameling in de cache krijgen in een andere sessie, zelfs als het filter uitgeschakeld was.

Dit is waarom de @Filter annotatie schakelt in feite caching voor de entiteit uit.

6. In kaart brengen van een entiteitsreferentie met @Ieder

Soms willen we een verwijzing naar een van meerdere entiteitstypen toewijzen, zelfs als ze niet op een enkele zijn gebaseerd @Map_Superclass. Ze kunnen zelfs worden toegewezen aan verschillende niet-gerelateerde tabellen. We kunnen dit bereiken met de @Ieder annotatie.

In ons voorbeeld we zullen een beschrijving moeten toevoegen aan elke entiteit in onze persistentie-eenheid, namelijk, Werknemer en Telefoon. Het zou onredelijk zijn om alle entiteiten van een enkele abstracte superklasse te erven om dit te doen.

6.1. Relatie met in kaart brengen @Ieder

Hier leest u hoe we een verwijzing kunnen definiëren naar elke entiteit die implementeert Serialiseerbaar (d.w.z. naar eender welke entiteit):

@Entity openbare klasse EntityDescription implementeert Serializable {private String description; @Any (metaDef = "EntityDescriptionMetaDef", metaColumn = @Column (name = "entity_type")) @JoinColumn (name = "entity_id") privé Serialiseerbare entiteit; }

De metaDef eigenschap is de naam van de definitie, en metaColumn is de naam van de kolom die zal worden gebruikt om het entiteitstype te onderscheiden (vergelijkbaar met de discriminatorkolom in de hiërarchietoewijzing met enkele tabel).

We specificeren ook de kolom die verwijst naar het ID kaart van de entiteit. Het is vermeldenswaard dat deze kolom zal geen externe sleutel zijn omdat het naar elke gewenste tabel kan verwijzen.

De entiteit_id kolom kan over het algemeen ook niet uniek zijn omdat verschillende tabellen herhaalde ID's kunnen hebben.

De entiteitstype/entiteit_id pair moet echter uniek zijn, omdat het op unieke wijze de entiteit beschrijft waarnaar we verwijzen.

6.2. Het definiëren van de @Ieder In kaart brengen met @BuienRadarNL

Op dit moment weet Hibernate niet hoe verschillende entiteitstypen te onderscheiden, omdat we niet hebben gespecificeerd wat de entiteitstype kolom zou kunnen bevatten.

Om dit te laten werken, moeten we de meta-definitie van de mapping toevoegen met de @BuienRadarNL annotatie. De beste plaats om het te plaatsen is het pakketniveau, zodat we het in andere mappings kunnen hergebruiken.

Hier is hoe de package-info.java bestand met de @BuienRadarNL annotatie zou er als volgt uitzien:

@AnyMetaDef (name = "EntityDescriptionMetaDef", metaType = "string", idType = "int", metaValues ​​= {@MetaValue (value = "Employee", targetEntity = Employee.class), @MetaValue (value = "Phone", targetEntity = Phone.class)}) pakket com.baeldung.hibernate.pojo;

Hier hebben we het type van de entiteitstype kolom (draad), het type van de entiteit_id kolom (int), de acceptabele waarden in de entiteitstype kolom ("Werknemer" en "Telefoon") en de bijbehorende entiteitstypen.

Stel nu dat we een werknemer hebben met twee telefoons die als volgt worden beschreven:

Medewerker medewerker = nieuwe medewerker (); Phone phone1 = nieuwe telefoon ("555-45-67"); Phone phone2 = nieuwe telefoon ("555-89-01"); employee.getPhones (). add (phone1); employee.getPhones (). add (phone2);

Nu kunnen we beschrijvende metadata toevoegen aan alle drie de entiteiten, ook al hebben ze verschillende niet-gerelateerde typen:

EntityDescription employeeDescription = nieuwe EntityDescription ("Stuur volgend jaar naar conferentie", medewerker); EntityDescription phone1Description = nieuwe EntityDescription ("Thuistelefoon (niet bellen na 22.00 uur)", phone1); EntityDescription phone2Description = nieuwe EntityDescription ("Telefoon werk", phone1);

7. Conclusie

In dit artikel hebben we enkele van de annotaties van Hibernate onderzocht die het mogelijk maken entiteitstoewijzing te verfijnen met behulp van onbewerkte SQL.

De broncode voor het artikel is beschikbaar op GitHub.