Categorie
Sviluppo software

EclipseLink JPA, gestire tabelle o viste senza ID univoci

database jpa senza chiave unicaNello sviluppo di web application basate sul framework Java EE utilizziamo l’ORM EclipseLink, nella sua versione 2.0. EclipseLink è un’implementazione delle specifiche JPA (JSR-000317) che ha come obiettivo la definizione di un layer per la gestione della persistenza dotato di funzionalità aggiuntive che rendono più semplice e veloce la gestione della cache ottimizzando le performance.

Quando possiamo progettare da zero la web application strutturiamo il database in modo che ogni tabella sia dotata di una chiave univoca, indispensabile per la definizione delle entity JPA, e del relativo indice, ma succede spesso di dover modificare o prendere in carico lo sviluppo di componenti che si appoggiano su database datati o comunque mal strutturati che includono viste o  tabelle prive di colonne univoche fra i vari record. Vediamo come operare in questi casi e quali sono i limiti.

In generale quando una tabella o una vista è prima di un ID univoco si cerca in prima istanza di capire se un set di colonne può costituire una chiave composita. In pratica, se siamo in grado di definire N colonne tali che la tupla dei loro valori assume valori univoci sui vari record, possiamo risolvere il problema definendo una chiave che “ingloba” tutte le colonne. In questo caso non c’è nessun limite tecnico e una volta definita la chiave composita possiamo sfruttare il 100% delle feature di EclipseLink sulle nostre entity.

JPA, definire chiavi composite

La definizione di chiavi composite si può fare in due modi:

  • utilizzando le annotazioni @Embeddable e @EmbeddedId
  • utilizzando l’annotazione @IdClass

Io utilizzo la prima forma, che trovo più chiara (e soprattutto più semplice da gestire quando mi trovo a dover fare refactoring). In pratica si tratta di definire una classe (che non può essere una inner class) pubblica che raggruppa le colonne della entità che andranno a comporre l’indice, e di annotarla come @Embeddable.


@Embeddable
public class MyPK implements Serializable {

  @Column(name = "COLUMN_A")
  private String columnA;

  @Column(name = "COLUMN_B")
  private String columnB;

  // setter e getter

}

In questo modo nella nostra entity possiamo dichiare un field come segue:


@Entity
@Table(name = "MY_TABLE_NAME")
@XmlRootElement
@NamedQueries({
    @NamedQuery( ... )
    })
public class MyEntity implements Serializable {

  @EmbeddedId
  private VocPK compKey;

  @Column(name = "COLUMN_C")
  private String treno1Numero;

  // altri campi annotati COLUMN

}

In questo modo possiamo gestire entità che hanno una chiave unica composta dalle colonne COLUMN_A e COLUMN_B e che hanno altri campi (es. colonna COLUMN_C) non univoci. Per comodità possiamo definire getter all’interno della classe MyEntity che restituiscono i valori dell’istanza interna della classe MyPK che rappresenta la chiave univoca, nascondendo così al codice chiamante la struttura dell’entità stessa.

L’unica restrizione è che le colonne che fanno parte della chiave composita non possono ammettere valori nulli. Anche se nello schema sono definite come nullable quando e se JPA incontra un valore nullo la query fallisce generando una QueryException.

Questa restrizione in apparenza poco limitante, lo è purtroppo in casi in cui il database non è modificabile e non consente di poter avere tuple che soddisfino contemporaneamente la condizione di non contenere valori nulli ed avere valore unico fra i vari record. In questo caso è necessario aggirare il problema, tenendo ben presente i vincoli tecnici descritti sotto.

JPA, operare con ID non univoci

In questo caso, stabilito che non esiste il modo di definire una chiave univoca quello che è necessario fare è stabilire la chiave composita che, rispettando il vincolo di non avere valori nulli, ha la probabilità più bassa di mostrare valori identici nei result set collegati alle query d’interesse per l’applicazione.

Questo perchè EclipseLink, attraverso la classe EntityManagerFactory, nel calcolare il result set di una query se incontra due record che hanno lo stesso ID la prima volta istanzia una istanza della classe attesa con i valori corrispondenti al record del DB, mentre la seconda volta restituisce dalla cache interna il clone della prima riga, senza guardare il DB. Quindi se la seconda riga differisce dalla prima per qualcosa che non è compreso nella tupla alla base della chiave composita, il risultat set ritornato è diverso dallo stato del DB, con effetti ovviamente nefasti e poco prevedibili. Inoltre, a causa di questa ambiguità le query di update, non sono di fatto possibili.

Minimizzando la probabilità che questo accada, e gestendo a valle della persistenza la probabilità di duplicati indesiderati, è possibile utilizzare in modalità read-only una tabella senza ID univoci, beneficiando ancora del caching e delle performance offerte da JPA.

Un’altra strategia possibile è quella di utilizzare una Native Query istanziata con il metodo createNativeQuery(String s) della classe EntityManagerFactory, ovvero senza passare la Class dell’oggetto da ritornare. In questo caso EclipseLink ritorna il result set sotto forma di array multidimensionale di Object ed è responsabilità dello sviluppatore leggerlo correttamente ed istanziare la classe corretta. In questo caso non ci sono problematiche di id perchè l’intera reflection delle entity viene bypassata demandando la costruzione degli oggetti al codice applicativo. Questa soluzione però a livello di manutenibilità e performance è molto peggiorativa per cui va scelta nel quadro complessivo se i vincoli visti sopra non consentono altri approcci.

ELbuild, sviluppo web application, gestionali ed API REST

ELbuild vanta un’esperienza pluriennale nello sviluppo di web application, sistemi gestionali, ecommerce e API REST su piattaforma Java EE, ed offre garanzie tecniche anche nel caso di subentro ad un precedente fornitore grazie a skill di reverse engineering che consentono di riutilizzare anche codice scarsamente documentato.

Rivolgiti a noi per un parere sul tuo progetto di sviluppo e per sapere cosa possiamo fare per te e per la tua azienda.

Di Luca Adamo

Luca Adamo si è laureato con lode in Ingegneria delle Telecomunicazioni all'Università degli studi di Firenze ed è dottorando in Ingegneria Informatica, Multimedialità e Telecomunicazioni, sempre nella stessa facoltà. Le sue competenze tecniche includono lo sviluppo software, sia orientato al web che desktop, in C/C++ e Java (J2EE, J2SE, J2ME), l'amministrazione di macchine Unix-based, la gestione di reti di telecomunicazioni, ed il design di database relazionali.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.