ponedeljek, 7. april 2008

Named SQL queries

Hibernate je ena tako zelo zanimiva stvarca. Zadnje čase veliko delam z njim in ga vedno bolj spoštujem kot produkt in rešitevlja v težkih trenutkih razvoja programske opreme. Danes bi rad napisal nekaj o sposobnosti Hibernate-a, da izvaja nativne SQL poizvedbe, pod lastnim okriljem (znotraj Hibernate-a). Lastnost je zelo koristna, v primeru, da je potrebno sestaviti SQL poizvedbo, ki je kompleksna iz zapis v HQL ali pa enostavno naredi nekaj posebnega. Zadnje čase se sam zatekam k tej metodi, ker enostavno nimam modeliranih vseh tabel, ki jih uporabljam pri poizvedbah. Ne da se mi modelirat vseh tabel, ki jih uporabljam. (Dolga zgodba: baza ni niti približno programerju prijazna...).
Tako sem začel polniti svoje "mapping" datoteke z SQL poizvedbami, ki sem jih opisal v Hibernate-u razumljiv način.
Primere bom jemal iz uradne Hibernate dokumentacije za Named SQL queries.
Pa poglejmo prvi primer:
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string"/>
<return-scalar column="age" type="long"/>
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
</sql-query>

Primer opisuje SQL poizvedbo poimenovano mySqlQuery, ki ima dva stolpca name in age, s svojima podatkovnima tipoma. Podatkovni tipi so Hibernate podatkovni tipi: obstaja logična preslikava v Java podatkove tipe, seveda se lahko uporablja tudi direktno Java podatkovni tip, npr. namesto string bi lahko bil java.lang.String. Takšna poizvedba, je "veljavna" Hibernate poizvedba in jo lahko vključimo v druge dele Hibernate nivoja - Hibernate pozna vhod in izhod. Hitro sem se navadil na tak način dela, ker je dokaj enostavno. V priljubljenem orodju za delo z SQL-om, sestavim poizvedbo, kopiram v mapirno datoteko. Povem kaj je vhod in izhod in sem gotov.
Pa poglejmo, kako povem Hibernate-u, kaj naj pričakuje kot vhod.
<sql-query name="mySqlQuery">
<return-scalar column="name" type="string" />
<return-scalar column="age" type="long" />
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
AND p.AGE = :age
<query-param name="age" type="long" />
</sql-query>

Tako, parameter je age. Vse zapisano se uporablja v Java kodi na sledeč način:

Query query = null;
query = this.getSession().getNamedQuery("mySqlQuery");
query.setString("prdId", prdId.toString());

Object objResult = null;
objResult = query.uniqueResult();
// ...
Do sem vse lepo in prav. Delo na ta način je zelo prijetno in olajša delo. Bolj ko postanejo poizvedbe zahtevne in obseže, bolj pride v poštev komentiranje kode (kot vedno). Pa poglejmo primer (da se izognem XML formatiranju, bom pisal samo SQL poizvedbo):

-- Prebere ime in starost oseb z imeni, ki se začnejo na Hiber
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'

Če to izvedem v priljubljenem SQL orodju, deluje enako kot prej. Deluje tudi v Hibernate okolju. Pa če rečemo, da postane koda še bolj kompleksna in malo nejasna, opišemo svoje mnenje z dodatnimi komentarji.

-- Prebere ime in starost oseb z imeni, ki se začnejo na Hiber
-- Je to polje prvo ime ali priimek ?
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'

Za potrditev kopiramo poizvedbo v priljubljeno SQL orodje in poženemo. Dobim identičen rezultat. Kopiram v Hibernate mapirno datoteko (prvi primer) in na veliko presenečenje dobim veliko sočno, nelogično izjemo:

org.hibernate.QueryException: Expected positional parameter count: 1, actual parameters: [] [-- Prebere ime in starost oseb z imeni, ki se začnejo na Hiber
-- Je to polje prvo ime ali priimek ?
SELECT p.NAME AS name,
p.AGE AS age,
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'] ...

Sledi obilo solate. V zmedi, kopiram poizvedbo v SQL orodje, poženem in dela. Prenesem nazaj, poženem in ne dela. Prosim?
Hja. Pridemo spet do tega: kaj za koga kaj pomeni oz. pride do očitne težave: kaj bi naj bilo in kaj dejansko je.
Povedano na kratko: Hibernate je vprašaj (?) v komentarju razumel kot pozicioniran parameter in ne kot del komentarja. Vrednosti za najden paremter pa ni najdel. Torej sledi edina logična rešitev: izjema.
Ne vem, če je to napaka ali lastnost Hibernate-a, meni se vsekakor zdi, da je to napaka. A kaj čmo, tako pač je. V dokumentaciji nisem (do sedaj) še nič najdel na to temo, tako da ne znam povedati nič več.

Le toliko za opomnik, da ne bo potrebnega toliko časa, kot sem ga potreboval jaz; da sem razrešil to skrivnost. Naj povem, da so poizvedbe, ki jih imam napisane več 100 vrstic dolge, tako da ni bilo tako preprosti poiskati težav; kot so očitne v primeru, ki sem za zapisal. Da sem rešil ta problem, sem potreboval dobro uro: to je pa res zadnja stvar, ki sem jo pričakoval. Vse je enkrat prvič.