Raccolta di mappe JPA di Enums
Domanda
Esiste un modo in JPA per mappare una raccolta di Enum all'interno della classe Entity? O l'unica soluzione è avvolgere Enum con un'altra classe di dominio e usarlo per mappare la raccolta?
@Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//@???
Collection<InterestsEnum> interests;
}
Sto usando l'implementazione di Hibernate JPA, ma ovviamente preferirei una soluzione agnostica di implementazione.
Soluzione
usando Hibernate puoi farlo
@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
Altri suggerimenti
Il link nella risposta di Andy è un ottimo punto di partenza per mappare raccolte di " non Entity " oggetti in JPA 2, ma non è del tutto completo quando si tratta di mappare gli enum. Ecco invece cosa mi è venuto in mente.
@Entity
public class Person {
@ElementCollection(targetClass=InterestsEnum.class)
@Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
@CollectionTable(name="person_interest")
@Column(name="interest") // Column name in person_interest
Collection<InterestsEnum> interests;
}
Sono stato in grado di farlo in questo modo semplice:
@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
È necessario un caricamento ansioso per evitare errori di inizializzazione del caricamento lento, come spiegato qui .
Sto usando una leggera modifica di java.util.RegularEnumSet per avere un EnumSet persistente:
@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>>
extends AbstractSet<E> {
private long elements;
@Transient
private final Class<E> elementType;
@Transient
private final E[] universe;
public PersistentEnumSet(final Class<E> elementType) {
this.elementType = elementType;
try {
this.universe = (E[]) elementType.getMethod("values").invoke(null);
} catch (final ReflectiveOperationException e) {
throw new IllegalArgumentException("Not an enum type: " + elementType, e);
}
if (this.universe.length > 64) {
throw new IllegalArgumentException("More than 64 enum elements are not allowed");
}
}
// Copy everything else from java.util.RegularEnumSet
// ...
}
Questa classe è ora la base di tutti i miei set di enum:
@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
E quel set che posso usare nella mia entità:
@Entity
public class MyEntity {
// ...
@Embedded
@AttributeOverride(name="elements", column=@Column(name="interests"))
private InterestsSet interests = new InterestsSet();
}
Vantaggi:
- Lavorare con un tipo enum sicuro e performante impostato nel tuo codice (vedi
java.util.EnumSet
per una descrizione) - Il set è solo una colonna numerica nel database
- tutto è semplice JPA (nessun tipo personalizzato ) specifico del provider
- facile (e breve) dichiarazione di nuovi campi dello stesso tipo, rispetto alle altre soluzioni
Svantaggi:
- Duplicazione del codice (
RegularEnumSet
ePersistentEnumSet
sono quasi uguali)- Puoi racchiudere il risultato di
EnumSet.noneOf(enumType)
nel tuoPersistenEnumSet
, dichiarareAccessType.PROPERTY
e fornire due metodi di accesso che utilizzano la riflessione per leggere e scrivere il campoelements
- Puoi racchiudere il risultato di
- È necessaria una classe set aggiuntiva per ogni classe enum che deve essere archiviata in un set persistente
- Se il tuo provider di persistenza supporta embeddabili senza un costruttore pubblico, puoi aggiungere
@Embeddable
a... interests = new PersistentEnumSet<>(InterestsEnum.class);
e rilasciare il classe extra (@AttributeOverride
)
- Se il tuo provider di persistenza supporta embeddabili senza un costruttore pubblico, puoi aggiungere
- Devi usare un
values()
, come indicato nel mio esempio, se ne hai più di unoEnumSet.getUniverse()
nella tua entità (altrimenti entrambi sarebbero archiviati nella stessa colonna " elementi ") - L'accesso di
sun.misc
con la riflessione nel costruttore non è ottimale (soprattutto se si guarda alle prestazioni), ma anche le altre due opzioni hanno i loro svantaggi:- Un'implementazione come <=> utilizza una <=> classe
- Fornire l'array dei valori come parametro ha il rischio che i valori indicati non siano quelli corretti
- Sono supportati solo enum con un massimo di 64 valori (è davvero uno svantaggio?)
- Invece potresti usare BigInteger
- Non è facile utilizzare il campo degli elementi in una query di criteri o JPQL
- È possibile utilizzare operatori binari o una colonna maschera di bit con le funzioni appropriate, se il database lo supporta
Le raccolte in JPA si riferiscono a relazioni uno-a-molti o molti-a-molti e possono contenere solo altre entità. Scusa, ma dovresti avvolgere quelle enumerazioni in un'entità. Se ci pensate, avreste bisogno di una sorta di campo ID e chiave esterna per memorizzare comunque queste informazioni. Questo a meno che tu non faccia qualcosa di folle come memorizzare un elenco separato da virgole in una stringa (non farlo!).