Newsgroup: it.comp.java
Titolo: Enums da persistere (JPA) come stringhe

Siccome ci sono molti codici alfanumerici da inserire in vari campi di
un DB, ho pensato di ricorrere agli Enum per dare dei nomi simbolici e
rendere più leggibili i sorgenti.
Insomma invece di fare
	o.setType("A1");
poter fare
	o.setType(TipoProdotto.Tastiera);
(o meglio ancora usando l'import static o.setType(Tastiera); ).
Quindi mi sono fatto i miei vari Enum, del tipo (inventato) per
esempio :
public enum TipoProdotto {
    Tastiera("A1", "Tastiera PC"),  Mouse("A2", "Mouse Usb"),
Faro("B1","Faretto da esterni"), Cd("C1", "Compact disk vergine");
    private final String key;
    private final String description;
    TipoProdotto (String key, String description) {
        this.key = key;
        this.description = description;
    }
    public String getTypeCode() {
        return this.key;
    }
    public String getDescription() {
        return this.description;
    }
    @Override
    public String toString() {
        return this.getTypeCode();
    }
}
Bene poi nella Entity (uso Jpa con Hibernate come provider) ho messo
...
    @Enumerated(EnumType.STRING)
    @Column(name = "ProductType")
    private TipoProdotto ProductType;
...
    public void setProductType(TipoProdotto productType) {
        this.cdTACSystemType = productType;
    }
...
Ora nel mio codice posso fare benissimo
o.setProductType(Tastiera); //o è una istanza della mia classe di tipo
entity
il problema è che poi quando cerco di persistere l'oggetto, esso cerca
di scrivere sul DB la stringa "Tastiera" e non "A1". Ed ovviamente non
funziona.
Insomma sembra che voglia persistere il nome e non il valore stringa
del tipo enumerato.
Inoltre purtroppo il metodo name() della classe Enum non può essere
ridefinito (come ho fatto per il metodo toString) perché è un metodo
Final
Ci ho perso un sacco di ore ma senza trovare una soluzione valida.
Si può fare quello che voglio?
E come devo fare ?
Per il momento ho tamponato ridefinendo il campo nell'entity come non
enumerato, ossia
...
    @Column(name = "ProductType")
    private String ProductType;
...
    public void setProductType(String productType) {
        this.cdTACSystemType = productType;
    }
...
e poi mettendo nel codice
o.setProductType(Tastiera.toString());
ma ovviamente cosi' non mi piace per nulla, sia perché devo mettere
per esplicito il toString() (altrimenti non va) sia perché poi posso
passare qualsiasi stringa in input, cioè
o.setProductType("ZZAD");
è lecito, e perdo il vincolo che solo i tipi enumerati sono ammessi.
Ciao
Fabrizio

Non ho trovato la soluzione ufficiale, però ho trovato un workaround
che mi soddisfa.
Ho modificato le mie classi Enum aggiungendoci anche i metodi per il
reverse lookup (che mi sarebbero cmq serviti)
public enum TipoProdotto {
    Tastiera("A1", "Tastiera PC"),  Mouse("A2", "Mouse Usb"),
Faro("B1","Faretto da esterni"), Cd("C1", "Compact disk vergine");
    private final String key;
    private final String description;
    private static final Map<String, TipoProdotto> tmap = new
TreeMap<String, TipoProdotto>();
    //Uso TreeMap e non HashMap perché ho paura di chiavi Hash
duplicate (alias che collidono) dato che
    //le stringhe usate per generare le chiavi (alias A1, A2, B1, C1)
sono troppo corte e simili tra loro
    static {
        for (TipoProdotto obj : TipoProdotto.values()) {
            tmap.put(obj.key, obj);
        }
    }
    TipoProdotto(String key, String description) {
        this.key = key;
        this.description = description;
    }
    public String getCode() {
        return this.key.toString();
    }
    public String getDescription() {
        return this.description;
    }
    public static TipoProdotto getType(String code){
        return tmap.get(code);
    }
	//Tolgo l'override su toString non mi serve più
    //@Override
    //public String toString() {
    //    return this.getTypeCode();
    //}
}
Poi ho optato per rinunciare all'uso di @Enumerated e gestire il campo
come generico String ma specializzando i getter e setter ad usare il
tipo Enum e non il generico String
...
    //@Enumerated(EnumType.STRING)
    @Column(name = "ProductType")
    //private TipoProdotto ProductType;
    private String ProductType;
...
    public void setProductType(TipoProdotto productType) {
        //this.productType = productType;
        this.productType = productType.getCode();
    }
    public TipoProdotto getProductType() {
        return TipoProdotto.getType(this.productType);
    }
...
Ovviamente codice simile c'è anche nei costruttori della classe (o
meglio ancora i costruttori usano i metodi per popolare l'oggetto).
Non so quanto sia efficiente e corretto, ma comunque sembra funzionare
e fare quello che voglio.
Ossia ho la tipizzazione forte (alias non posso passare stringhe
generiche che potrebbero corrispondere a Codici non validi) e contemp.
è migliore la leggibilità dei sorgenti (leggere
objProd.setProductType(Tastiera); è meglio di leggere
objProd.setProductType("A1"); )
Ciao
Fabrizio