Vedvarende Enums i JPA

Java Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Introduktion

I JPA version 2.0 og derunder er der ingen praktisk måde at kortlægge Enum-værdier til en databasesøjle. Hver mulighed har sine begrænsninger og ulemper. Disse problemer kan undgås ved hjælp af JPA 2.1. funktioner.

I denne tutorial tager vi et kig på de forskellige muligheder, vi har for at fortsætte enums i en database ved hjælp af JPA. Vi beskriver også deres fordele og ulemper samt giver enkle kodeeksempler.

2. Brug @Enumereret Kommentar

Den mest almindelige mulighed for at kortlægge en enumværdi til og fra dens databaseretning i JPA før 2.1. er at bruge @Enumereret kommentar. På denne måde kan vi instruere en JPA-udbyder om at konvertere en enum til sin ordinal eller Snor værdi.

Vi undersøger begge muligheder i dette afsnit.

Men lad os først oprette en simpel @Enhed som vi bruger i hele denne tutorial:

@Entity public class-artikel {@Id privat int id; privat streng titel; // standardkonstruktører, getters og settere}

2.1. Kortlægning af ordinær værdi

Hvis vi lægger @Enumerated (EnumType.ORDINAL) bemærkning på enum-feltet, vil JPA bruge Enum.ordinal () værdi ved vedvarende en given enhed i databasen.

Lad os introducere det første enum:

offentlig enum Status {ÅBEN, ANMELDELSE, GODKENDT, AFVIST; }

Lad os derefter tilføje det til Artikel klasse og kommentere det med @Enumerated (EnumType.ORDINAL):

@Entity public class-artikel {@Id privat int id; privat streng titel; @Enumerated (EnumType.ORDINAL) privat status status; }

Nu, når vedvarende en Artikel enhed:

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

JPA udløser følgende SQL-sætning:

indsæt i artikel (status, titel, id) værdier (?,?,?) bindende parameter [1] som [INTEGER] - [0] bindingsparameter [2] som [VARCHAR] - [ordinær titel] bindingsparameter [3] som [INTEGER] - [1]

Et problem med denne form for kortlægning opstår, når vi har brug for at ændre vores enum. Hvis vi tilføjer en ny værdi i midten eller omarrangerer enums rækkefølge, bryder vi den eksisterende datamodel.

Sådanne problemer kan være svære at få fat i, såvel som problematiske at løse, da vi bliver nødt til at opdatere alle databaseposter.

2.2. Kortlægning af strengværdi

Analogt vil JPA bruge Enum.name () værdi ved lagring af en enhed, hvis vi kommenterer enum-feltet med @Enumerated (EnumType.STRING).

Lad os oprette det andet enum:

public enum Type {INTERN, EXTERNAL; }

Og lad os tilføje det til vores Artikel klasse og kommentere det med @Enumerated (EnumType.STRING):

@Entity public class-artikel {@Id privat int id; privat streng titel; @Enumerated (EnumType.ORDINAL) privat status status; @Enumerated (EnumType.STRING) privat Type type; }

Nu, når vedvarende en Artikel enhed:

Artikelartikel = ny artikel (); article.setId (2); article.setTitle ("streng titel"); article.setType (Type.EXTERNAL);

JPA udfører følgende SQL-sætning:

indsæt i artikel (status, titel, type, id) værdier (?,?,?,?) bindingsparameter [1] som [INTEGER] - [null] bindingsparameter [2] som [VARCHAR] - [streng titel] binding parameter [3] som [VARCHAR] - [EXTERNAL] bindingsparameter [4] som [INTEGER] - [2]

Med @Enumerated (EnumType.STRING), kan vi sikkert tilføje nye enumværdier eller ændre vores enums rækkefølge. At omdøbe en enumværdi vil dog stadig bryde databasedataene.

Derudover, selv om denne datarepræsentation er langt mere læselig i forhold til @Enumerated (EnumType.ORDINAL) mulighed, det bruger også meget mere plads end nødvendigt. Dette kan vise sig at være et vigtigt problem, når vi har brug for at håndtere en stor datamængde.

3. Brug @PostLoad og @PrePersist Kommentarer

En anden mulighed, vi har at gøre med vedvarende enums i en database, er at bruge standard JPA callback-metoder. Vi kan kortlægge vores enumer frem og tilbage i @PostLoad og @PrePersist begivenheder.

Ideen er at have to attributter i en enhed. Den første kortlægges til en databaseværdi, og den anden er en @Transient felt, der har en reel enumværdi. Den midlertidige attribut bruges derefter af forretningslogikoden.

For bedre at forstå konceptet, lad os oprette et nyt rum og bruge det int værdi i kortlægningslogikken:

public enum Priority {LOW (100), MEDIUM (200), HIGH (300); privat int prioritet; privat prioritet (int prioritet) {denne.prioritet = prioritet; } public int getPriority () {returprioritet; } offentlig statisk prioritet for (int-prioritet) {return Stream.of (Priority.values ​​()) .filter (p -> p.getPriority () == prioritet) .findFirst () .orElseThrow (IllegalArgumentException :: new); }}

Vi har også tilføjet Priority.of () metode til at gøre det let at få en Prioritet eksempel baseret på dets int værdi.

Nu for at bruge det i vores Artikel klasse, skal vi tilføje to attributter og implementere tilbagekaldsmetoder:

@Entity public class-artikel {@Id privat int id; privat streng titel; @Enumerated (EnumType.ORDINAL) privat status status; @Enumerated (EnumType.STRING) privat Type type; @ Grundlæggende privat int prioritetsværdi; @Transient privat prioritetsprioritet; @PostLoad ugyldigt fillTransient () {if (prioritetsværdi> 0) {dette.priority = Prioritet.af (prioritetsværdi); }} @PrePersist ugyldigt fillPersistent () {if (prioritet! = Null) {this.priorityValue = prioritet.getPriority (); }}}

Nu, når vedvarende en Artikel enhed:

Artikelartikel = ny artikel (); article.setId (3); article.setTitle ("callback title"); article.setPriority (Priority.HIGH);

JPA udløser følgende SQL-forespørgsel:

indsæt i artikel (prioritetsværdi, status, titel, type, id) værdier (?,?,?,?,?) bindingsparameter [1] som [INTEGER] - [300] bindingsparameter [2] som [INTEGER] - [ null] bindingsparameter [3] som [VARCHAR] - [callback title] bindingsparameter [4] som [VARCHAR] - [null] bindingsparameter [5] som [INTEGER] - [3]

Selvom denne mulighed giver os større fleksibilitet i valg af repræsentation af databaseværdien sammenlignet med tidligere beskrevne løsninger, er den ikke ideel. Det føles bare ikke rigtigt at have to attributter, der repræsenterer en enkelt enum i enheden. Derudover, hvis vi bruger denne type kortlægning, er vi ikke i stand til at bruge enums værdi i JPQL-forespørgsler.

4. Brug af JPA 2.1 @Konverter Kommentar

For at overvinde begrænsningerne i de løsninger, der er vist ovenfor, introducerede JPA 2.1 release en ny standardiseret API, der kan bruges til at konvertere en enhedsattribut til en databaseværdi og omvendt. Alt hvad vi skal gøre er at oprette en ny klasse, der implementeres javax.persistence.AttributeConverter og kommentere det med @Konverter.

Lad os se et praktisk eksempel. Men først opretter vi som sædvanligt et nyt enum:

offentlig enum Kategori {SPORT ("S"), MUSIK ("M"), TEKNOLOGI ("T"); privat streng kode; privat kategori (strengkode) {this.code = code; } offentlig String getCode () {returkode; }}

Vi er også nødt til at føje det til Artikel klasse:

@Entity public class-artikel {@Id privat int id; privat streng titel; @Enumerated (EnumType.ORDINAL) privat status status; @Enumerated (EnumType.STRING) privat Type type; @ Grundlæggende privat int prioritetsværdi; @Transient privat prioritetsprioritet; privat kategorikategori; }

Lad os nu oprette et nyt CategoryConverter:

@Converter (autoApply = true) public class CategoryConverter implementerer AttributeConverter {@Override public String convertToDatabaseColumn (Kategorikategori) {if (category == null) {return null; } returner category.getCode (); } @ Override offentlig kategori convertToEntityAttribute (strengkode) {if (code == null) {return null; } returner Stream.of (Category.values ​​()). filter (c -> c.getCode (). er lig med (code)) .findFirst () .orElseThrow (IllegalArgumentException :: new); }}

Vi har indstillet @Konverter'S værdi af autoApply til rigtigt så JPA automatisk anvender konverteringslogikken til alle kortlagte attributter for en Kategori type. Ellers bliver vi nødt til at sætte @Konverter kommentar direkte på enhedens felt.

Lad os nu fortsætte en Artikel enhed:

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

Derefter udfører JPA følgende SQL-sætning:

indsæt i artikel (kategori, prioritetsværdi, status, titel, type, id) værdier (?,?,?,?,?,?) Konverteret værdi ved binding: MUSIK -> M bindingsparameter [1] som [VARCHAR] - [ M] bindingsparameter [2] som [INTEGER] - [0] bindingsparameter [3] som [INTEGER] - [null] bindingsparameter [4] som [VARCHAR] - [konverteret titel] bindingsparameter [5] som [VARCHAR ] - [null] bindingsparameter [6] som [INTEGER] - [4]

Som vi kan se, kan vi simpelthen indstille vores egne regler for konvertering af enums til en tilsvarende databaseværdi, hvis vi bruger AttributeConverter interface. Desuden kan vi sikkert tilføje nye enumværdier eller ændre de eksisterende uden at bryde de allerede vedvarende data.

Den overordnede løsning er enkel at implementere og løser alle ulemperne ved de muligheder, der er præsenteret i de tidligere afsnit.

5. Brug af Enums i JPQL

Lad os nu se, hvor let det er at bruge enums i JPQL-forespørgsler.

At finde alt Artikel enheder med Kategori.SPORT kategori, skal vi udføre følgende erklæring:

String jpql = "vælg en fra artikel a hvor a.category = com.baeldung.jpa.enums.Category.SPORT"; Liste artikler = em.createQuery (jpql, Article.class) .getResultList ();

Det er vigtigt at bemærke, at i dette tilfælde er vi nødt til at bruge et fuldt kvalificeret enumnavn.

Selvfølgelig er vi ikke begrænset til statiske forespørgsler. Det er helt lovligt at bruge de navngivne parametre:

String jpql = "vælg en fra artikel a hvor a.category =: category"; TypedQuery-forespørgsel = em.createQuery (jpql, Article.class); query.setParameter ("kategori", Category.TECHNOLOGY); Liste artikler = query.getResultList ();

Ovenstående eksempel præsenterer en meget bekvem måde at danne dynamiske forespørgsler på.

Derudover behøver vi ikke bruge fuldt kvalificerede navne.

6. Konklusion

I denne vejledning har vi dækket forskellige måder til vedvarende enumværdier i en database. Vi har præsenteret muligheder, vi har, når vi bruger JPA i version 2.0 og derunder, samt en ny API tilgængelig i JPA 2.1 og derover.

Det er værd at bemærke, at dette ikke er de eneste muligheder for at håndtere enums i JPA. Nogle databaser, som PostgreSQL, giver en dedikeret kolonnetype til at gemme enumværdier. Sådanne løsninger er dog uden for denne artikels anvendelsesområde.

Som en tommelfingerregel skal vi altid bruge AttributeConverter interface og @Konverter kommentar, hvis vi bruger JPA 2.1 eller nyere.

Som sædvanligt er alle kodeeksempler tilgængelige på vores GitHub-lager.

Java bund

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN