JPA 2.1 Attribute Converters

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<O, R> dove O è il tipo nel mondo ad oggetti, R nel mondo relazionale:

@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;
    }
}

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!!

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+