Ustrajni enumi u JPA

Java Top

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ

1. Uvod

U JPA verziji 2.0 i starijim, ne postoji prikladan način za mapiranje vrijednosti Enum u stupac baze podataka. Svaka opcija ima svoja ograničenja i nedostatke. Ova se pitanja mogu izbjeći korištenjem JPA 2.1. značajke.

U ovom uputstvu ćemo pogledati različite mogućnosti koje imamo kako bismo nastavili nabrajati u bazi podataka koristeći JPA. Također ćemo opisati njihove prednosti i nedostatke, kao i jednostavne primjere koda.

2. Korištenje @Nabrojeno Bilješka

Najčešća opcija mapiranja vrijednosti nabrajanja u i iz njezine baze podataka u JPA prije 2.1. je koristiti @Nabrojeno bilješka. Na ovaj način možemo uputiti JPA davatelja da pretvori enum u njegov redni ili Niz vrijednost.

Istražit ćemo obje mogućnosti u ovom odjeljku.

Ali prvo, stvorimo jednostavan @ Entitet koje ćemo koristiti tijekom ovog vodiča:

Članak o javnoj klasi entiteta {@Id private int id; privatni naslov niza; // standardni konstruktori, getteri i postavljači}

2.1. Mapiranje redne vrijednosti

Ako stavimo @Enumerated (EnumType.ORDINAL) napomena na polju nabrajanja, JPA će koristiti Enum.ordinal () vrijednost kod trajnog zadatka entiteta u bazi podataka.

Uvedimo prvi nabrajanje:

status javnog popisa {OTVORENO, PREGLEDATI, ODOBRENO, ODBIJENO; }

Dalje, dodajte ga u Članak razredu i označite ga s @Enumerated (EnumType.ORDINAL):

Članak o javnoj klasi entiteta {@Id private int id; privatni naslov niza; @Numerani (EnumType.ORDINAL) status privatnog statusa; }

Sada, kad se ustraje Članak entitet:

Članak u članku = novi članak (); article.setId (1); article.setTitle ("redni naslov"); article.setStatus (Status.OPEN); 

JPA će pokrenuti sljedeći SQL izraz:

umetnite u članak (status, naslov, id) vrijednosti (?,?,?) parametar vezanja [1] kao [INTEGER] - [0] parametar vezanja [2] kao [VARCHAR] - [redni naslov] parametar vezanja [3] kao [INTEGER] - [1]

Problem s ovom vrstom mapiranja pojavljuje se kada moramo izmijeniti svoj nabrajanje. Ako u sredinu dodamo novu vrijednost ili preuredimo redoslijed nabrajanja, razbit ćemo postojeći model podataka.

Takve probleme može biti teško uhvatiti, ali ih je i problematično riješiti, jer bismo morali ažurirati sve zapise baze podataka.

2.2. Mapiranje vrijednosti niza

Analogno tome, JPA će koristiti Enum.name () vrijednost kod spremanja entiteta ako polje enum označimo s @Nabrojeno (EnumType.STRING).

Stvorimo drugi nabrajanje:

javni popis Tip {UNUTARNJI, VANJSKI; }

I dodajmo to našem Članak razredu i označite ga s @Nabrojeno (EnumType.STRING):

Članak o javnoj klasi entiteta {@Id private int id; privatni naslov niza; @Numerani (EnumType.ORDINAL) status privatnog statusa; @Enumerated (EnumType.STRING) tip privatnog tipa; }

Sada, kad se ustraje Članak entitet:

Članak u članku = novi članak (); article.setId (2); article.setTitle ("naslov niza"); article.setType (Type.EXTERNAL);

JPA će izvršiti sljedeći SQL izraz:

umetnite u članak (status, naslov, vrsta, id) vrijednosti (?,?,?,?) parametar vezanja [1] kao [INTEGER] - [null] parametar vezanja [2] kao [VARCHAR] - [string title] vezanje parametar [3] kao [VARCHAR] - [EXTERNAL] parametar vezanja [4] kao [INTEGER] - [2]

S @Nabrojeno (EnumType.STRING), možemo sigurno dodati nove vrijednosti nabrajanja ili promijeniti redoslijed nabrajanja. Međutim, preimenovanje vrijednosti nabrajanja i dalje će slomiti podatke baze podataka.

Uz to, iako je ovaj prikaz podataka daleko čitljiviji u odnosu na @Enumerated (EnumType.ORDINAL) opcija, također troši puno više prostora nego što je potrebno. To bi se moglo pokazati značajnim problemom kada se moramo nositi s velikom količinom podataka.

3. Korištenje @PostLoad i @PrePersist Bilješke

Druga mogućnost s kojom se moramo nositi s trajnim nabrajanjima u bazi podataka je uporaba standardnih metoda JPA povratnog poziva. Naše popise možemo mapirati naprijed-natrag u @PostLoad i @PrePersist događaja.

Ideja je imati dva atributa u entitetu. Prva se preslikava na vrijednost baze podataka, a druga je @Prijelazno polje koje ima stvarnu vrijednost nabrajanja. Privremeni atribut tada koristi kod poslovne logike.

Da bismo bolje razumjeli koncept, stvorimo novu enum i upotrijebimo je int vrijednost u logici mapiranja:

javni popis Prioritet {LOW (100), SREDNJI (200), HIGH (300); privatni int prioritet; privatni prioritet (int prioritet) {this.priority = prioritet; } public int getPriority () {prioritet povrata; } javni statički prioritet (int prioritet) {return Stream.of (Priority.values ​​()) .filter (p -> p.getPriority () == prioritet) .findFirst () .orElseThrow (IllegalArgumentException :: new); }}

Također smo dodali Priority.of () metoda koja olakšava dobivanje a Prioritet instanca na temelju svoje int vrijednost.

Sada, da ga koristimo u našem Članak klase, moramo dodati dva atributa i implementirati metode povratnog poziva:

Članak o javnoj klasi entiteta {@Id private int id; privatni naslov niza; @Numerani (EnumType.ORDINAL) status privatnog statusa; @Enumerated (EnumType.STRING) tip privatnog tipa; @Basic private int priorityValue; @Prijelazni privatni prioritetni prioritet; @PostLoad void fillTransient () {if (priorityValue> 0) {this.priority = Priority.of (prioritetValue); }} @PrePersist void fillPersistent () {if (prioritet! = Null) {this.priorityValue = prioritet.getPriority (); }}}

Sada, kad se ustraje Članak entitet:

Članak članak = novi članak (); article.setId (3); article.setTitle ("naslov povratnog poziva"); article.setPriority (Priority.HIGH);

JPA će pokrenuti sljedeći SQL upit:

umetnite u Article (priorityValue, status, title, type, id) vrijednosti (?,?,?,?,?) parametar vezanja [1] kao [INTEGER] - [300] parametar vezanja [2] kao [INTEGER] - [ null] parametar vezanja [3] kao [VARCHAR] - [naslov povratnog poziva] parametar vezanja [4] kao [VARCHAR] - [null] parametar vezanja [5] kao [INTEGER] - [3]

Iako nam ova opcija daje veću fleksibilnost u odabiru predstavljanja vrijednosti baze podataka u odnosu na prethodno opisana rješenja, ona nije idealna. Jednostavno se ne osjeća ispravno imati dva atributa koji predstavljaju jedan nabrajanje u entitetu. Uz to, ako koristimo ovu vrstu mapiranja, ne možemo koristiti vrijednost enum-a u JPQL upitima.

4. Korištenje JPA 2.1 @Konverter Bilješka

Da bi se prevladala ograničenja gore prikazanih rješenja, izdanje JPA 2.1 predstavilo je novi standardizirani API koji se može koristiti za pretvaranje atributa entiteta u vrijednost baze podataka i obrnuto. Sve što trebamo učiniti je stvoriti novu klasu koja se provodi javax.persistence.AttributeConverter i zabilježite ga s @Konverter.

Pogledajmo praktični primjer. Ali prvo, kao i obično, stvorit ćemo novi nabrajanje:

kategorija javnog popisa {SPORT ("S"), GLAZBA ("M"), TEHNOLOGIJA ("T"); kod privatnog niza; privatna kategorija (kod niza) {this.code = code; } javni String getCode () {povratni kôd; }}

Također ga moramo dodati u Članak razred:

Članak o javnoj klasi entiteta {@Id private int id; privatni naslov niza; @Numerani (EnumType.ORDINAL) status privatnog statusa; @Enumerated (EnumType.STRING) tip privatnog tipa; @Basic private int priorityValue; @Prijelazni privatni prioritetni prioritet; kategorija privatne kategorije; }

Ajmo sada stvoriti novi Pretvarač kategorija:

@Converter (autoApply = true) javna klasa CategoryConverter implementira AttributeConverter {@Override public String convertToDatabaseColumn (kategorija kategorije) {if (category == null) {return null; } vratiti kategoriju.getCode (); } @Override javna kategorija convertToEntityAttribute (kod niza) {if (code == null) {return null; } return Stream.of (Category.values ​​()) .filter (c -> c.getCode (). jednako (kod)) .findFirst () .orElseThrow (IllegalArgumentException :: new); }}

Postavili smo @Konverter'S vrijednost od autoPrijavi do pravi tako da će JPA automatski primijeniti logiku pretvorbe na sve preslikane atribute a Kategorija tip. Inače bismo morali staviti @Konverter napomena izravno na polju entiteta.

Ustrajmo sada Članak entitet:

Članak u članku = novi članak (); article.setId (4); article.setTitle ("pretvoreni naslov"); article.setCategory (Category.MUSIC);

Tada će JPA izvršiti sljedeći SQL izraz:

umetnuti u članak (kategorija, prioritetVrijednost, status, naslov, vrsta, id) vrijednosti (?,?,?,?,?,?) Pretvorena vrijednost na vezivanju: GLAZBA -> M parametar vezivanja [1] kao [VARCHAR] - [ M] parametar vezanja [2] kao [INTEGER] - [0] parametar vezanja [3] kao [INTEGER] - [null] parametar vezanja [4] kao [VARCHAR] - [pretvoreni naslov] parametar vezanja [5] kao [VARCHAR ] - [null] parametar vezanja [6] kao [INTEGER] - [4]

Kao što vidimo, možemo jednostavno postaviti vlastita pravila pretvaranja enuma u odgovarajuću vrijednost baze podataka ako koristimo AttributeConverter sučelje. Štoviše, možemo sigurno dodati nove vrijednosti nabrajanja ili promijeniti postojeće bez razbijanja već zadržanih podataka.

Cjelokupno rješenje jednostavno je implementirati i rješava sve nedostatke opcija predstavljenih u ranijim odjeljcima.

5. Korištenje Enumsa u JPQL-u

Pogledajmo sada kako je lako koristiti enume u JPQL upitima.

Da biste pronašli sve Članak entiteti sa Kategorija.SPORT kategoriji, moramo izvršiti sljedeću izjavu:

Niz jpql = "odaberite a iz članka a gdje je a.category = com.baeldung.jpa.enums.Category.SPORT"; Popis članaka = em.createQuery (jpql, Article.class) .getResultList ();

Važno je napomenuti da u ovom slučaju moramo koristiti potpuno kvalificirano ime popisa.

Naravno, nismo ograničeni na statičke upite. Potpuno je legalno koristiti imenovane parametre:

Niz jpql = "odaberite a iz članka a gdje je a.category =: kategorija"; Upit TypedQuery = em.createQuery (jpql, Article.class); query.setParameter ("kategorija", Kategorija.TECHNOLOGIJA); Popis članaka = query.getResultList ();

Gornji primjer predstavlja vrlo prikladan način formiranja dinamičkih upita.

Uz to, ne trebamo koristiti potpuno kvalificirana imena.

6. Zaključak

U ovom smo priručniku pokrili razne načine trajnog nabrajanja vrijednosti u bazi podataka. Predstavili smo mogućnosti koje imamo pri korištenju JPA u verziji 2.0 i starijim, kao i novi API dostupan u JPA 2.1 i novijim.

Vrijedno je napomenuti da to nisu jedine mogućnosti rješavanja popisa u JPA. Neke baze podataka, poput PostgreSQL-a, pružaju namjenski tip stupca za spremanje vrijednosti nabrajanja. Međutim, takva su rješenja izvan dosega ovog članka.

Kao pravilo, uvijek bismo trebali koristiti AttributeConverter sučelje i @Konverter napomena ako koristimo JPA 2.1 ili noviji.

Kao i obično, svi primjeri koda dostupni su na našem GitHub spremištu.

Dno Java

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ