Capitolo 3. Configurazione della SessionFactory

Poiché Hibernate è progettato per funzionare in molti ambienti differenti, ci sono un gran numero di parametri di configurazione. Fortunatamente, la maggior parte hanno dei valori predefiniti, e Hibernate viene distribuito con un file hibernate.properties di esempio che mostra le differenti opzioni possibili. Solitamente è sufficiente mettere quel file nel classpath e applicare le modifiche necessarie per il proprio ambiente.

3.1. Configurazione programmativa

Una istanza di net.sf.hibernate.cfg.Configuration rappresenta un insieme completo di mappaggi dei tipi java di una applicazione verso un database SQL. La Configuration viene usata per costruire un oggetto SessionFactory (immutabile). I mappaggi vengono compilati dai vari file di configurazione XML.

Potete ottenere una istanza di Configuration istanziandola direttamnete. Qui di seguito c'è un esempio di impostazione di un contenitore di dati a partire da dei mappaggi definiti in due file di configurazione XML (che si trovano sul classpath):

Configuration cfg = new Configuration()
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml");

Una maniera alternativa (e in certi casi migliore), è di fare in modo che Hibernate carichi un file di mappaggio usando getResourceAsStream():

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class);

In questo caso Hibernate cercherà file di mappaggio che si chiamano /org/hibernate/autcion/Item.hbm.xml e /org/hibernate/autcion/Bid.hbm.xml sul classpath. Questo approccio elimina qualsiasi nome di file cablato nel codice.

Un oggetto Configuration specifica anche alcune proprietà opzionali:

Properties props = new Properties();
...
Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class)
    .setProperties(props);

Una Configuration viene considerata un oggetto "da fase di configurazione", che va cioè scartato una volta che una SessionFactory sia stata costruita.

3.2. Ottenere una SessionFactory

Quando tutti i mappaggi sono stati interpretati dalla Configuration, l'applicazione deve costruire una factory per le istanze di Session instances. Questa factory è fatta in modo tale da essere condivisa da tutti i flussi esecutivi (thread) dell'applicazione:

SessionFactory sessions = cfg.buildSessionFactory();

Comunque, Hibernate consente alla vostra applicazione di istanziare più di una SessionFactory. Questo è utile in particolare se state usando più di un database.

3.3. Connessioni JDBC fornite dall'utente

Una SessionFactory può aprire una Session su una connessione JDBC fornita dall'utente. Quesa scelta di design dà la libertà all'applicazione di ottenere le connessioni JDBC in qualunque modo preferisca:

java.sql.Connection conn = datasource.getConnection();
Session session = sessions.openSession(conn);

// do some data access work

L'applicazione deve essere molto attenta a non aprire due Sessioni concorrenti sulla stessa connessione JDBC!

3.4. Connessioni JDBC fornite da Hibernate

In alternativa potete fare in modo che la SessionFactory apra le connessioni per voi. La SessionFactory deve ricevere le proprietà per le connessioni JDBC in una delle maniere seguenti:

  1. Passate una istanza di java.util.Properties al metodo Configuration.setProperties().

  2. Mettete il file hibernate.properties in una directory che si trovi alla radice del classpath.

  3. Impostate le proprietà System usando java -Dproperty=value all'avvio.

  4. Includete elementi <property> nel file hibernate.cfg.xml.

Se seguite questo approccio, aprire una Session non è più difficile di così:

Session session = sessions.openSession(); // apre una nuova sessione
// fate del lavoro di accesso ai dati, una connessione JDBC sarà usata se ce ne sarà bisogno

Tutti i nomi e le semantiche delle proprietà di Hibernate sono definiti nella classe net.sf.hibernate.cfg.Environment. Ora descriveremo le impostazioni più importanti per la configurazione delle connessioni JDBC.

Hibernate otterrà le connessioni usando java.sql.DriverManager (e le manterrà in un lotto) se impostate le proprietà seguenti:

Tabella 3.1. Proprietà JDBC di Hibernate

Nome della proprietàScopo
hibernate.connection.driver_classclasse del driver jdbc
hibernate.connection.urlURL jdbc
hibernate.connection.usernamenome utente database
hibernate.connection.passwordchiave di accesso al database per l'utente
hibernate.connection.pool_sizenumero massimo di connessioni nel lotto

L'algoritmo di "pooling" (mantenimento nel lotto) di Hibernate è abbastanza rudimentale. Ha lo scopo di aiutarvi a cominciare a lavorare, ma non è fatto per l'uso in un sistema in produzione, o anche solo per dei test di performance. Usate un'altra libreria di pooling per le migliori performance e la stabilità, ovvero sostituite la proprietà hibernate.connection.pool_size con le proprietà specifiche per il settaggio del pool.

C3P0 è una libreria open source di pooling per connessioni JDBC che viene distribuita insieme ad Hibernate nella directory lib. Hibernate userà il C3P0ConnectionProvider integrato per il pooling delle connessioni se settate le proprietà hibernate.c3p0.*. C'è anche un supporto integrato per Apache DBCP e per Proxool. Dovete in questo caso impostare le proprietà hibernate.dbcp.* per abilitare il DBCPConnectionProvider. Il caching dei prepared statement è abilitato se sono impostate le porprietà hibernate.dbcp.ps.* (caldamente consigliato). Riferitevi alla documentazione di Apache commons-pool per l'interpretazione di queste proprietà. Se invece volete usare Proxool, settate le proprietà hibernate.proxool.*.

Questo è un esempio usando C3P0:

hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.minPoolSize=5
hibernate.c3p0.maxPoolSize=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statement=50
hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect

Per l'uso all'interno di un application server, Hibernate può ottenere connessioni da un javax.sql.Datasource registrato nel JNDI. Impostate per questo le proprietà seguenti:

Tabella 3.2. Hibernate Datasource Properties

Nome della proprietàScopo
hibernate.connection.datasourceNome JNDI del datasource
hibernate.jndi.urlURL del provider JNDI (optional)
hibernate.jndi.classclasse dell'InitialContextFactory JNDI (optional)
hibernate.connection.usernameutente del database (opzionale)
hibernate.connection.passwordchiave di accesso al database per l'utente (opzionale)

Questo è un esempio usando un datasource fornito dal JNDI dell'application server:

hibernate.connection.datasource = java:/comp/env/jdbc/MyDB
hibernate.transaction.factory_class = \
    net.sf.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
    net.sf.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = \
    net.sf.hibernate.dialect.PostgreSQLDialect

Connessioni JDBC ottenute da un datasource JNDI parteciperanno automaticamete alle transazioni gestite dal container dell'application server.

Altre proprietà per la connessione possono venire impostate facendo precedere "hibernate.connnection" al nome della proprietà. Ad esempio, potete specificare un charSet usando hibernate.connnection.charSet.

Potete definire la vostra strategia "plugin" per ottenere le connessioni JDBC implementando l'interfaccia net.sf.hibernate.connection.ConnectionProvider. Potete selezionare una implementazionee custom impostando hibernate.connection.provider_class.

3.5. Configurazione di proprietà opzionali

C'è un certo numero di altre proprietà che controllano il funzionamento in fase di esecuzione di Hibernate. Sono tutte opzionali, e hanno dei valori predefiniti ragionevoli.

Le proprietà a livello di sistema possono essere impostate eslusivamente tramite java -Dproperty=value o essere definite in hibernate.properties e non con una istanza di Properties passata alla classe Configuration.

Tabella 3.3. Proprietà per la configurazione di Hibernate

Nome della proprietàScopo
hibernate.dialect Il nome della classe di un Dialect di Hibernate - attiva alcune funzionalità dipendenti dalla piattaforma di database.

e.g. nome.completo.del.Dialect

hibernate.default_schema Specificate il nome dello schema/tablespace nei nomi delle tabelle nell'SQL che viene generato.

e.g. NOME_DELLO_SCHEMA

hibernate.session_factory_name Il SessionFactory verrà automaticamente pubblicato sul JNDI sotto questo nome, se è stato specificato.

e.g. nome/composito/jndi

hibernate.use_outer_join Attiva il reperimento via join esterno. Deprecato, usate max_fetch_depth.

e.g. true | false

hibernate.max_fetch_depth Imposta una "profondità" massima per l'albero di join esterno che risolve le associazioni ad una singola estremità (uno-a-uno, molti-a-uno). Uno 0 disabilita la risoluzione via join esterno (che invece è attivata per default).

e.g. i valori raccomandati sono tra 0 e 3

hibernate.jdbc.fetch_size Un valore non nullo determina le dimensioni di raccolta del JDBC (chiama Statement.setFetchSize()).
hibernate.jdbc.batch_size Un valore non nullo abilita l'uso dei "batch update" di JDBC 2 da parte di Hibernate (aggiornamenti in blocco).

e.g. I valori raccomandati sono tra 5 e 30

hibernate.jdbc.use_scrollable_resultset Consente l'uso di resultset scrollabili JDBC2 da parte di Hibernate. Questa proprietà è necessaria solo quando vengono usate connessioni JDBC fornite dall'utente: Hibernate usa i metadati di connessione, altrimenti.

e.g. true | false

hibernate.jdbc.use_streams_for_binary Se abilitata, Hibernate usa gli stream quando scrive/legge tipi binary o serializable da/a JDBC (proprietà di livello di sistema).

e.g. true | false

hibernate.cglib.use_reflection_optimizer Abilita l'uso di CGLIB invece di "reflection" in fase di esecuzione (è una proprietà a livello di sistema, il default è usare CGLIB quando possibile). La "reflection" può a volte essere usata in fase di risoluzione dei problemi.

e.g. true | false

hibernate.jndi.<propertyName> Passa la proprietà propertyName all' InitialContextFactory JNDI.
hibernate.connection.isolation Setta il livello di isolamento transazionale JDBC. Consultate la documentazione di java.sql.Connection per ottenere i valori significativi, ma tenete presente che la maggior parte dei database non supportano tutti i livelli di isolamento.

eg. 1, 2, 4, 8

hibernate.connection.<propertyName> Passa il nome di proprietà JDBC propertyName a DriverManager.getConnection().
hibernate.connection.provider_class Il nome della classe di un ConnectionProvider definito dall'utente.

e.g. nomediclasse.del.ConnectionProvider

hibernate.cache.provider_class Il nome di classe di un CacheProvider fornito dall'utente.

eg. nomediclasse.del.CacheProvider

hibernate.cache.use_minimal_puts Ottimizza le operazioni della cache di secondo livello in modo tale da minimizzare le scritture, al costo di letture più frequenti (usato per cache in cluster).

e.g. true|false

hibernate.cache.use_query_cache Attiva il caching delle interrogazioni, le query singole vanno comunque impostate come "cacheabili".

e.g. true|false

hibernate.cache.region_prefix Il prefisso da usare per i nomi delle regioni della cache di secondo livello.

eg. prefisso

hibernate.transaction.factory_class Il nome di classe di una TransactionFactory da usare con l'API Transaction di Hibernate (il valore predefinito è JDBCTransactionFactory).

eg. nomediclasse.della.TransactionFactory

jta.UserTransaction Il nome di classe usato da JTATransactionFactory per ottenere la UserTransaction JTA dall'application server.

eg. nome/composito/jndi

hibernate.transaction.manager_lookup_class Il nome di classe di un TransactionManagerLookup - richiesto quando il caching a livello di JVM è abilitato in un contesto JTA.

eg. nomediclasse.del.TransactionManagerLookup

hibernate.query.substitutions Mappaggio da alcune etichette nelle query di Hibernate a dei valori di sostituzione per le query SQL (potrebbero essere funzioni o letterali, ad esempio).

e.g. hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC

hibernate.show_sql Scrive tutte le istruzioni SQL sulla console.

e.g. true | false

hibernate.hbm2ddl.auto Esporta automaticamente lo schema DDL sul database quando viene creata la SessionFactory. Con create-drop, lo schema di database verrà eliminato subito dopo che la SessionFactory verrà chiusa esplicitamente.

e.g. update | create | create-drop

3.5.1. Dialetti SQL

Dovreste sempre impostare la proprietà hibernate.dialect al valore della sottoclasse di net.sf.hibernate.dialect.Dialect giusta per il vostro database. Questo non è strettamente necessario, a meno che desideriate usare la generazione di chiavi primaria native o sequence o il locking pessimistico (con, ad esempio, Session.lock() o Query.setLockMode()). Comunque, se specificate un dialetto, Hibernate imposterà dei default adatti per alcune delle proprietà elencate in precedenza, risparmiandovi lo sforzo di specificarle manualmente.

Tabella 3.4. Dialetti SQL di HIbernate (hibernate.dialect)

RDBMSDialetto
DB2net.sf.hibernate.dialect.DB2Dialect
MySQLnet.sf.hibernate.dialect.MySQLDialect
SAP DBnet.sf.hibernate.dialect.SAPDBDialect
Oracle (any version)net.sf.hibernate.dialect.OracleDialect
Oracle 9net.sf.hibernate.dialect.Oracle9Dialect
Sybasenet.sf.hibernate.dialect.SybaseDialect
Sybase Anywherenet.sf.hibernate.dialect.SybaseAnywhereDialect
Progressnet.sf.hibernate.dialect.ProgressDialect
Mckoi SQLnet.sf.hibernate.dialect.MckoiDialect
Interbasenet.sf.hibernate.dialect.InterbaseDialect
Pointbasenet.sf.hibernate.dialect.PointbaseDialect
PostgreSQLnet.sf.hibernate.dialect.PostgreSQLDialect
HypersonicSQLnet.sf.hibernate.dialect.HSQLDialect
Microsoft SQL Servernet.sf.hibernate.dialect.SQLServerDialect
Ingresnet.sf.hibernate.dialect.IngresDialect
Informixnet.sf.hibernate.dialect.InformixDialect
FrontBasenet.sf.hibernate.dialect.FrontbaseDialect

3.5.2. Reperimento via join esterno

Se il vostro database supporta i join esterni ANSI o Oracle, il reperimento via join esterno può aumentare le performance limitando il numero di accessi al database (al costo di un lavoro maggiore probabilmente effettuato dal database stesso). Il reperimento via join esterno consente ad un grafo di oggetti connessi da associazioni molti-a-uno, uno-a-molti o uno-a-uno di essere caricati in una singola SELECT SQL.

Per default, il grafo inizializzato quando si carica un oggetto termina agli oggetti foglia, alle collezioni, agli oggetti con mediatori (proxy) o dove ci siano delle circolarità.

Per una associazione particolare, il caricamento può essere abilitato o disabilitato (e quindi si può modificare il comportamento predefinito) impostando l'attributo outer-join nel mapping XML.

Il caricamento via join esterno può essere disabilitato globalmente impostando la proprietà hibernate.max_fetch_depth a 0. Un settaggio di 1 o più abilita il join esterno per tutte le associazioni uno-a-uno e molti-a-uno che sono, sempre come impostazione predefinita, impostate ad auto. In ogni caso, le associazioni uno-a-molti e le collezioni non vengono mai caricate con un join esterno, a meno che questo non venga esplicitamente dichiarato per ogni particolare associazione. Anche questo comportamento può essere modificato a runtime con delle query di Hibernate.

3.5.3. Flussi (stream) binari

Oracle limita la dimensione degli array di byte che possono essere passati da o al suo driver JDBC. Se volete usare istanze di tipi binari o serializzabili di grandi dimensioni, dovete abilitare hibernate.jdbc.use_streams_for_binary. Questa impostazione viene settata esclusivamente a livello di JVM.

3.5.4. CacheProvider personalizzati

Potete integrare una cache di secondo livello che operi all'interno della virtual machine (JVM-level) o sull'intero cluster (clustered) implementando l'interfaccia net.sf.hibernate.cache.CacheProvider. Potete poi impostare l'implementazione personalizzata con l'opzione hibernate.cache.provider_class.

3.5.5. Configurazione della strategia transazionale

Se volete usare l'API Transaction di Hibernate, dovete specificare una classe factory ("fabbricatore" di istanze) per gli oggetti Transaction impostando la proprietà hibernate.transaction.factory_class. L'API Transaction maschera il meccanismo transazionale sottostante e consente al codice di Hibernate di eseguirsi in contesti gestiti dal container (managed) e non.

Ci sono due scelte possibili (pre-installate in Hibernate):

net.sf.hibernate.transaction.JDBCTransactionFactory

delega al meccanismo transazionale JDBC transactions (impostazione predefinita)

net.sf.hibernate.transaction.JTATransactionFactory

delega a JTA (se esiste una transazione attiva, la Session esegue il suo lavoro in quel contesto, altrimenti una nuova transazione viene attivata)

Potete anche definire le vostre strategie transazionali (per usare un servizio transazionale CORBA, ad esempio).

Se volete usare caching a livello di JVM per dati che siano modificabili in un contesto JTA, dovete specificare una strategia per ottenere il TransactionManager JTA, poiché non esiste un metodo standardizzato nei container J2EE per farlo:

Tabella 3.5. TransactionManager JTA

Transaction FactoryApplication Server
net.sf.hibernate.transaction.JBossTransactionManagerLookupJBoss
net.sf.hibernate.transaction.WeblogicTransactionManagerLookupWeblogic
net.sf.hibernate.transaction.WebSphereTransactionManagerLookupWebSphere
net.sf.hibernate.transaction.OrionTransactionManagerLookupOrion
net.sf.hibernate.transaction.ResinTransactionManagerLookupResin
net.sf.hibernate.transaction.JOTMTransactionManagerLookupJOTM
net.sf.hibernate.transaction.JOnASTransactionManagerLookupJOnAS
net.sf.hibernate.transaction.JRun4TransactionManagerLookupJRun4

3.5.6. SessionFactory pubblicata sul JNDI

Una SessionFactory di Hibernate pubblicata sul JNDI può semplificare il reperimento della factory stessa, e la creazione di nuove Sessioni.

Se volete che la SessionFactory venga pubblicata su uno spazio di nomi JNDI, specificate un nome (ad esempio. java:comp/env/hibernate/SessionFactory) usando la proprietà hibernate.session_factory_name. Se questa proprietà viene omessa, la SessionFactory non verrà pubblicata sul JNDI. (Questo è particolarmente utile in ambienti in cui l'implementazione standard del JNDI sia di sola lettura, come ad esempio in Tomcat)

Quando Hibernate pubblicherà la SessionFactory sul JNDI, userà i valori di hibernate.jndi.url e hibernate.jndi.class per istanziare il contesto iniziale JNDI (InitialContext). Se queste proprietà non vengono specificate, verrà usato l'InitialContext predefinito.

Se scegliete di usare il JNDI, un EJB o qualsiasi altra classe di utilità può ottenere la SessionFactory con una ricerca (lookup) sull'albero JNDI.

3.5.7. Sostituzioni per il linguaggio di interrogazione

Potete definire nuove etichette per le interrogazioni di Hibernate usando la proprietà hibernate.query.substitutions. Ad esempio:

hibernate.query.substitutions true=1, false=0

farebbe sì che le etichette true e false venissero tradotti in letterali interi nell'SQL generato.

hibernate.query.substitutions toLowercase=LOWER

permetterebbe di rinominare la funzione LOWER dell'SQL.

3.6. Traccia di esecuzione (logging)

Hibernate traccia ("logs") vari eventi utilizzando la libreria commons-logging di Apache.

Il servizio di commons-logging indirizzerà l'uscita a Apache Log4j (se includete log4j.jar nel vostro classpath) o al logging nativo di JDK1.4 (se l'applicazione sta funzionando sotto JDK1.4 o superiori). Potete scaricare Log4j da http://jakarta.apache.org. Per usare Log4j avrete bisogno di un file log4j.properties sul classpath: un file di proprietà di esempio viene distribuito con Hibernate nella directory src/.

Raccomandiamo vivamente che vi familiarizziate con i messaggi di log di Hibernate. È stato fatto un grande sforzo per far sì che le tracce lasciate da Hibernate siano il più dettagliate possibile senza per questo renderle illeggibili. È uno strumento di risoluzione dei problemi fondamentale. Non dimenticate anche di abilitare la traccia dell'SQL come descritto in precedenza (hibernate.show_sql), perché è il primo passo quando si lavori alla risoluzione di problemi di performance.

3.7. Implementazione di una strategia di denominazione (NamingStrategy)

L'interfaccia net.sf.hibernate.cfg.NamingStrategy vi consente di specificare uno "standard di denominazione" per gli oggetti del database e gli elementi dello schema.

Potete fornire regole per generare automaticamente identificatori di database da identificatori java, o per ricavare nomi "fisici" di tabella e colonna dai nomi "logici" dati nel file di mappaggio. Questa funzionalità consente di ridurre la prolissità del documento di mappaggio, eliminando il rumore ripetitivo (come ad esempio i prefissi TBL_). La strategia base è abbastanza minimale.

Potete specificare una strategia differente chiamando Configuration.setNamingStrategy() prima di aggiungere i mappaggi alla configurazione:

SessionFactory sf = new Configuration()
    .setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml")
    .buildSessionFactory();

net.sf.hibernate.cfg.ImprovedNamingStrategy è una strategia che viene distribuita con Hibernate e che potrebbe essere un punto di partenza utile per alcune applicazioni.

3.8. File di configurazione XML

Un approccio alternativo è specificare una configurazione completa in un file chiamato hibernate.cfg.xml. Questo file può essere usato come un'alternativa al file hibernate.properties o, se sono presenti entrambi, per ridefinirne le proprietà.

Il file di configurazione XML viene caricato da Hibernate dalla radice del CLASSPATH. Ecco un esempio:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 2.0//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <!-- una istanza di SessionFactory indicata con il suo /nome/jndi -->
    <session-factory
        name="java:comp/env/hibernate/SessionFactory">

        <!-- proprietà -->
        <property name="connection.datasource">my/first/datasource</property>
        <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">false</property>
        <property name="use_outer_join">true</property>
        <property name="transaction.factory_class">
            net.sf.hibernate.transaction.JTATransactionFactory
        </property>
        <property name="jta.UserTransaction">java:comp/UserTransaction</property>

        <!-- mapping files -->
        <mapping resource="org/hibernate/auction/Item.hbm.xml"/>
        <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

La configurazione di Hibernate richiede solo di scrivere

SessionFactory sf = new Configuration().configure().buildSessionFactory();

È comunque possibile specificare un differente file di configurazione XML usando

SessionFactory sf = new Configuration()
    .configure("catdb.cfg.xml")
    .buildSessionFactory();