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à:
[java]
@Entity
public class Box implements Serializable {

private Boolean delivered;
}
[/java]
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<O, R> dove O è il tipo nel mondo ad oggetti, R nel mondo relazionale:
[java]
@Converter
public class BooleanConverter implements AttributeConverter<Boolean, String> {
@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;
}
}
[/java]
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:
    [java]
    @Entity
    public class Box implements Serializable {

    @Convert(converter = BooleanConverter.class)
    private Boolean delivered;
    }
    [/java]
  • 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!!