Untitled Document
   
 
Usare Hibernate3 come frameworkJDBC
di Gaving King traduzione di Marco Castaldi

Recentemente alcuni semplici framework JDBC, come iBATIS, hanno fatto parlare parecchio di sé. Personalmente, mi è piaciuta l'idea di utilizzare iBATIS in applicazioni che non necessitano di un modello di dominio orientato agli oggetti e che non lavorano in contesti in cui all'interno di una singola transazione vi siano entità associate a grafi molto profondi. Un framework JDBC può essere particolarmente adatto se si sta lavorando con una qualche forma di "insano" legacy database; infatti le soluzioni ORM tendono ad assumere che le associazioni siano rappresentate come chiavi esterne pulite aventi vincoli referenziali corretti (in Hibernate3 molto meno che in Hibernate 2.x).

Alcuni sostengono che i framework JDBC siano una valida alternativa all'ORM anche per quei sistemi in cui lo stesso ORM si adatta perfettamente, ossia quelle applicazioni con degli schemi object oriented molto puliti. Essi supportano tale affermazione sostenendo che ci sia sempre da guadagnare nell'utilizzare del codice SQL scritto a mano piuttosto che generato in maniera automatica. Bene, io penso che ciò non sia vero, non soltanto perché una gran parte del codice SQL necessario nella maggioranza delle applicazioni è molto noioso da scrivere, e semplicemente non richiede l'intervento umano, ma soprattutto perché un framework JDBC opera ad un livello semantico diverso rispetto ad un ORM. Infatti, una soluzione come iBATIS conosce molto poco della semantica del codice SQL che sta utilizzando, così come i relativi dataset.
Ciò significa che si hanno a disposizione molte meno possibiltà per poter effettuare una qualche forma di ottimizzazione, come ad esempio un caching efficiente.
Con "efficiente" mi sto riferendo principalmente alle strategie di invalidazione della cache, che sono cruciali per la sua effettiva utilità.
Inoltre, ogni volta che abbiamo visto del codice SQL scritto a mano, abbiamo anche incontrato N+1 problemi di select: è estremamente noioso scrivere una nuova query SQL per ogni combinazione di associazioni che potrebbe essere necessario dover recuperare.
HQL è molto meno prolisso, per cui rappresenta un aiuto significativo in simili situazioni.
Affinché possa effettuare lo stesso tipo di ottimizzazioni di un ORM, un framework JDBC dovrebbe evolvere verso un altrettanto simile livello di sofisticazione. Essenzialmente, esso dovrebbe essere uguale ad un ORM, tranne che per la generazione automatica del codice SQL: a dimostrazione di questo fatto molti framework JDBC esistenti hanno già iniziato ad evolvere in tale direzione. Questa evoluzione inizia però ad erodere uno dei benefici dichiarati di tali framework: la rivendicata semplicità.

Questo inoltre solleva la seguente interessante considerazione: se, aggiungendo gradualmente funzionalità, un framework JDBC diventerà alla fine molto simile ad un ORM, escludendo la generazione del codice SQL, allora perché non utilizzare semplicemente una soluzione ORM esistente come, ohh, um … Hibernate, forse … eliminando la generazione dell'SQL?

Il team di Hibernate ha lungamente esaminato la possibilità di mescolare e di armonizzare il codice SQL generato con delle query occasionalmente scritte a mano. Nelle versioni più vecchie di Hibernate, la nostra soluzione è stata semplicemente quella di esporre le connessioni JDBC che Hibernate stava usando, in modo da poter eseguire gli statement preparati dall'utente. Questa strategia è iniziata a cambiare un po' di tempo fa, e Max Andersen ha la-vorato molto al riguardo. Attualmente, in Hibernate3, è possibile scrivere una intera appli-cazione in completa assenza di codice SQL generato automaticamente, mantenendo i vantaggi portati da tutte le altre caratteristiche di Hibernate.

Ma realmente ci aspettiamo o vogliamo che si usi Hibernate in questo modo? Beh, non proprio.
D ubito che ci siano molte persone che realmente si divertano a scrivere dei noiosissimi comandi di INSERT, UPDATE, DELETE tutto il giorno! D'altro canto, noi riteniamo che soltanto una quantità modesta di persone abbia realmente bisogno di adattare le query alle proprie esigenze.
Comunque, per dimostrare il punto della questione, mostrerò nel seguito come realizzare tale adattamento, sempre che si voglia realmente effettuarlo.

Iniziamo considerando un modello di dominio semplice, come ad esempio Person-Employment-Organization. (Il codice di questo esempio si trova nel package org.hibernate.test.sql, per cui non verrà riprodotto in questo articolo.) La classe più semplice è Person, il cui mapping è il seguente:

<class name="Person" lazy="true">
<id name="id" unsaved-value="0">
<generator class="increment"/>
</id>

<property name="name" not-null="true"/>

<loader query-ref="person"/>

<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>

Si noti che i comandi INSERT, UPDATE, DELETE non sono generati, ma scritti a mano dallo sviluppatore. L'ordine dei caratteri ? corrisponde all'ordine in cui le rispettive proprietà sono listate nelle righe precedenti (prima o poi dovremmo supportare dei parametri nomi-nativi, suppongo). Immagino che non ci sia nulla di interessante in questo spezzone di codi-ce.

Più interessante è il tag <loader>: esso definisce un riferimento ad una named query che dovrà essere utilizzata ogni volta che carichiamo un persona utilizzando get(), load(), oppure nel caso di recupero dati basato su associazioni lazy. In particolare la named query può essere scritta in codice SQL nativo, che in questo caso è il seguente:

<sql-query name="person">
<return alias="p" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {p.name}, ID AS {p.id} FROM PERSON WHERE ID=? FOR UPDATE
</sql-query>

(Una query in codice SQL nativo solitamente ritorna "colonne" di entità; quello proposto qui è il caso più semplice, in cui il risultato della query è una singola entità.)

Employment è leggermente più complessa, visto che non tutte le proprietà sono incluse nei comandi di INSERT e nell'UPDATE:

<class name="Employment" lazy="true">
<id name="id" unsaved-value="0">
<generator class="increment"/>
</id>

<many-to-one name="employee" not-null="true" update="false"/>
<many-to-one name="employer" not-null="true" update="false"/>
<property name="startDate" not-null="true" update="false"
insert="false"/>
<property name="endDate" insert="false"/>
<property name="regionCode" update="false"/>

<loader query-ref="employment"/>

<sql-insert>
INSERT INTO EMPLOYMENT
(EMPLOYEE, EMPLOYER, STARTDATE, REGIONCODE, ID)
VALUES (?, ?, CURRENT_DATE, UPPER(?), ?)
</sql-insert>
<sql-update>UPDATE EMPLOYMENT SET ENDDATE=? WHERE ID=?</sql-update>
<sql-delete>DELETE FROM EMPLOYMENT WHERE ID=?</sql-delete>
</class>

Il mapping per Organization ha una collection di Employments:

<sql-query name="employment">
<return alias="emp" class="Employment"/>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, ID AS {emp.id}
FROM EMPLOYMENT
WHERE ID = ?
</sql-query>


Non soltanto c'è una <loader> query per Organization, ma anche per la sua collection di Employments:

<sql-query name="organization">
<return alias="org" class="Organization"/>
SELECT NAME AS {org.name}, ID AS {org.id} FROM ORGANIZATION
WHERE ID=?
</sql-query>

<sql-query name="organizationEmployments">
<return alias="empcol" collection="Organization.employments"/>
<return alias="emp" class="Employment"/>
SELECT {empcol.*},
EMPLOYER AS {emp.employer}, EMPLOYEE AS {emp.employee},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, ID AS {emp.id}
FROM EMPLOYMENT empcol
WHERE EMPLOYER = :id AND DELETED_DATETIME IS NULL
</sql-query>

Mentre scrivevo questo codice, ho iniziato a percepire realmente quale vantaggio sia avere Hibernate che scrive il codice SQL per me. Già nella stesura di questo semplice esempio, a-vrei eliminato più di 35 linee di codice che dovrò invece provvedere a mantenere in futuro.

Infine, per quanto riguarda la definizione di query specifiche, possiamo definire le interrogazioni in SQL nativo (una named query, oppure una query incapsulata all'interno di codice Java). Ad esempio:

<sql-query name="allOrganizationsWithEmployees">
<return alias="org" class="Organization"/>
SELECT DISTINCT NAME AS {org.name}, ID AS {org.id}
FROM ORGANIZATION org
INNER JOIN EMPLOYMENT e ON e.EMPLOYER = org.ID
</sql-query>

Personalmente, preferisco programmare in Java piuttosto che in XML, per cui ritengo che il codice presentato in questo articolo sia troppo basato su XML per essere di mio gradimento. Penso che rimarrò attaccato alla generazione automatica dell'SQL, ogni volta che mi sarà possibile, ossia quasi sempre. Non è che non mi piace l'SQL. Io ammiro l'SQL, e mi piace guardare le query generate da Hibernate durante il suo funzionamento. E' solo che Hiberna-te è molto più bravo a scrivere query di quanto non lo sia io.

 

L'articolo è stato originariamente pubblicato su TheServerSide.com