Szukam różnych sposobów mapowania wyliczenia za pomocą JPA. W szczególności chcę ustawić wartość całkowitą każdego wpisu wyliczenia i zapisać tylko wartość całkowitą.
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
Prostym rozwiązaniem jest użycie wyliczonej adnotacji w EnumType.ORDINAL:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Ale w tym przypadku JPA odwzorowuje indeks wyliczeniowy (0,1,2), a nie żądaną wartość (100,200,300).
Dwa znalezione przeze mnie rozwiązania nie wydają się proste ...
Pierwsze rozwiązanie
Proponowane tutaj rozwiązanie wykorzystuje @PrePersist i @PostLoad do konwersji wyliczenia na inne pole i oznaczenia pola wyliczenia jako przejściowe:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
Drugie rozwiązanie
Drugie zaproponowane tutaj rozwiązanie zaproponowało ogólny obiekt konwersji, ale nadal wydaje się ciężki i zorientowany na hibernację (@Type wydaje się nie istnieć w Java EE):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
Czy są jakieś inne rozwiązania?
Mam na myśli kilka pomysłów, ale nie wiem, czy istnieją one w JPA:
- używaj metody ustawiającej i pobierającej prawego członka klasy klasy Authority podczas ładowania i zapisywania obiektu klasy Authority
- równoważnym pomysłem byłoby poinformowanie JPA, jakie są metody Right enum do konwersji enum na int i int na enum
- Ponieważ używam Springa, czy jest jakiś sposób, aby powiedzieć JPA, aby używał określonego konwertera (RightEditor)?
Odpowiedzi:
W przypadku wersji wcześniejszych niż JPA 2.1, JPA zapewnia tylko dwa sposoby radzenia sobie z wyliczeniami, przez ich
name
lub przez ichordinal
. Standardowy JPA nie obsługuje niestandardowych typów. Więc:UserType
, EclipseLinkConverter
itp.). (drugie rozwiązanie). ~ lub ~int
wartości ~ lub ~Zilustruję najnowszą opcję (jest to podstawowa implementacja, dostosuj ją w razie potrzeby):
źródło
Jest to teraz możliwe w JPA 2.1:
Dalsze szczegóły:
źródło
@Converter
, aleenum
należy postępować bardziej elegancko po wyjęciu z pudełka!AttributeConverter
ALE cytuje kod, który nic nie robi i nie odpowiada OP.AttributeConverter
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
Od JPA 2.1 możesz używać AttributeConverter .
Utwórz wyliczoną klasę w następujący sposób:
I utwórz konwerter taki jak ten:
Na encji potrzebujesz:
Twoje szczęście
@Converter(autoApply = true)
może się różnić w zależności od kontenera, ale przetestowane pod kątem działania na Wildfly 8.1.0. Jeśli to nie działa, możesz dodać@Convert(converter = NodeTypeConverter.class)
kolumnę klasy encji.źródło
Najlepszym rozwiązaniem byłoby odwzorowanie unikalnego identyfikatora na każdy typ wyliczenia, unikając w ten sposób pułapek ORDINAL i STRING. Zobacz ten post, który opisuje 5 sposobów mapowania wyliczenia.
Z linku powyżej:
1 i 2. Korzystanie z @Enumerated
Obecnie istnieją 2 sposoby mapowania wyliczeń w jednostkach JPA za pomocą adnotacji @Enumerated. Niestety zarówno EnumType.STRING, jak i EnumType.ORDINAL mają swoje ograniczenia.
Jeśli użyjesz EnumType.String, wówczas zmiana nazwy jednego z typów wyliczenia spowoduje, że wartość wyliczenia nie będzie zsynchronizowana z wartościami zapisanymi w bazie danych. Jeśli użyjesz EnumType.ORDINAL, wówczas usunięcie lub zmiana kolejności typów w twoim wyliczeniu spowoduje mapowanie wartości zapisanych w bazie danych na niewłaściwe typy wyliczeń.
Obie te opcje są delikatne. Jeśli wyliczenie zostanie zmodyfikowane bez przeprowadzania migracji bazy danych, można narzucić integralność danych.
3. Callback cyklu życia
Możliwym rozwiązaniem byłoby użycie adnotacji zwrotnych JPA cyklu życia, @PrePersist i @PostLoad. Czuje się to dość brzydko, ponieważ będziesz mieć teraz dwie zmienne w swojej jednostce. Jeden odwzorowuje wartość przechowywaną w bazie danych, a drugi rzeczywisty wyliczenie.
4. Mapowanie unikalnego identyfikatora do każdego typu wyliczenia
Preferowanym rozwiązaniem jest odwzorowanie wyliczenia na stałą wartość lub identyfikator zdefiniowany w wyliczeniu. Odwzorowanie na predefiniowaną, stałą wartość sprawia, że kod jest bardziej niezawodny. Jakakolwiek zmiana kolejności typów wyliczeń lub refaktoryzacja nazw nie spowoduje żadnych negatywnych skutków.
5. Korzystanie z Java EE7 @Convert
Jeśli używasz JPA 2.1, możesz skorzystać z nowej adnotacji @Convert. Wymaga to utworzenia klasy konwertera, opatrzonej adnotacją @Converter, w której zdefiniowałbyś, jakie wartości są zapisywane w bazie danych dla każdego typu wyliczenia. W swojej jednostce adnotowałbyś wtedy swój wyliczenie za pomocą @Convert.
Moje preferencje: (numer 4)
Powodem, dla którego wolę definiować moje identyfikatory w wyliczeniu zamiast używania konwertera, jest dobra enkapsulacja. Tylko typ wyliczeniowy powinien znać swój identyfikator, a tylko jednostka powinna wiedzieć, w jaki sposób mapuje wyliczenie do bazy danych.
Zobacz oryginalny post dla przykładu kodu.
źródło
Problemem jest, jak sądzę, to, że JPA nigdy nie wpadł na pomysł, że moglibyśmy już mieć złożony wcześniej istniejący schemat.
Myślę, że wynikają z tego dwa główne niedociągnięcia, specyficzne dla Enum:
Pomóż mojej sprawie i zagłosuj na JPA_SPEC-47
Czy nie byłoby to bardziej eleganckie niż użycie @Converter do rozwiązania problemu?
źródło
Prawdopodobnie blisko powiązany kod Pascala
źródło
Zrobiłbym następujące:
Deklaruj osobno wyliczenie we własnym pliku:
Zadeklaruj nowy podmiot JPA o nazwie Prawo
Będziesz także potrzebował konwertera do odbierania tych wartości (tylko JPA 2.1 i jest problem, nie będę tutaj omawiać z tymi wyliczeniami, które będą bezpośrednio utrwalane za pomocą konwertera, więc będzie to droga tylko w jedną stronę)
Organ:
W ten sposób nie można ustawić bezpośrednio w polu wyliczenia. Można jednak ustawić odpowiednie pole w polu Uprawnienia za pomocą
A jeśli chcesz porównać, możesz użyć:
Co wydaje mi się całkiem fajne. Nie jest to całkowicie poprawne, ponieważ konwerter nie jest przeznaczony do użytku z enum. W rzeczywistości dokumentacja mówi, aby nigdy nie używać jej do tego celu, zamiast tego należy użyć adnotacji @Enumerated. Problem polega na tym, że istnieją tylko dwa typy wyliczeń: ORDINAL lub STRING, ale ORDINAL jest trudny i nie jest bezpieczny.
Jeśli jednak nie jest to satysfakcjonujące, możesz zrobić coś nieco bardziej zuchwałego i prostszego (lub nie).
Zobaczmy.
The RightEnum:
i podmiot władzy
W tym drugim pomyśle nie jest to idealna sytuacja, ponieważ włamujemy się do atrybutu porządkowego, ale jest to znacznie mniejsze kodowanie.
Myślę, że specyfikacja JPA powinna zawierać EnumType.ID, w którym pole wartości wyliczeniowej powinno być opatrzone adnotacją @EnumId.
źródło
Moje własne rozwiązanie do rozwiązania tego rodzaju mapowania Enum JPA jest następujące.
Krok 1 - Napisz następujący interfejs, którego będziemy używać dla wszystkich wyliczeń, które chcemy zamapować na kolumnę db:
Krok 2 - Zaimplementuj niestandardowy ogólny konwerter JPA w następujący sposób:
Ta klasa przekonwertuje wartość wyliczenia
E
na pole bazy danych typuT
(np.String
) Przy użyciugetDbVal()
on enumE
i odwrotnie.Krok 3 - Pozwól oryginalnemu enum zaimplementować interfejs zdefiniowany w kroku 1:
Krok 4 - Rozszerz konwerter z kroku 2 o
Right
wyliczenie z kroku 3:Krok 5 - Ostatnim krokiem jest opisanie pola w encji w następujący sposób:
Wniosek
IMHO jest to najczystsze i najbardziej eleganckie rozwiązanie, jeśli masz wiele wyliczeń do odwzorowania i chcesz użyć określonego pola wyliczenia jako wartości odwzorowania.
W przypadku wszystkich innych wyliczeń w projekcie, które wymagają podobnej logiki odwzorowania, wystarczy powtórzyć kroki od 3 do 5, to znaczy:
IDbValue
na swoim enum;EnumDbValueConverter
tylko 3 linie kodu (możesz to zrobić również w swojej jednostce, aby uniknąć tworzenia oddzielnej klasy);@Convert
zjavax.persistence
pakietu.Mam nadzieję że to pomoże.
źródło
pierwszy przypadek (
EnumType.ORDINAL
)druga sprawa (
EnumType.STRING
)źródło