Programmazione Java Avanzata
Hibernate (Parte 2)
Ing. Giuseppe D'Aquì
2
Testi Consigliati● Beginning Hibernate 2nd edition (Apress)
● Sul sito è possibile scaricare, tra gli “extra”, il codice sorgente e il capitolo 3
● Hibernate Getting Started● http://docs.jboss.org/hibernate/core/3.6/quickstart/en-US/html_single/
3
Pro e contro Annotations● Pro:
● Le annotation sono standard JPA, quindi sono usate in modo identico da tutti gli ORM JPA-compatibili
● Meno verbosità● Intuitive
● Contro:● Le versioni obsolete di Hibernate supportano solo
XML● Se il database è usato da più applicazioni, si rischia
di distribuire il mapping su applicazioni diverse
4
Chiave Composta● Se in una tabella la chiave primaria è
costituita da più campi, si ha una chiave composta
● Quando non ci sono particolari vincoli (ovvero abbiamo il controllo dello schema del database), in genere si preferisce semplificare lo sviluppo generando una chiave surrogata● Una chiave surrogata non ha significato al di fuori
del database, è un campo che rimpiazza i molti campi della chiave composta
5
Chiave Composta● Quando però ci sono vincoli la chiave
composta deve essere mappata su Hibernate● Ci sono tre modalità, tutte e tre prevedono la
creazione di una classe che contenga il mapping dei campi della chiave composta● Es. se la chiave composta è (nome, cognome),
allora serve una classe (differente dalla corrente) che contenga come variabili membro String nome e String cognome
6
Chiave composta: modo 1● La classe con gli attributi chiave deve essere
pubblica ed esterna ● public class UserPrimaryKey{...}
● La classe va annotata con @Embeddable, e devono essere implementati i metodi hashCode() ed equals()
● Nella classe originaria, invece, bisogna sostituire gli attributi della chiave composta con un unica variabile “id” (annotata con @Id)● private UserPrimaryKey id;
7
Chiave composta: modo 2● La classe con gli attributi chiave va creata
come nel modo 1, ma internamente alla classe originaria
● La classe non va annotata con @Embeddable● La classe originaria ha sempre la variabile “id”
ma annotata con @EmbeddedId
8
Chiave composta: modo 3● La classe con gli attributi chiave va creata
come nel modo 2 ● Si annota solo la classe originaria con
● @IdClass● Passandogli la classe che fa le funzioni di
chiave● @IdClass(User.UserPrimaryKey.class)
● Nella classe originaria non c'è più “id” ma vanno messi gli attributi originali annotati con @Id
9
Chiave composta: riassunto● Il codice SQL generato in tutti i casi è uguale● Ci sono particolarità di utilizzo:
● Modo 1: La classe con gli attributi è esterna ed embeddable (vedi relazioni one-to-one), può essere riutilizzata
● Modo 3: possiamo utilizzare gli attributi chiave direttamente dalla classe originaria [getNome() e getCognome()], invece di ottenere prima l'oggetto id
● Modo 2: una via di mezzo
10
Mapping di relazioni● Le relazioni tra tabelle possono essere di
diverso tipo● One-to-One (uno-a-uno)● One-to-Many (uno-a-molti)● Many-to-Many (molti-a-molti)
11
One-to-one: tipi● Le relazioni uno-a-uno, in Hibernate, sono di
due tipi:● “Classiche”, due tabelle corrispondono a
due oggetti● “Component” o “embedded”, due oggetti
che risiedono su una sola tabella (es. l'indirizzo)
12
One-to-one “embedded”● Una relazione “one-to-one embedded” si ha
quando un'entità è interamente racchiusa all'interno di un'altra entità, ossia è un “componente” dell'alltra entità
● La classe “componente” va annotata con @Embeddable, non possiede @Id
● Nella classe principale viene posta una variabile membro con lo stesso tipo della classe componente, annotata con @Embedded
13
One-to-one “embedded”public class User {
…
@Embedded
public Address getAddress(){...}
}
@Embeddable
public class Address{
private String via;
private String civico;
//getters/setters
}
14
One-to-one● La relazione uno-a-uno convenzionale è in
genere “sospetta”, perché potrebbe nascondere un errore di progettazione● In genere, infatti, si tende ad accorpare tutti gli
attributi in una sola entità● Ci sono dei casi in cui, però, è preferibile
avere due tabelle che rappresentano due aspetti differenti di una certa entità “ideale”● Aspetti che devono essere gestiti differentemente,
o che devono evolvere in modo indipendente● Es. Profilo Autenticazione e Anagrafiche
15
One-to-one● Per mappare una relazione uno-a-uno basta
annotare con @OneToOne la variabile membro (o il getter) che contiene l'oggetto correlato
16
One-to-one “classica”public class User {
…
@OneToOne
public Address getAddress(){...}
}
@Entity
public class Address{
@Id
private int id;
private String via;
private String civico;
//getters/setters
}
17
One-to-one● L'annotazione @OneToOne può avere i seguenti
attributi:● cascade: indica se le operazioni su una entità
devono propagarsi alle altre della relazione● fetch: indica il tipo di acquisizione, “eager”
(carica tutti gli oggetti del grafo delle relazioni) o “lazy” (carica gli oggetti correlati solo se servono)
● optional: serve per stabilire se ci possono essere NULL
18
One-to-one bidirezionale● La relazione OneToOne è bidirezionale quando
l'oggetto A contiene un riferimento all'oggetto B, e l'oggetto B contiene un riferimento all'oggetto A● Es. User.getAddress() e Address.getUser()
● In questo caso uno dei due oggetti deve essere considerato “principale”● Quello la cui tabella contiene la chiave primaria
dell'altro come riferimento● L'oggetto secondario dovrà usare l'attributo
“mappedBy” nella sua annotazione @OneToOne
19
One-to-one “bidirezionale”@Entity
public class User {
… //User è l'oggetto “principale”
@OneToOne
public Address getAddress(){...}
}
@Entity
public class Address{
… //Address è l'oggetto “secondario”
//il “mappedBy” si riferisce alla proprietà dell'oggetto principale
@OneToOne(mappedBy=address)
public User getUser(){...}
}
20
One-to-one - JoinColumn● Per default, Hibernate cercherà come foreign
key un campo della tabella con queste caratteristiche:● Inizia con il nome della proprietà dell'oggetto
principale (User ha getAddress(), quindi “address”)● Concatenato con underscore “_”● Concatenato con il nome della chiave primaria
dell'oggetto secondario (Es, Address ha getId(), quindi “id”
● Nell'esempio quindi cercherà una colonna chiamata “address_id”
21
One-to-one - JoinColumn● È possibile sovrascrivere questo default con
due tipi di annotazioni:● @PrimaryKeyJoinColumn : usato assieme a
@OneToOne sull'oggetto principale, indica che la foreign key è la stessa chiave primaria (ovvero, le due tabelle possiedono la stessa chiave primaria)
● @JoinColumn(name=”address_fk”) : usato assieme a @OneToOne sull'oggetto principale, indica che la foreign key sta in una colonna con un nome preciso (in questo caso “address_fk”)
22
Cascading delle operazioni● Il cascading è la propagazione delle operazioni
di aggiornamento e rimozione su oggetti collegati tra loro da relazioni● Esempio: se viene cancellato un User deve essere
cancellato il corrispondente Address?● Hibernate definisce diversi tipi di cascading,
definiti nelle relazioni● Se non si specifica niente, per default non si
ha cascading
23
Cascading delle operazioni● Tipi di cascading:
● CascadeType.MERGE : propaga gli UPDATE● CascadeType.PERSIST : propaga il primo
inserimento (INSERT)● CascadeType.REFRESH : propaga l'aggiornamento
dal database verso gli oggetti (SELECT)● CascadeType.DETACH : propaga la rimozione
dell'oggetto dalla persistenza● CascadeType.REMOVE : propaga la rimozione dei
dati dell'oggetto (DELETE)● CascadeType.ALL : tutti i precedenti
24
One-to-Many● La relazione uno-a-molti può essere vista da
due “prospettive” diverse● L'oggetto A ha una relazione con molti oggetti B● Ogni oggetto B ha una relazione con uno e un solo
oggetto A● Hibernate mappa queste due prospettive
usando @OneToMany e @ManyToOne
25
One-to-Many● @OneToMany va sull'oggetto che contiene● @ManyToOne va sull'oggetto contenuto● JPA considera, per convezione, “oggetto
principale” quello che ha @ManyToOne che, quindi, dovrà specificare un mappedBy
26
One-to-Many@Entity
public class User {
… //User è l'oggetto “secondario”
@OneToMany(mappedBy=”user”)
public Set<Telephone> getTelephone(){...}
}
@Entity
public class Telephone{
… //Telephone è l'oggetto “principale”
@ManyToOne
public User getUser(){...}
}
27
One-to-Many - JoinColumn● Valgono le stesse considerazioni di JoinColumn
fatte nel caso One-to-One● Nell'esempio Hibernate cercherà una colonna
chiamata “user_id” nella tabella “telephone” (oggetto principale)
● Possiamo però specificare un'altra colonna utilizzando @JoinColumn insieme a @ManyToOne nell'oggetto principale
28
Ordering● Quando vengono mappate delle Collection,
possiamo definire una colonna sulla quale queste verranno ordinate
● @OneToMany(mappedBy=”user”)
● public List<Telephone> getTelephone(){...}
● //List è un tipo collection ordinato
● Si può aggiungere a @OneToMany l'annotazione @OrderBy, che permette di specificare quale proprietà di Telephone usare per l'ordinamento
● @OneToMany(mappedBy”user”)
● @OrderBy(“prefisso ASC”)
● public List<Telephone> getTelephone(){...}
29
Many-to-Many● In una relazione Many-to-Many entrambi gli
oggetti coinvolti utilizzeranno @ManyToMany● Uno dei due deve essere l'oggetto principale,
l'altro conterrà l'attributo “mappedBy”● Una relazione molti-a-molti nel modello
relazionale ha bisogno di una tabella ausiliaria che contiene le foreign key di entrambe le tabelle
30
Many-to-Many● La tabella ausiliaria ha, per default, il nome:
● Tabellaprincipale_Tabellasecondaria● E chiavi Tabellaprincipale_id e
Tabellasecondaria_id● Questi default possono essere sovrascritti con
@JoinTable, usato lato oggetto principale@JoinTable(
name=”studenti_e_corsi”,
joinColumns={@JoinColumn(name=”corso_id”)},
inverseJoinColumns{@JoinColumn(name=”matricola”)} )
31
Many-to-Many: nota bene● Quando c'è una relazione molti-a-molti che
contiene attributi specifici, forse non si tratta di una mera relazione tra tabelle ma è una entità “di fatto”● Es. se la tabella Studente_Corso contiene
l'attributo “voto”, non è più una semplice tabella di join ma un'entità Esame, che ha relazioni uno-a-molti con Studente e con Corso
● La tabella di join, per Hibernate, contiene solo le foreign key e non altri attributi
32
Ereditarietà● L'ereditarietà in Hibernate e JPA può essere
rappresentata in tre modi:● Single table: una singola tabella che contiene padri
e figli● Joined: una tabella per il padre e una per ciascuno
dei figli, ma i figli contengono solo gli attributi non in comune con il padre
● Table-per-class: tabelle complete per ogni figlio
33
Ereditarietà: single table● Single table prevede una singola tabella per i
padri e per i figli● Una singola tabella contiene più
specializzazioni di una stessa entità base: gli attributi di un figlio non avranno senso nel caso di un “fratello” (e saranno quindi NULL)
● La strategia single table si usa annotando la classe padre con● @Inheritance(strategy= SINGLE_TABLE)
34
Ereditarietà: single table● Quando si usa una singola tabella per tutta la
gerarchia, bisogna specificare una “colonna discriminante” che contiene informazioni sul tipo dell'oggetto
● Si aggiunge @DiscriminatorColumn nelle annotazioni della classe padre● @Entity● @DiscriminatorColumn(name=”tipologia”)● public class Padre {...}
35
Ereditarietà: single table● Per default Hibernate, come discriminante,
crea/cerca una colonna chiamata DTYPE di tipo stringa
● Questo default può essere sovrascritto:● discriminatorType: può essere
– DiscriminatorType.STRING– DiscriminatorType.CHAR– DiscriminatorType.INTEGER
● lenght: la lunghezza del campo, usata solo nel caso stringa
36
Ereditarietà: single table● I valori contenuti nella colonna discriminante
sono, per default, i nomi delle classi figlio● Volendo usare valori differenti si dovranno
annotare le classi figlio con @DiscriminatorValue(“valore”)
37
Ereditarietà: Joined● Le annotazioni sono identiche al caso Single
Table● C'è @DiscriminatorColumn,
@DiscriminatorValue eccetera● Si attua annotando la classe padre con
● @Inheritance(strategy = JOINED)● A basso livello Hibernate creerà/cercherà una
tabella per il padre...● ...e una tabella per ogni figlio, che conterrà
soltanto gli attributi che non sono già presenti nel padre
38
Ereditarietà: Table Per Class● Con questa strategia, tutti gli oggetti (padre e
figli), se concreti, avranno tabelle che contengono tutti i loro attributi
● Basta annotare la classe padre con● @Inheritance(strategy = TABLE_PER_CLASS)
39
Ereditarietà: Quale Strategia?● Usando Joined si avrà uno schema più
mantenibile, perchè ogni modifica di un figlio impatta solo sulla tabella del figlio, e ogni modifica al padre impatta solo sulla tabella padre
● Usando table-per-class, ogni modifica al padre impatterà anche su tutte le tabelle dei figli – mentre le performance sono migliori del caso Joined, perché non ci sono join
● Single Table ha migliori performance, perché tutte le query vengono fatte su una sola tabella, ma può diventare disordinata
40
Mapped Superclass● Un caso speciale di ereditarietà si ha quando
la classe padre non è resa persistente e quindi, in teoria, non dovrebbe possedere annotazioni Hibernate
● In realtà per far funzionare l'ereditarietà dovremo annotare la classe padre come se fosse persistente, e poi aggiungere● @MappedSuperClass
● Subito dopo @Entity, per specificare che la classe padre è solo mappata e non deve essere resa persistente
41
Altre annotazioni
42
Dati temporali● Le proprietà di tipo java.util.Date vengono di
default mappate su attributi di tipo TIMESTAMP● Questo comportamento può essere modificato
con l'annotazione @Temporal● @Temporal(TemporalType.DATE)● @Temporal(TemporalType.TIME)● @Temporal(TemporalType.TIMESTAMP)
43
Large Objects● I Large Objects sono attributi di una tabella
che contengono oggetti molto grandi: ● stringhe estese (LONGTEXT)● sequenze di byte (BLOB, Binary Large OBject)
● Per specificare che una certa proprietà va salvata in un attributo di tipo large, basta annotarla con @Lob● @Lob● public String getTitle() { … }
● Si creerà un attributo che non sarà varchar
44
Generare Indici● Questa è una annotazione non inclusa in JPA,
solo in Hibernate● Gli indici possono essere applicati:
● Alla singola colonna, con l'annotazione– @Index(name=”nomedellindice”)
● Su diverse colonne contemponaneamente, annotando la classe con:
@Table(appliesTo=”nometabella”, indexes = {@Index(name=”nomeindice1”, columnNames={“col1”, “col2”}
})
45
Named Query● Una Named Query è una query (in HQL) a cui
diamo un nome, per poter essere richiamata successivamente
● È composta quindi da una coppia (nome, espressione)
● Si definisce con annotazione di classe:
@Entity
@NamedQuery(name=”findAllStudents”,
query=”from Students”)
public class Student{ … }
46
Named Query● La caratteristica delle Named Query è quella
di essere riutilizzabili● Se vogliamo riutilizzarle tra più classi,
dobbiamo definirle a livello di package● Si crea un file chiamato package-info.java che
conterrà le annotazioni che devono essere condivise tra tutte le classi del package
@NamedQuery(name=”findAllStudents”,
query=”from Students”)
package it.unirc.pja.example2
47
Hibernate Console
48
Hibernate Console● La Hibernate Console è un tool che fa parte
del plugin per Eclipse● Permette di svolgere le seguenti funzioni
● Visualizzazione rapida dei mapping e degli schemi● Scrittura di query di test HQL (parametrizzate e
non)● Generazione dello schema del database a partire
dai mapping● Generazione di diagrammi che rappresentano i
mapping
49
Hibernate console: esempio● Vedi
50
Metodi base di Session
51
Metodi base di Session● Abbiamo già visto che l'oggetto Session è
fondamentale● Svolge la funzione di interfaccia tra i nostri
oggetti Java e Hibernate● Session ha un certo numero di metodi utilizzati
per richiamare le funzionalità di Hibernate
52
Salvataggio● Quando si crea un nuovo oggetto che possiede
mapping Hibernate, questo non viene automaticamente reso persistente
● Si deve salvare per la prima volta in una Session, tramite il metodo save()● session.save(oggetto);
●
53
Nota Bene: Confronto● Un certo oggetto reso persistente avrà due modi per
essere identificato:
● Identificativo di istanza della classe (che rappresenta l'oggetto)
● Chiave primaria (che rappresenta la tupla)● Nell'ambito di una stessa sessione entrambi gli
identificativi potranno essere usati, mentre su più sessioni l'identificativo di istanza potrebbe cambiare
● Bisogna quindi evitare di usare l'operatore == (che confronta l'identificativo di istanza); meglio usare equals(), magari implementata da noi con il confronto tra le primary key
54
Caricamento● Il caricamento di oggetti persistenti avviene
tramite load()● Ha due parametri:
● La classe della entity, passata come nome (String) o come oggetto Class
● L'id dell'oggetto (chiave primaria)● Se la chiave specificata non esiste verrà
lanciata una eccezione● Simile a load() è get(): se la chiave non esiste
restituisce NULL invece di una eccezione
55
Caricamento: Lock modes● Sia load() che get() supportano un terzo
parametro, opzionale, che serve a specificare il lock degli oggetti caricati
● Il lock è usato per evitare problemi di aggiornamento concorrente
● I lock vengono rilasciati alla fine della transazione
● Attenzione: usare i lock può creare problemi di deadlock, se più thread in contemporanea acquisiscono parte delle risorse e non le rilasciano (vedi Problema dei 5 filosofi)
56
Caricamento: Lock modes● Lock mode disponibili:
● NONE: default; legge dal database solo se l'oggetto richiesto non è in cache
● READ: legge semre l'oggetto dal database● UPGRADE: se supportato dal database/dialect,
imposta un lock di aggiornamento; se non è possibile ottenerlo, attende che vengano rilasciati i lock che ostacolano
● UPGRADE_NOWAIT: come UPGRADE, ma se non è possibile ottenere il lock lancia subito una eccezione
57
Aggiornamento dal DB● Se vogliamo ottenere l'ultima versione
dell'oggetto dal database (eliminando le modifiche fatte da noi) basta utilizzare il metodo refresh()
● In realtà Hibernate nella maggior parte dei casi lo fa in automatico, non c'è bisogno di chiamare refresh() direttamente● Il caso particolare si ha quando l'oggetto viene
aggiornato da applicazioni esterne oppure da query SQL: in questo caso Hibernate non può sapere che l'oggetto è stato aggiornato
58
Update● Se un oggetto è persistente, ogni modifica che
operiamo su di esso viene “accodata” ed eseguita da Hibernate alla fine della sessione
● Non è necessario eseguire esplicitamente una operazione di update (con il metodo update())
● Nel caso volessimo salvare tutte le operazioni effettuate sul db prima del termine della sessione, possiamo usare il metodo flush()
59
Update: flush() modes● La strategia di flushing “automatica” prevede
di salvare ogni oggetto prima di eseguire una query che restituisce l'oggetto stesso
● Esistono altre strategie, impostabili con setFlushMode():● ALWAYS: prima di ogni query effettua flush(),
salvando tutti gli oggetti. Lento● COMMIT: esegue flush() solo in fase di commit● MANUAL: non esegue mai flush(), dobbiamo
richiamarlo esplicitamente nel codice
60
Delete● Se si deve cancellare un oggetto persistente si
può usare il metodo delete()● Il parametro di delete() è un Object, che può
essere:● L'oggetto da cancellare● Un oggetto dello stesso tipo di quello da
cancellare, con la proprietà chiave impostata (questo si usa se non abbiamo l'oggetto completo ma conosciamo solo l'id)
61
SaveOrUpdate()● Il metodo save() rende persistente un oggetto
(INSERT) mentre update() lo aggiorna (UPDATE)● La prima volta va usato save(), le successive
update()● Se la prima volta si usa update(), o nelle
successive save(), ci sarà un errore● Se non sappiamo se usare save() o update(),
possiamo usare saveOrUpdate() che effettua una select per vedere se l'oggetto esiste, e poi chiama save() o update()
62
Disassociazione● Può capitare di aver bisogno che un certo
oggetto non sia più persistente● Dato che ogni modifica dentro una sessione
viene tracciata da Hibernate, per poi eseguire gli aggiornamenti, vorremmo evitare che venissero tracciate operazioni che non vanno riportate sul database
● In questo caso si usa il metodo evict(), che disassocia un oggetto dalla sessione corrente
63
Hibernate Query Language
64
Sintassi base● Hibernate fornise un proprio linguaggio di
query, basato suglio oggetti e modellato in modo molto simile a SQL
● Poiché dall'interno di Hibernate si possono sfruttare le informazioni di mapping, molte delle query in HQL sono più compatte delle corrispondenti versioni SQL
● Ovviamente, HQL viene tradotto in SQL prima di essere inviato al database
65
Sintassi base● SELECT● È identico all'equivalente SQL, solo che la
parte di proiezione (SELECT nome, cognome) è opzionale: se non si specifica si assume “SELECT *”
● La clausola FROM è seguita dal nome di una classe, piuttosto che di una tabella
66
CreateQuery● Un oggetto di tipo Query può essere ottenuto,
a partire dalla corrispondente stringa HQL, tramite il metodo createQuery() di Session● Query q = session.createQuery(“from User”);
67
Controllare codice SQL generato● Il codice HQL viene convertito in SQL prima di
essere inviato al database● Può capitare che, in alcuni casi, la traduzione
sia una query SQL inefficiente● Possiamo visualizzare le traduzioni tramite:
● Hibernate console in Eclipse● File di Log, se abilitiamo la proprietà “show_sql”
nel file di configurazione di Hibernate
68
Filtri condizionali● Ovvero la clausola WHERE● Possiamo usare tutti gli operatori tipici di SQL
(OR, AND, =, <>, like eccetera)● In più possiamo definire dei parametri, per
realizzare query parametriche● I parametri si inseriscono come :nomeparametro● Es: “From User where name=:name”
69
Named Parameters● I Named Parameters ci aiutano a difendere la
nostra applicazione dagli attacchi di SQL Injection
● Infatti la sostituzione tra parametri e valore effettivo avviene controllando il tipo esatto dell'oggetto passato● Es.
– Query query = session.createQuery(“from User where address=:address”);
– query.setEntity(“address”, myAddress);
70
Pagination● Normalmente in una web application
restituiremo solo un certo numero di dati all'utente
● Se sono molti, questo significa mostrare una pagina alla volta
● L'oggetto query possiede due metodi per supportare questa funzionalità:● setFirstResult(int) : indica la tupla di partenza● setMaxResults(int) : indica quante tuple prelevare
Risultato unico
● L'oggetto Query ha un metodo list() che restituisce tutti i risultati di una query
● Possiede anche un metodo uniqueResult() che restituisce solo un oggetto
● Se l'oggetto è più di uno, lancia una eccezione● Quando vogliamo ottenere solo il primo
risultato, dovremo usare una combinazione di uniqueResult() e setMaxResults(1)
Order by
● HQL supporta la clausola Order By che viene usata come in SQL
● “order by nomeproprietà [desc|asc]”● Se vogliamo ordinare per più proprietà, basta
separarle con la virgola
Join
● Tramite le join si possono usare più classi in una sola query
● Hibernate supporta più tipi di join:● Inner, cross, left outer, right outer, full outer
● Se ci sono i mapping non c'è bisogno di specificare le condizioni di join
Aggregazioni
● HQL supporta gli operatori di aggregazione:● avg(name)● count(name|*)● max(name)● min(name)● sum(name)
Aggiornamenti in blocco
● Quando serve aggiornare o cancellare un certo numero di oggetti contemporaneamente, usare un ciclo for potrebbe essere inefficiente
● Si possono usare gli equivalenti HQL di UPDATE e DELETE
● Basta creare una query di update/delete, e poi chiamarne il metodo executeUpdate()
SQL Nativo
● L'uso di SQL nativo si dovrebbe evitare per ottenere la massima portabilità; se proprio è necessario, possiamo creare una query in SQL utilizzando il metodo● Session.createSQLQuery(String)