Ormai si sa, molto spesso la piattaforma Java Enterprise prende, migliora e rende standard ciò che c’è di buono in giro e che alcuni vendor hanno sviluppato per spingere le specifiche Java EE sempre avanti con soluzioni praticamente già consolidate e apprezzate. Un esempio tra questi sono gli Attribute Converters JPA, già implementati in EclipseLink e a modo suo anche in Hibernate. Ma di cosa si tratta?
ORM sui tipi dei dati
Il famoso problema dell’Object-Relational-Mapping non si limita solo a mappare la struttura dei dati a livello di entità, ma anche il tipo degli attributi dell’entità, che può capitare che non coincidano (o non siano compatibili) tra il modello relazionale e quello ad oggetti. Prendiamo un semplice esempio: se per vincoli progettuali, o per legacy, siete costretti ad avere un database le cui colonne che sarebbero dovute essere di tipo booleano, in realtà assumono i soli valori testuali S o N, come riportare specificità nel modello ad oggetti? Non vorrei proprio creare un enum BooleanType.S e BooleanType.N o peggio ancora usare una stringa, quindi che soluzione ci rimane?
E’ molto tempo che EclipseLink e Hibernate hanno sviluppato una soluzione proprietaria al problema: il primo tramite i Converters e le annotation correlate, il secondo tramite i Custom Types.
Abbiamo dovuto attendere la versione 2.1 della specifica JPA perché si arrivasse ad una soluzione standard tramite gli Attribute Converters.
JPA Attribute Converters nel dettaglio
Riprendiamo l’esempio iniziale: se per esempio abbiamo una entità:
@Entity public class Box implements Serializable { ... private Boolean delivered; }
dove la corrispondente tabella sul database deve avere la colonna “delivered” come valore testuale S o N, come risolvo il problema della differenza tra tipi e valori? Da JPA 2.1 basta implementare l’interfaccia AttributeConverter
dove O è il tipo nel mondo ad oggetti, R nel mondo relazionale:
@Converter public class BooleanConverter implements AttributeConverter{ @Override public String convertToDatabaseColumn(Boolean attribute) { return attribute != null ? (attribute ? "S" : "N") : null; } @Override public Boolean convertToEntityAttribute(String dbData) { return dbData != null ? "S".equals(dbData) : null; } }
L’interfaccia espone due metodi che servono per la conversione bidirezionale Boolean < -> String. Definito il converter, va attivato tramite l’annotation @Converter
.
Tutto qui? Ci siamo quasi. Adesso come facciamo a dire a JPA dove applicarlo? Esistono due modi. L’annotation @Converter
ha un attributo autoApply che di default vale false:
- se autoApply=false (default): è necessario specificare esplicitamente dove applicare il converter:
@Entity public class Box implements Serializable { ... @Convert(converter = BooleanConverter.class) private Boolean delivered; }
- se autoApply=true: il converter si attiva automaticamente su tutti i gli attributi di tipo booleano che trova nel contesto di persistenza.
Semplice no?
Conclusioni
Si poteva fare di più: la specifica purtroppo è stata piuttosto timida nel rendere standard quello che già i vendor avevano implementato. Questa implementazione infatti somiglia molto a quella di EclipseLink, ma quest’ultima rimane comunque più versatile. Con EclipseLink per esempio è possibile mappare l’esempio precedente con poche righe di xml o annotations tramite gli ObjectTypeConverters. Con i Composite User Types di Hibernate invece è possibile mappare più colonne in un unico tipo Java: situazioni forse non molto frequente ma quando si ha a che fare con un database legacy è possibile che ce ne sia bisogno.
Apparte quindi quest’ultimo caso, l’implementazione standard risulta basilare ma spesso più che sufficiente: con un po’ di codice (relativamente semplice) è possibile risolvere i problemi di tutti i giorni, come per esempio mappare gli enum in modo molto più versatile rispetto allo standard @Enumerated
!!