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
|