Postato il 12 Novembre 2012 da Alessio Stalla

Hai probabilmente familiarità con almeno un modo per configurare Hibernate, che sia usando l'XML o le annotazioni. In questo articolo, tuttavia, esamineremo un metodo che molto probabilmente non conosci: costruire i metadati della configurazione Hibernate programmaticamente.

Per chiarezza, non stiamo parlando della configurazione programmatica descritta nella documentazione di Hibernate, che è limitata all'aggiunta dinamica di file di mappatura o classi annotate e al settaggio delle proprietà di configurazione di Hibernate. Stiamo parlando di sporcarci le mani con le classi interne di Hibernate!

Addentrarsi all'interno di Hibernate può essere un utile esercizio per capire meglio come Hibernate modella le tabelle della base dati e le entità Java persistenti. In questo articolo, esploreremo la costruzione programmatica di una mappatura Hibernate così come è implementata in Portofino 4. Anche se non è necessariamente esaustiva, questa analisi approfondita può essere utile come introduzione dettagliata all'implementazione interna di Hibernate, la quale è, per quanto ne sappiamo, non documentata. Nota: al momento della scrittura di questo articolo, in Portofino utilizziamo Hibernate 3.6.9, ma il codice qui riportato funziona con Hibernate almeno fino alla versione 4.1.8, attualmente l'ultima disponibile su Maven Central.

Il modello dei dati

Hibernate realizza una Mappatura tra il mondo Object-Oriented e il mondo Relazionale (da qui la dicitura ORM); non ci sorprende scoprire, dunque, che il modello dei dati che utilizza per la sua configurazione può essere suddiviso in un modello “fisico” che descrive la base dati relazionale, ed un modello “logico” che rappresenta le entità Java mappate. Entrambi i modelli sono implementati dalle classi nel package org.hibernate.mapping.

Il modello fisico si occupa di Tabelle, Colonne, Chiavi Primarie ed Esterne, ecc. Esso impone poche restrizioni sulla struttura della base dati; la più importante è il vincolo per cui ogni tabella deve avere una chiave primaria perché Hibernate sappia mapparla.

Il modello logico, invece, si occupa di Classi, Proprietà e Collezioni, anche se, come vedremo, Hibernate sa mappare le entità relazionali non solo su Java bean, ma anche su altre strutture dati. Il modello logico è sostanzialmente un Decorator per il modello fisico: in genere, ogni oggetto logico si riferisce direttamente o indirettamente ad uno o più oggetti fisici corrispondenti (ad es. ogni Classe racchiude una Tabella). Ciò detto, la separazione tra i due modelli non è netta; il grafo degli oggetti ha molte interconnessioni, e non è raro che un oggetto che rappresenta un'entità fisica abbia un riferimento ad un'entità logica.

In ultima istanza, i due modelli sono contenuti in un singolo oggetto, un'istanza della classe org.hibernate.cfg.Mappings, come si può vedere da questa visione ad alto livello dei passi coinvolti nella creazione di una SessionFactory, presi dal codice sorgente di Portofino:

Configuration configuration = new Configuration();

setupConnection(configuration);
setupConfigurationProperties(configuration);

Mappings mappings = configuration.createMappings();
//Class Mapping
classMapping(database, mappings);
//One2Many Mapping
o2mMapping(database, configuration, mappings);
//Many2One Mapping 
m2oMapping(database, configuration, mappings);

Il modello fisico: tabelle e colonne

Il modello fisico rispecchia la struttura della base dati. Alla radice del modello c'è un insieme di oggetti Table. Ogni tabella è un'aggregazione degli oggetti del database ad essa collegati – al momento della stesura di questo articolo, Portofino conosce soltanto colonne e chiavi (primarie ed esterne), ma il modello di Hibernate può mantenere informazioni anche riguardo a indici e vincoli.

Segue la procedura che utilizziamo per configurare una tabella:

  1. Creare una Table dai Mappings:

    Table table = mappings.addTable(schemaName, null, tableName, null, false);

    I parametri del metodo addTable sono schema, catalogo, nome, subselect, abstract, ma noi passiamo solamente schema e nome. Subselect è utilizzato per definire tabelle logiche (un po' come le viste, ma non memorizzate nella base dati). is used to define logical tables (much like views, but not stored in the database). Abstract serve per definire classi astratte, che non corrispondono ad alcuna tabella sulla base dati

  2. Creare la chiave primaria. Questa è un'operazione relativamente compless che attraversa i confini tra il modello fisico e quello logico, e la vedremo dettagliatamente in seguito.

  3. Creare le colonne:

    Column col = new Column();
    col.setName(name);
    col.setLength(len);
    col.setPrecision(len);
    col.setScale(scale);
    col.setNullable(nullable);
    col.setSqlTypeCode(jdbcType);
    col.setSqlType(columnType);
    

    Di nuovo, le colonne che fanno parte della chiave primaria richiedono attenzioni particolari, ma ce ne occuperemo in seguito.

  4. Aggiungere le colonne alla tabella e all'oggetto Mappings:

    table.addColumn(col);
    mappings.addColumnBinding(columnName, column, table);

Dopo esserci occupati del modello fisico, possiamo ora concentrarci sull'altra metà del mondo...

Il modello logico: classi e proprietà

Il modello logico si occupa di classi e proprietà. La parola “classi” va intesa in senso generico, dato che non ci riferiamo necessariamente a classi Java (anche se quello è l'utilizzo più comune di Hibernate). Infatti, di base Hibernate può mappare su:

  • classi Java
  • HashMaps
  • nodi DOM4J (XML)

In Portofino 4, usiamo le mappe di default, dato che richiedono un setup più semplice e sono comunque accessibili con una sintassi piacevole in Groovy. Si possono fornire le proprie classi Java se si vuole, e si possono anche mischiare i due approcci.

Le classi in Hibernate sono modellate come istanze di PersistentClass. Questa classe astratta ha diverse sottoclassi, ma in questo articolo trattiamo solo una di queste, RootClass, che è utilizzata nella maggior parte dei casi. Le altre sottoclassi di PersistentClass servono a gestire vari tipi di ereditarietà nel proprio modello a oggetti.

Questo è il codice che usiamo in Portofino per configurare una RootClass:

  1. Crearla e impostare le proprietà di base:

    RootClass clazz = new RootClass();
    clazz.setEntityName(entityName);
    clazz.setJpaEntityName(entityName);
    if (javaClass != null) {
        clazz.setClassName(javaClass);
        clazz.setProxyInterfaceName(javaClass);
    }
    clazz.setLazy(lazy);
    clazz.setTable(table);

    Come si può vedere, mappiamo su una classe Java solo se viene fornito il parametro javaClass. Altrimenti mappiamo su HashMap.

  2. Aggiungere una proprietà per ogni colonna:

    Property prop = new Property();
    prop.setName(propertyName);
    clazz.addProperty(prop);
  3. Aggiungere la classe ai Mappings:

    mappings.addClass(clazz);
    mappings.addImport(clazz.getEntityName(), clazz.getEntityName());

La mappatura su HashMap è più dinamica, mentre quella su classi Java è tipata più fortemente. Resta a te la decisione su quale approccio usare.

Un ponte tra i due modelli: i valori

Fino ad ora, abbiamo visto gli elementi base del modello di Hibernate, ma abbiamo saltato alcuni passi importanti. Ad esempio, come sono collegate le colonne e le proprietà? Per mezzo di un'astrazione chiamata Value, che è l'argomento di questa sezione.

Un Value rappresenta qualsiasi cosa che possa essere memorizzata come il valore di una colonna, inclusi valori scalari (numeri, stringhe, date, ecc.) e collezioni (bag, set, liste, ecc.). Concentriamoci sui valori scalari per ora: ci occuperemo quindi di una sola implementazione dell'interfaccia Value, SimpleValue.

Questi sono i passi che seguiamo per creare un SimpleValue:

SimpleValue value = new SimpleValue(mappings, tab);
value.setTypeName(typeName);
if(typeParams != null) {
    value.setTypeParameters(typeParams);
}
value.addColumn(col);
prop.setValue(value);

Come si può vedere dall'invocazione di addColumn(), il valore ha una lista di colonne (anche se generalmente la colonna è una sola) ed è contenuto in una Property. Si comporta da ponte tra il modello logico e quello fisico.

I valori semplici hanno un altro valore fondamentale: mantengono l'informazione relativa al tipo di dato Hibernate della proprietà associata. Portofino adotta alcune euristiche per determinare il tipo corretto a partire dai metadati del database, permettendo comunque all'utente di personalizzare la mappatura, ma non parleremo di tali euristiche.

typeName è il nome di un tipo Hibernate (una derivata di org.hibernate.type.Type, ad es., org.hibernate.type.IntegerType). Un tipo Hibernate è un collegamento tra un tipo JDBC e un tipo Java.

Nel prossimo articolo: chiavi e relazioni

Seguirà un secondo articolo che tratterà le chiavi primarie e le relazioni - le informazioni chiave cui abbiamo accennato in questo articolo ma che non abbiamo ancora coperto. Rimanete sintonizzati!

 

comments powered by Disqus