Hibernate + PostgreSQL, prove di convivenza
Antefatto
Tempo, lavorando sul mio progettino open source StudioDix, sono incappato in un "problema" inusuale, relativo alla convivenza fra Hibernate (o più in generale a JDBC) e il noto database PostgreSQL.
Premessa: StudioDix utilizza Appfuse v2.x, il quale a sua volta utilizza Maven2 come tool di build. Maven2 consente con un semplice comando di eseguire la compilazione, la creazione del database di test, l'esecuzione dei test e l'impacchettamento dell'applicazione. Molto comodo!
Il mio problema si verificava eseguendo il build con il profilo "postgresql"; Maven infatti consente di definire dei profili di build dell'applicazione, nel mio caso ci sono un profilo che utilizza MySql, uno che usa H2, un altro che usa PostgreSQL, Oracle, eccetera. Il progetto è sviluppato principalmente su MySql, ma ogni tanto faccio dei regression test per vedere se ho perso la compatibilità con qualche DB, il che succede praticamente nove volte su dieci :-)
L'errore "fantasma"
Per farla breve, i test con Postgres fallivano, dando questo errore:
SQL state [25P02]; error code [0]; ERROR: current transaction is aborted, commands ignored until end of transaction block
Non solo, ignorando i test e installando, l'applicazione funzionava correttamente!
Per un po' ho ignorato il problema, ma il tarlo non la smetteva di... tarlare, allora mi sono un po' documentato; devo confessare che le scoperte che ho fatto all'inizio mi hanno un po' sorpreso, poi però mi hanno fatto apprezzare ancora di più Postegres.
La soluzione
Ho scoperto in sostanza che Postgres durante una transazione si comporta, pensate un po', come un database! Ciò vuol dire che se succede un errore, tutto ciò che viene eseguito dopo viene ignorato e la transazione mandata in rollback. Tutto ciò sembra ovvio forse, mavi assicuro che non lo è per tutti. Se ad esempio io catturo la SQLException che viene lanciata, la ignoro e vado avanti, cosa mi dovrei aspettare? Vediamo un esempio vero, tratto dal codice di StudioDix.
Questo metodo di test veniva utilizzato per verificare l'integrità referenziale, tentando la cancellazione di un record referenziato da altri, scatenando quindi una ConstraintViolationException (specifica di Hibernate):
protected void checkCannotDelete(Class c, Serializable id) {
try {
daoFactory.getUniversalDao().remove(c, id);
daoFactory.flush();
fail("ConstraintViolationException expected: delete " + c.getName() + " id = " + id.toString());
} catch (ConstraintViolationException cve) {
// ok
}
}
Questo codice veniva utilizzato in alcuni metodi che poi facevano altri controlli, quindi altre query/operazioni sul database, ad esempio:
public void test_remove_Client() throws Exception {
if (log.isDebugEnabled()) {
log.debug("test_remove_Client()");
}
// because used in Project [-3]
checkCannotDelete(Client.class, -4L);
...
[ALTRE OPERAZIONI
...
}
Con MySql, H2 e HSQLDB non ho mai avuto problemi: questi test sono sempre andati lisci, avevo solamente un sacco di stacktrace negli output dei test.
Invece con Postgres no, mi faceva comparire un sacco di errori proprio dopo il codice che verificava l'integrità! E lo sapete perché? Perché Postgres è un database vero, che rispetta appieno l'acronimo ACID per le transazioni: Atomicity, Consistency, Isolation, Durability sono tutte rispettate appieno. Soprattutto la "D"!!! Cito Wikipedia:
Durability refers to the guarantee that once the user has been notified of success, the transaction will persist, and not be undone. This means it will survive system failure, and that the database system has checked the integrity constraints and won't need to abort the transaction.
Quindi, ricapitolando, per far funzionare nuovamente il mio codice ho dovuto trattare le SQLException come errori fatali (ed in effetti lo sono!), evitando di ignorarle e gestendo opportunamente il flusso di esecuzione. Nel mio caso si è trattato semplicemente di suddividere i metodi di test in più parti: una che verifica l'integrità, un'altra che verifica la cancellazione "pulita", un'altra per le letture, e così via.
Penso che in futuro il database di default diventerà questo simpatico elefantone :)
- dimitri's blog
- Aggiungi un commento
- 1336 letture
-
Potresti essere interessato anche a...
- How to configure relative paths for Log4j using Spring
- Absent Code attribute in method that is not native or abstract in class file javax/servlet/ServletException
- Configurare path relativi per Log4j utilizzando Spring
- Absent Code attribute in method that is not native or abstract in class file javax/servlet/ServletException
- Impariamo ad usare The Play Framework - III parte
