Inizia con Hibernate

È utile comprendere la necessità di ORM (Object / Relational Mapping) nelle applicazioni Java, ma probabilmente non vedi l'ora di vedere Hibernate in azione. Inizieremo mostrandoti un semplice esempio che dimostra parte della sua potenza.

Come probabilmente saprai, è tradizione che un libro di programmazione inizi con un esempio "Hello World". In questo capitolo, seguiamo questa tradizione introducendo Hibernate con un programma "Hello World" relativamente semplice. Tuttavia, la semplice stampa di un messaggio su una finestra della console non sarà sufficiente per dimostrare realmente Hibernate. Invece, il nostro programma memorizzerà gli oggetti appena creati nel database, li aggiornerà ed eseguirà query per recuperarli dal database.

Oltre all'esempio canonico "Hello World", introduciamo le API di base di Hibernate e forniamo dettagli per una configurazione di base.

"Hello World" con Hibernate

Le applicazioni di ibernazione definiscono classi persistenti che vengono "mappate" alle tabelle del database. Il nostro esempio "Hello World" è costituito da una classe e un file di mappatura. Vediamo come appare una semplice classe persistente, come viene specificata la mappatura e alcune delle cose che possiamo fare con le istanze della classe persistente usando Hibernate.

L'obiettivo della nostra applicazione di esempio è memorizzare i messaggi in un database e recuperarli per la visualizzazione. L'applicazione ha una semplice classe persistente Message, che rappresenta questi messaggi stampabili. La nostra Messageclasse è mostrata nel Listato 1.

Listato 1. Message.java: una semplice classe persistente

ciao pacchetto; public class Message {private Long id; stringa di testo privata; messaggio privato nextMessage; messaggio privato () {} messaggio pubblico (String text) {this.text = text; } public Long getId () {return id; } private void setId (Long id) {this.id = id; } public String getText () {return text; } public void setText (String text) {this.text = text; } messaggio pubblico getNextMessage () {return nextMessage; } public void setNextMessage (Message nextMessage) {this.nextMessage = nextMessage; }}

La nostra Messageclasse ha tre attributi: l'attributo identificatore, il testo del messaggio e un riferimento a un altro Message. L'attributo identificatore consente all'applicazione di accedere all'identità del database, il valore della chiave primaria, di un oggetto persistente. Se due istanze di Messagehanno lo stesso valore identificativo, rappresentano la stessa riga nel database. Abbiamo scelto Longil tipo del nostro attributo identificatore, ma questo non è un requisito. Hibernate consente praticamente qualsiasi cosa per il tipo di identificatore, come vedrai in seguito.

Avrai notato che tutti gli attributi della Messageclasse hanno metodi di accesso alle proprietà in stile JavaBean. La classe ha anche un costruttore senza parametri. Le classi persistenti che usiamo nei nostri esempi avranno quasi sempre un aspetto simile a questo.

Le istanze della Messageclasse possono essere gestite (rese persistenti) da Hibernate, ma non devono esserlo. Poiché l' Messageoggetto non implementa alcuna classe o interfaccia specifica di Hibernate, possiamo usarlo come qualsiasi altra classe Java:

Messaggio messaggio = nuovo messaggio ("Hello World"); System.out.println (message.getText ());

Questo frammento di codice fa esattamente quello che ci si aspetta dalle applicazioni "Hello World": stampa "Hello World"sulla console. Potrebbe sembrare che stiamo cercando di essere carini qui; in effetti, stiamo dimostrando una caratteristica importante che distingue Hibernate da alcune altre soluzioni di persistenza, come i bean di entità EJB (Enterprise JavaBean). La nostra classe persistente può essere utilizzata in qualsiasi contesto di esecuzione, non è necessario alcun contenitore speciale. Ovviamente, sei venuto qui per vedere lo stesso Hibernate, quindi salviamo un nuovo Messagenel database:

Session session = getSessionFactory (). OpenSession (); Transazione tx = session.beginTransaction (); Messaggio messaggio = nuovo messaggio ("Hello World"); session.save (messaggio); tx.commit (); session.close ();

Questo codice chiama Hibernate Sessione Transactioninterfacce. (Arriveremo presto a quella getSessionFactory()chiamata.) Risulta nell'esecuzione di qualcosa di simile al seguente SQL:

inserire in MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) valori (1, "Hello World", null) 

Aspetta: la MESSAGE_IDcolonna viene inizializzata con un valore strano. Non abbiamo impostato la idproprietà da messagenessuna parte, quindi ci aspetteremmo che fosse null, giusto? In realtà, la idproprietà è speciale: è una proprietà identificatore — contiene un valore univoco generato. (Discuteremo in seguito come viene generato il valore.) Il valore viene assegnato Messageall'istanza da Hibernate quando save()viene chiamato.

Per questo esempio, assumiamo che la MESSAGEStabella esista già. Ovviamente, vogliamo che il nostro programma "Hello World" stampi il messaggio sulla console. Ora che abbiamo un messaggio nel database, siamo pronti per dimostrarlo. Il prossimo esempio recupera tutti i messaggi dal database, in ordine alfabetico, e li stampa:

Session newSession = getSessionFactory (). OpenSession (); Transazione newTransaction = newSession.beginTransaction (); List messages = newSession.find ("from Message as m order by m.text asc"); System.out.println (messages.size () + "message (s) found:"); for (Iterator iter = messages.iterator (); iter.hasNext ();) {Message message = (Message) iter.next (); System.out.println (message.getText ()); } newTransaction.commit (); newSession.close ();

La stringa letterale "from Message as m order by m.text asc"è una query di Hibernate, espressa nel linguaggio Hibernate Query Language (HQL) orientato agli oggetti di Hibernate. Questa query viene tradotta internamente nel seguente SQL quando find()viene chiamata:

seleziona m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID da MESSAGES m ordina per m.MESSAGE_TEXT asc 

Il frammento di codice viene stampato:

1 messaggio (i) trovato / i: Hello World 

Se non hai mai utilizzato uno strumento ORM come Hibernate prima, probabilmente ti aspettavi di vedere le istruzioni SQL da qualche parte nel codice o nei metadati. Non ci sono. Tutto l'SQL viene generato in fase di esecuzione (in realtà all'avvio, per tutte le istruzioni SQL riutilizzabili).

Per consentire che questa magia si verifichi, Hibernate necessita di maggiori informazioni su come Messagerendere persistente la classe. Queste informazioni vengono solitamente fornite in un documento di mapping XML . Il documento di mappatura definisce, tra le altre cose, il modo in cui le proprietà della Messageclasse vengono mappate alle colonne della MESSAGEStabella. Diamo un'occhiata al documento di mappatura nel Listato 2.

Listato 2. Una semplice mappatura XML di Hibernate


  

Il documento di mappatura dice a Hibernate che la Messageclasse deve essere mantenuta nella MESSAGEStabella, che la proprietà identificatore è mappata a una colonna denominata MESSAGE_ID, che la proprietà text è mappata a una colonna denominata MESSAGE_TEXTe che la proprietà denominata nextMessageè un'associazione con molti-a-uno molteplicità che viene mappata a una colonna denominata NEXT_MESSAGE_ID. (Non preoccuparti per gli altri dettagli per ora.)

Come puoi vedere, il documento XML non è difficile da capire. Puoi facilmente scriverlo e mantenerlo a mano. Qualunque sia il metodo scelto, Hibernate dispone di informazioni sufficienti per generare completamente tutte le istruzioni SQL necessarie per inserire, aggiornare, eliminare e recuperare istanze della Messageclasse. Non è più necessario scrivere manualmente queste istruzioni SQL.

Nota
Molti sviluppatori Java si sono lamentati dell '"inferno dei metadati" che accompagna lo sviluppo di J2EE. Alcuni hanno suggerito un passaggio dai metadati XML al semplice codice Java. Sebbene applaudiamo questo suggerimento per alcuni problemi, ORM rappresenta un caso in cui i metadati basati su testo sono davvero necessari. Hibernate ha impostazioni predefinite ragionevoli che riducono al minimo la digitazione e una definizione matura del tipo di documento che può essere utilizzata per il completamento automatico o la convalida negli editor. Puoi persino generare automaticamente metadati con vari strumenti.

Ora, cambiamo il nostro primo messaggio e, già che ci siamo, creiamo un nuovo messaggio associato al primo, come mostrato nel Listato 3.

Listato 3. Aggiornamento di un messaggio

Session session = getSessionFactory (). OpenSession (); Transazione tx = session.beginTransaction (); // 1 è l'ID generato del primo messaggio Message message = (Message) session.load (Message.class, new Long (1)); message.setText ("Saluti terrestre"); Message nextMessage = new Message ("Portami dal tuo leader (per favore)"); message.setNextMessage (nextMessage); tx.commit (); session.close ();

Questo codice chiama tre istruzioni SQL all'interno della stessa transazione:

seleziona m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID da MESSAGES m dove m.MESSAGE_ID = 1 inserisci in MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) valori (2, "Portami dal tuo leader (per favore)", null) aggiorna MESSAGGI set MESSAGE_TEXT = 'Greetings Earthling', NEXT_MESSAGE_ID = 2 dove MESSAGE_ID = 1 

Notice how Hibernate detected the modification to the text and nextMessage properties of the first message and automatically updated the database. We've taken advantage of a Hibernate feature called automatic dirty checking: this feature saves us the effort of explicitly asking Hibernate to update the database when we modify the state of an object inside a transaction. Similarly, you can see that the new message was made persistent when a reference was created from the first message. This feature is called cascading save: it saves us the effort of explicitly making the new object persistent by calling save(), as long as it's reachable by an already persistent instance. Also notice that the ordering of the SQL statements isn't the same as the order in which we set property values. Hibernate uses a sophisticated algorithm to determine an efficient ordering that avoids database foreign key constraint violations but is still sufficiently predictable to the user. This feature is called transactional write-behind.

If we run "Hello World" again, it prints:

2 message(s) found: Greetings Earthling Take me to your leader (please) 

This is as far as we'll take the "Hello World" application. Now that we finally have some code under our belt, we'll take a step back and present an overview of Hibernate's main APIs.

Understanding the architecture

The programming interfaces are the first thing you have to learn about Hibernate in order to use it in the persistence layer of your application. A major objective of API design is to keep the interfaces between software components as narrow as possible. In practice, however, ORM APIs aren't especially small. Don't worry, though; you don't have to understand all the Hibernate interfaces at once. The figure below illustrates the roles of the most important Hibernate interfaces in the business and persistence layers.

We show the business layer above the persistence layer, since the business layer acts as a client of the persistence layer in a traditionally layered application. Note that some simple applications might not cleanly separate business logic from persistence logic; that's okay—it merely simplifies the diagram.

The Hibernate interfaces shown in the figure above may be approximately classified as follows:

  • Interfaces called by applications to perform basic CRUD (create/read/update/delete) and querying operations. These interfaces are the main point of dependency of application business/control logic on Hibernate. They include Session, Transaction, and Query.
  • Interfaces called by application infrastructure code to configure Hibernate, most importantly, the Configuration class.
  • Callback interfaces that allow the application to react to events occurring inside Hibernate, such as Interceptor, Lifecycle, and Validatable.
  • Interfaces that allow extension of Hibernate's powerful mapping functionality, such as UserType, CompositeUserType, and IdentifierGenerator. These interfaces are implemented by application infrastructure code (if necessary).

Hibernate makes use of existing Java APIs, including JDBC (Java Database Connectivity), Java Transaction API (JTA), and Java Naming and Directory Interface (JNDI). JDBC provides a rudimentary level of abstraction of functionality common to relational databases, allowing almost any database with a JDBC driver to be supported by Hibernate. JNDI and JTA allow Hibernate to be integrated with J2EE application servers.

In questa sezione, non trattiamo la semantica dettagliata dei metodi API di Hibernate, ma solo il ruolo di ciascuna delle interfacce primarie. Puoi trovare la maggior parte di queste interfacce nel pacchetto net.sf.hibernate. Diamo una breve occhiata a ciascuna interfaccia a turno.

Le interfacce principali

Le cinque interfacce principali vengono utilizzate in quasi tutte le applicazioni Hibernate. Utilizzando queste interfacce, è possibile archiviare e recuperare oggetti persistenti e controllare le transazioni.

Interfaccia di sessione