martedì 29 luglio 2008

Hibernate, session, Dynamic Methods + Inject

Ci siamo accorti che così come utilizziamo noi sql, creiamo una differente connessione al database per ogni servizio, cosa non molto gradevole in effetti.
Infatti, sino ad ora, abbiamo raramente usato GORM e Hibernate, data la "stramba" costruzione del database già esistente per il programma (id stringa, più campi per chiavi primarie, eccetera), e quindi risulta più "facile" usare sql diretto piuttosto che gorm e hibernate. Quindi usiamo groovy.sql.Sql per la creazione di query sql on the fly.
Abbiamo tentanto la strada della memorizzazione delle sessioni nella session http, cosa che funziona, ma rende poco pratica la gestione delle sessioni stesse, perchè ogni volta che serve richiamare la connessione, bisogna scrivere svariate righe di codice, per ogni metodo dei dervizi che utilizzano sql...
Per farlo abbiamo usato questa strategia: nella prima connessione al database creata, quella del login, creiamo un istanza SQL come normalmente si dovrebbe fare in ogni service
import groovy.sql.Sql
def sql = Sql.newInstance("jdbc:jtds:sqlserver://192.168.0.22/sacco;instance=SQLSSE", "sa", "fdl", "net.sourceforge.jtds.jdbc.Driver")
e poi la memorizziamo nella sessione come altre informazioni (user, profilo, eccetera)
def session = RCH.currentRequestAttributes().session
session.connessioneDB = sql
E il tutto sembra funzionare... per poi richiamare questa connessione sql, basta fare in ogni servizio
def session = RCH.currentRequestAttributes().session
def sql= session.connessioneDB
per poi usare l'oggetto sql normalmente (quindi sql.execute eccetera)
Questo portava anche al vantaggio si avere la stringa di connessione al database in uan sola classe, e non in ogni servizio (maggiore mantenibilità)

Abbiamo quindi provato un'altra strada... essendoci hibernate comodamente pronto nel framework grails, abbiamo provbato ad usare le sessioni di hibernate per fare una cosa simile, in più abbiamo provato ad utilizzare i metodi dinamici di groovy iniettando nei servizi un oggetto sql già pronto all'uso... il tutto funziona... solo che non usando nativamente hibernate (ma lo usiamo solo per ricavare connessioni e non per gestire gli accessi al database) pare che non si riescano a recuparare connessioni univoche... ma che hibernate ne crei una nuova ogni volta che viene invocato con il metodo che più avanti spieghiamo.
E' stato comunque interessante provare questo approccio perchè ci ha permesso di verificare la possibilità di iniettare metodi (e proprietà) in svariate classi (singole, nominali, tutti i servizi, i controller, le classi di dominio eccetera)... ed addirittura provare la creazione di plugin (che pare essere l'unica strada efficace per effettuare l'iniettamento di metodi dinamici)
Questo approccia, nonostante non persegue il fine di riutilizzare una connessione unica, ha il vataggio di mettere a disposizione l'oggetto sql ad ogni servizio, senza riga di codice alcuna, e di utilizzare il file DataSource.groovy normalmente presente nei progetti grails... così si possono utilizzare i tre environment di questo file (development, production, test)

Abbiamo quindi creato un nuovo plugin in (seguendo la procedura indicata qui: http://docs.codehaus.org/display/GRAILS/The+Plug-in+Developers+Guide), dentro al quale abbiamo inserito il seguente codice:

def doWithDynamicMethods = {applicationContext ->
System.err.println("il metodo dinamico è partito1");
def sf = applicationContext.sessionFactory
com.fdlservizi.sse.SSEService.metaClass.getDbConn = {->
def sessione = sf.currentSession
def connessione = sessione.connection()
return connessione
}
}

Il quale inietta nella classe SSEService il metodo gerDbConn. In Grails inoltre vige la convenzione che ad ogni metodo get e set, corrisponda immediatamente, anche se non dichiarata, la relativa proprietà... quindi, specificando per una determinata classe il metodo getProprietà e/o setProprietà, automaticamente si può richiamare il metodo getProprietà semplicemente definendo: def proprietà (oppure utilizzando direttamente proprietà)

La classe SSEService, posta nella directory src/groovy/com.fdlservizi.sse/ è così composta


package com.fdlservizi.sse

import groovy.sql.Sql
import java.sql.Connection

class SSEService {

Sql getSql(){
def globalSql = new Sql((Connection)dbConn)
globalSql.execute("set dateformat ymd")
System.err.println("connection = " + (Connection)dbConn);
return globalSql
}

Connection getConnection(){
System.err.println("connection = " + dbConn);
return dbConn
}

org.hibernate.impl.SessionImpl getDbSession(){
return dbSess
}
}

In questa classe, quindi, per quello detto prima, semplicemente richiamando "dbConn", è come se richiamassimo getDbConn iniettato dal plugin.
Questa classe la usiamo poi per estendere ogni servizio che intenda usare una connessione sql... in questo modo, all'interno del servizio, possiamo semplicemente usare la sintassi che fino a quel momento usavamo, senza mai dichiarare l'oggetto sql.... perchè di fatto iniettato nella classe che estendono, quindi ereditato

In ogni servizio infatti si può tranquillamente scrivere, per esempio
sql.eachRow("select * from imp_tipo")
e si sottoindente GetSql, che a sua volta, sottointende getDbConn, che a sua volta sottointende applicationContext.sessionFactory.currentSession.connection() che restituisce la connessione di hibernate (presa dal file DataSource.groovy)
Questo però, come detto prima, al contrario delle nostre aspettative, sembra aprire sempre nuove connessioni.
Inoltre, nella classe SSEService, facciamo ad ogni richiesta di connessione, l'esecuzione del setdateformat, necessario per l'esecuzione delle nostre query, anche questo vantaggio notevole, dato che senza questo accorgimento bisognava eseguirla, in ogni servizio che utilizzava le date (DateTime, TimeStamp) e simili nelle query.

Abbiamo comunque deciso di concetrarci ora allo studio del dialetto di Hibernate, per utilizzare direttamente le sue potenzialita, demandando l'uso di sql diretto solo nei momenti in cui sarà strettamente necessario. Questo ci permetterà, presumibilmente, di demandare ad hibernate la gestione del pool di connession al database e le differenze tra i due database che dovremmo abdare a supportare (sql server e oracle).