Aanhoudende Enums in JPA

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Inleiding

In JPA versie 2.0 en lager is er geen gemakkelijke manier om Enum-waarden toe te wijzen aan een databasekolom. Elke optie heeft zijn beperkingen en nadelen. Deze problemen kunnen worden vermeden door JPA 2.1 te gebruiken. Kenmerken.

In deze tutorial bekijken we de verschillende mogelijkheden die we hebben om enums in een database met JPA te behouden. We zullen ook hun voor- en nadelen beschrijven en eenvoudige codevoorbeelden geven.

2. Met behulp van @Genummerd Annotatie

De meest gebruikelijke optie om een ​​opsommingswaarde toe te wijzen aan en van de databaserepresentatie in JPA vóór 2.1. is om de @Genummerd annotatie. Op deze manier kunnen we een JPA-provider opdracht geven om een ​​enum om te zetten naar de ordinale of Draad waarde.

We zullen beide opties in deze sectie onderzoeken.

Maar laten we eerst een eenvoudig maken @Entiteit die we in deze tutorial zullen gebruiken:

@Entity public class Article {@Id private int id; private String-titel; // standaard constructeurs, getters en setters}

2.1. Ordinale waarde in kaart brengen

Als we de @Enumerated (EnumType.ORDINAL) annotatie in het enum-veld, gebruikt JPA de Enum.ordinal () waarde bij het behouden van een bepaalde entiteit in de database.

Laten we de eerste opsomming introduceren:

openbare opsomming Status {OPEN, REVIEW, GOEDGEKEURD, AFGEWEZEN; }

Laten we het vervolgens toevoegen aan het Artikel class en annoteer het met @Enumerated (EnumType.ORDINAL):

@Entity public class Article {@Id private int id; private String-titel; @Enumerated (EnumType.ORDINAL) privéstatusstatus; }

Nu, als u een Artikel entiteit:

Artikelartikel = nieuw artikel (); artikel.setId (1); article.setTitle ("ordinale titel"); article.setStatus (Status.OPEN); 

JPA activeert de volgende SQL-instructie:

invoegen in artikel (status, titel, id) waarden (?,?,?) bindende parameter [1] als [INTEGER] - [0] bindende parameter [2] als [VARCHAR] - [ordinale titel] bindende parameter [3] als [INTEGER] - [1]

Een probleem met dit soort mapping doet zich voor wanneer we onze opsomming moeten aanpassen. Als we een nieuwe waarde in het midden toevoegen of de volgorde van de opsomming herschikken, breken we het bestaande datamodel.

Dergelijke problemen kunnen moeilijk te ondervangen zijn, en ook problematisch om op te lossen, aangezien we alle databaserecords zouden moeten bijwerken.

2.2. Tekenreekswaarde toewijzen

Analoog gebruikt JPA de Enum.name () waarde bij het opslaan van een entiteit als we het enum-veld annoteren met @Enumerated (EnumType.STRING).

Laten we de tweede enum maken:

openbare opsomming Type {INTERNAL, EXTERNAL; }

En laten we het toevoegen aan ons Artikel class en annoteer het met @Enumerated (EnumType.STRING):

@Entity public class Article {@Id private int id; private String-titel; @Enumerated (EnumType.ORDINAL) privéstatusstatus; @Enumerated (EnumType.STRING) privé Type type; }

Nu, als u een Artikel entiteit:

Artikelartikel = nieuw artikel (); artikel.setId (2); article.setTitle ("stringtitel"); article.setType (Type.EXTERNAL);

JPA voert de volgende SQL-instructie uit:

invoegen in artikel (status, titel, type, id) waarden (?,?,?,?) bindende parameter [1] als [INTEGER] - [null] bindende parameter [2] als [VARCHAR] - [string titel] binding parameter [3] als [VARCHAR] - [EXTERN] bindende parameter [4] als [INTEGER] - [2]

Met @Enumerated (EnumType.STRING)kunnen we veilig nieuwe enum-waarden toevoegen of de volgorde van onze enum wijzigen. Het hernoemen van een enum-waarde zal echter nog steeds de databasegegevens breken.

Bovendien, hoewel deze gegevensweergave veel beter leesbaar is in vergelijking met de @Enumerated (EnumType.ORDINAL) optie, het neemt ook veel meer ruimte in beslag dan nodig. Dit kan een significant probleem blijken te zijn wanneer we te maken hebben met een grote hoeveelheid gegevens.

3. Met behulp van @PostLoad en @PrePersist Annotaties

Een andere optie waarmee we te maken hebben met aanhoudende enums in een database, is het gebruik van standaard JPA-callback-methoden. We kunnen onze enums heen en weer in kaart brengen in het @PostLoad en @PrePersist evenementen.

Het idee is om twee attributen in een entiteit te hebben. De eerste is toegewezen aan een databasewaarde en de tweede is een @Transient veld dat een echte enum-waarde bevat. Het tijdelijke attribuut wordt dan gebruikt door de bedrijfslogische code.

Om het concept beter te begrijpen, maken we een nieuwe opsomming en gebruiken we het int waarde in de mapping-logica:

openbare opsomming Prioriteit {LOW (100), MEDIUM (200), HIGH (300); privé int prioriteit; private Priority (int prioriteit) {this.priority = prioriteit; } public int getPriority () {terugkeerprioriteit; } openbare statische prioriteit van (int prioriteit) {retour Stream.of (Priority.values ​​()) .filter (p -> p.getPriority () == prioriteit) .findFirst () .orElseThrow (IllegalArgumentException :: nieuw); }}

We hebben ook het Priority.of () methode om het gemakkelijk te maken om een Prioriteit instantie op basis van zijn int waarde.

Nu, om het te gebruiken in onze Artikel class, moeten we twee attributen toevoegen en callback-methoden implementeren:

@Entity public class Article {@Id private int id; private String-titel; @Enumerated (EnumType.ORDINAL) privéstatusstatus; @Enumerated (EnumType.STRING) privé Type type; @Basic private int priorityValue; @Transient privé Prioriteit prioriteit; @PostLoad void fillTransient () {if (priorityValue> 0) {this.priority = Priority.of (priorityValue); }} @PrePersist void fillPersistent () {if (prioriteit! = Null) {this.priorityValue = priority.getPriority (); }}}

Nu, als u een Artikel entiteit:

Artikelartikel = nieuw artikel (); artikel.setId (3); article.setTitle ("callback-titel"); article.setPriority (Priority.HIGH);

JPA activeert de volgende SQL-query:

invoegen in artikel (prioriteitswaarde, status, titel, type, id) waarden (?,?,?,?,?) bindende parameter [1] als [INTEGER] - [300] bindende parameter [2] als [INTEGER] - [ null] bindende parameter [3] als [VARCHAR] - [callback titel] bindende parameter [4] als [VARCHAR] - [null] bindende parameter [5] als [INTEGER] - [3]

Hoewel deze optie ons meer flexibiliteit geeft bij het kiezen van de weergave van de databasewaarde in vergelijking met eerder beschreven oplossingen, is het niet ideaal. Het voelt gewoon niet goed om twee attributen te hebben die een enkele opsomming in de entiteit vertegenwoordigen. Als we dit type mapping gebruiken, kunnen we bovendien de waarde van enum niet gebruiken in JPQL-query's.

4. JPA gebruiken 2.1 @Converter Annotatie

Om de beperkingen van de hierboven getoonde oplossingen te overwinnen, introduceerde JPA 2.1-release een nieuwe gestandaardiseerde API die kan worden gebruikt om een ​​entiteitsattribuut om te zetten in een databasewaarde en vice versa. Het enige wat we hoeven te doen is een nieuwe klasse maken die implementeert javax.persistence.AttributeConverter en annoteer het met @Converter.

Laten we een praktisch voorbeeld bekijken. Maar eerst maken we, zoals gewoonlijk, een nieuwe opsomming:

openbare opsomming Categorie {SPORT ("S"), MUSIC ("M"), TECHNOLOGY ("T"); privé String-code; privécategorie (tekenreekscode) {this.code = code; } public String getCode () {retourcode; }}

We moeten het ook toevoegen aan het Artikel klasse:

@Entity public class Article {@Id private int id; private String-titel; @Enumerated (EnumType.ORDINAL) privéstatusstatus; @Enumerated (EnumType.STRING) privé Type type; @Basic private int priorityValue; @Transient privé Prioriteit prioriteit; privé categorie categorie; }

Laten we nu een nieuwe maken Categorie Omvormer:

@Converter (autoApply = true) public class CategoryConverter implementeert AttributeConverter {@Override public String convertToDatabaseColumn (categoriecategorie) {if (category == null) {return null; } return category.getCode (); } @Override public Category convertToEntityAttribute (String code) {if (code == null) {return null; } return Stream.of (Category.values ​​()) .filter (c -> c.getCode (). equals (code)) .findFirst () .orElseThrow (IllegalArgumentException :: new); }}

We hebben de @Converter‘S waarde van autoApply naar waar zodat JPA automatisch de conversielogica toepast op alle toegewezen attributen van een Categorie type. Anders zouden we de @Converter annotatie rechtstreeks op het veld van de entiteit.

Laten we nu een Artikel entiteit:

Artikelartikel = nieuw artikel (); artikel.setId (4); article.setTitle ("geconverteerde titel"); article.setCategory (Category.MUSIC);

Vervolgens voert JPA de volgende SQL-instructie uit:

invoegen in artikel (categorie, prioriteitwaarde, status, titel, type, id) waarden (?,?,?,?,?,?) Omgezette waarde bij binding: MUSIC -> M bindende parameter [1] als [VARCHAR] - [ M] bindingsparameter [2] als [INTEGER] - [0] bindingsparameter [3] als [INTEGER] - [null] bindingsparameter [4] als [VARCHAR] - [geconverteerde titel] bindingsparameter [5] als [VARCHAR ] - [null] bindende parameter [6] als [INTEGER] - [4]

Zoals we kunnen zien, kunnen we eenvoudig onze eigen regels instellen voor het converteren van enums naar een overeenkomstige databasewaarde als we de AttributeConverter koppel. Bovendien kunnen we veilig nieuwe enum-waarden toevoegen of de bestaande wijzigen zonder de reeds bestaande gegevens te verbreken.

De algehele oplossing is eenvoudig te implementeren en verhelpt alle nadelen van de opties die in de eerdere secties zijn gepresenteerd.

5. Enums gebruiken in JPQL

Laten we nu eens kijken hoe gemakkelijk het is om enums te gebruiken in de JPQL-queries.

Om alles te vinden Artikel entiteiten met Categorie.SPORT categorie, moeten we de volgende verklaring uitvoeren:

String jpql = "selecteer een uit artikel a waarbij a.category = com.baeldung.jpa.enums.Category.SPORT"; Lijst met artikelen = em.createQuery (jpql, Article.class) .getResultList ();

Het is belangrijk op te merken dat we in dit geval een volledig gekwalificeerde enum-naam moeten gebruiken.

Natuurlijk zijn we niet beperkt tot statische zoekopdrachten. Het is volkomen legaal om de genoemde parameters te gebruiken:

String jpql = "selecteer een uit artikel a waarbij a.category =: categorie"; TypedQuery-query = em.createQuery (jpql, Article.class); query.setParameter ("categorie", Category.TECHNOLOGY); Lijst artikelen = query.getResultList ();

Het bovenstaande voorbeeld is een erg handige manier om dynamische query's te maken.

Bovendien hoeven we geen volledig gekwalificeerde namen te gebruiken.

6. Conclusie

In deze zelfstudie hebben we verschillende manieren besproken om opsommingswaarden in een database te behouden. We hebben opties gepresenteerd die we hebben bij het gebruik van JPA in versie 2.0 en lager, evenals een nieuwe API die beschikbaar is in JPA 2.1 en hoger.

Het is vermeldenswaard dat dit niet de enige mogelijkheden zijn om met enums in JPA om te gaan. Sommige databases, zoals PostgreSQL, bieden een speciaal kolomtype om opsommingswaarden op te slaan. Dergelijke oplossingen vallen echter buiten het bestek van dit artikel.

Als vuistregel moeten we altijd de AttributeConverter interface en @Converter annotatie als we JPA 2.1 of hoger gebruiken.

Zoals gewoonlijk zijn alle codevoorbeelden beschikbaar in onze GitHub-repository.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS