EclipseLink AdditionalJoinExpression: questo sconosciuto

A chi non è mai capitato nelle proprie query di dover aggiungere sempre gli stessi filtri per esempio per non caricare righe con stati non logicamente validi o righe valide in certi intervalli di date? EclipseLink permette di inserire questi filtri una volta per tutte, dimenticandoci di doverci portare sempre il fardello.

Questione di stile

Se JPA o in generale gli ORM hanno risolto il mismatching tra il mondo ad oggetti e quello relazionale, il problema dei diversi metodi di progettazione tra i due mondi è un’altra questione.

Una delle caratteristiche ricorrenti nella progettazione dei database è la presenza di tabelle “storicizzate” e di “stati logici” delle righe. Per un DBA è infatti consuetudine non cancellare mai i dati dal database, ma si tende a nasconderli alla logica applicativa inserendo uno stato particolare o un intervallo di date in cui la riga stessa è valida. Questo approccio è molto utile da un punto di vista di integrità e di debug dei dati, ma lato applicativo costringe a riempire le query di filtri aggiuntivi per non caricare i dati indesiderati. In JPA può essere addirittura frustrante una situazione di questo tipo.

Immaginiamo infatti la relazione one-to-many tra le entità Ordine e DettaglioOrdine, rappresentata come al solito da una lista di DettaglioOrdine:

@Entity
public class Ordine implements Serializable {

   //Elementi di testata...
	
   @OneToMany(mappedBy = "ordine")
   private List<DettaglioOrdine> dettagliOrdine;

   public List<DettaglioOrdine> getDettagliOrdine() {
      return this.dettagliOrdine;
   }
}

@Entity
public class DettaglioOrdine implements Serializable {

   //Elementi di dettaglio...
	
   @ManyToOne
   @JoinColumn(name="id_ordine")
   private Ordine ordine;
	
   @Enumerated(EnumType.STRING)
   private StatoDettaglioOrdine stato;

   //Getters + Setters
}

Chiamando il metodo getDettagliOrdine(), JPA caricherà tutti i dettagli dell’ordine, non curante dell’eventuale stato della riga di dettaglio che la rende non logicamente valida per l’applicativo. Questo tipo di gestione, efficace lato database, in JPA impedisce di usare invece mapping di questo tipo, rendendo la programmazione meno pulita e inutilmente più articolata, perché è necessario ricorrere a query JPQL per caricare i dettagli validi di un ordine.

EclipseLink, aiutami tu!

Per mettere d’accordo tutti, EclipseLink non finisce di stupire! Attraverso il meccanismo della customizzazione dei descrittori delle entità, come già visto molto tempo fa in un’altra occasione, è possibile fare un sacco di cose, come appunto agganciare sempre uno specifico filtro quando si carica una certa entità! Come fare? Basta estendere l’interfaccia org.eclipse.persistence.sessions.factories.DescriptorCustomizer come segue:

public class DettaglioOrdineDescriptorCustomizer implements DescriptorCustomizer {

   @Override
   public void customize(ClassDescriptor descriptor) throws Exception {
      ExpressionBuilder orderDetailEb = new ExpressionBuilder();
      Expression filterExpression = orderDetailEb.get("stato")
                       .notEqual(StatoDettaglioOrdine.ANNULLATO);
      descriptor.getQueryManager()
                       .setAdditionalJoinExpression(filterExpression);
   }
}

Annotiamo poi l’entità DettaglioOrdine con

@Customizer(value = DettaglioOrdineDescriptorCustomizer.class)

in modo da caricare il DescriptorCustomizer all’avvio di EclipseLink e… date un occhiata alle query in console quando chiamate getDettagliOrdine() da Ordine!!

Conclusioni

Tutto questo è possibile grazie al metodo setAdditionalJoinExpression. Lo stesso risultato poteva essere ottenuto tramite la tecnica degli “Amendment Methods” almeno in teoria: il wiki di EclipseLink purtroppo non è molto esaustivo a riguardo.
Comunque sia, come suggerisce il javadoc del metodo setAdditionalJoinExpression, questo è solo uno dei tanti modi in cui questo metodo può essere usato: visto che prende come argomento una Expression, lascio alla vostra immaginazione il resto!!

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+