Studio e Sperimentazione di Framework Java per la Persistenza

Facoltà di Ingegneria dell'informazione, informatica e statistica
Ingegneria informatica
Tesi di Laurea
Studio e Sperimentazione di
Framework Java per la Persistenza
Relatore : Prof. Massimo Mecella
Laureando : Giulio Gullà
matricola : 1144509
Anno Accademico : 2011-2012
1
2
3
Sommario
1. INTRODUZIONE...................................................................................................................................... 7
1.1 Hibernate ................................................................................................................................................ 8
Hibernate Tools ........................................................................................................................................ 8
1.2 Java Persistence API ............................................................................................................................... 8
2. SVILUPPARE CON HIBERNATE E JPA ............................................................................................... 9
2.1 Generale ................................................................................................................................................. 9
2.2 Scenari di sviluppo .................................................................................................................................. 9
2.2.1 Top Down ........................................................................................................................................ 9
2.2.2 Bottom up........................................................................................................................................ 9
2.2.3 Middle out ..................................................................................................................................... 10
2.2.4 Meet in the middle ........................................................................................................................ 10
2.3 Classi di dominio ................................................................................................................................... 10
2.4 File di configurazione............................................................................................................................ 13
2.5 Mapping ............................................................................................................................................... 17
2.5.1 Mapping di proprietà identificatore .............................................................................................. 20
2.5.2 Mapping di proprietà con molteplicità massima 1 ........................................................................ 25
2.5.3 Tipi Hibernate ................................................................................................................................ 27
2.5.4 Mapping di proprietà con molteplicità massima maggiore di 1 ..................................................... 28
2.5.5 Mapping di proprietà istanze di una classe che realizza un tipo UML ........................................... 31
2.5.6 Mapping di associazioni ................................................................................................................. 35
2.5.7 Mapping di ereditarietà ................................................................................................................. 78
2.6 Interfacce di Hibernate ......................................................................................................................... 88
2.6.1 SessionFactory (org.hibernate.SessionFactory) ................................................ 88
2.6.2 Session (org.hibernate.Session) ................................................................................. 88
2.6.3 Transaction (org.hibernate.Transaction) .............................................................. 89
2.6.4 Query (org.hibernate.Query) ........................................................................................... 89
2.7 Java Persistence API ............................................................................................................................. 90
2.7.1 EntityManagerFactory (javax.persistence.EntityManagerFactory)........... 90
2.7.2 EntityManager (javax.persistence.EntityManager)............................................ 90
2.7.3 EntityTransaction (javax.persistence.EntityTransaction) ......................... 91
4
2.7.4 Query (javax.persistence.Query) ................................................................................. 92
2.8 Hibernate Query Language e Java Persistence Query Language .......................................................... 92
2.8.1 Creazione di un oggetto query ...................................................................................................... 92
2.8.2 Binding dei parametri .................................................................................................................... 94
2.8.3 Gestione dei risultati ..................................................................................................................... 95
2.8.4 Operazioni di aggiornamento ed eliminazione .............................................................................. 96
2.8.5 Query ............................................................................................................................................. 97
2.8 Generazione dello schema della base di dati ........................................................................................ 98
2.9 Creazione e funzionamento di un progetto .......................................................................................... 99
3. APPLICAZIONE DI ESEMPIO ........................................................................................................... 106
3.1 Requisiti .............................................................................................................................................. 106
3.2 Classi di dominio ................................................................................................................................. 107
3.3 Eccezioni ............................................................................................................................................. 116
3.4 Persistenza ......................................................................................................................................... 117
3.5 Attività ................................................................................................................................................ 120
3.5.1 AttivitàComplesse ........................................................................................................................ 123
3.5.2 Attività atomiche ......................................................................................................................... 131
3.6 Interfaccia grafica ............................................................................................................................... 144
3.7 Main ................................................................................................................................................... 165
4. CONSIDERAZIONI FINALI ................................................................................................................ 166
Riferimenti bibliografici ................................................................................................................................ 167
5
6
1. INTRODUZIONE
La gestione della persistenza è un argomento delicato nella progettazione di applicazioni software.
Lo sviluppo di applicazioni Object-Oriented che utilizzano un DBMS relazionale per immagazinare i dati in
memoria secondaria è uno scenario molto comune. Ciò che rende complessa la costruzione dello strato di
persistenza è il problema dell’object/relational impedence mismatch, la discrepanza tra il paradigma
Object-Oriented e il paradigma relazionale. Questo problema è causato dal fatto che il paradigma ObjectOriented è basato su principi di ingegneria del software mentre il paradigma relazionale è basato su principi
matematici. La progettazione e la realizzazione delle classi di dominio e della base di dati mostrano questa
differenza: le classi di dominio vengono progettate con UML, un linguaggio di modellazione ObjectOriented, mentre per la progettazione della base di dati viene inizialmente utilizzato il modello EntitàRelazione per creare uno schema concettuale che successivamente viene indebolito per permettere la
traduzione dello schema nel modello relazionale della base di dati. Il problema dell’impedence mismatch
deve essere analizzato e risolto nel modo opportuno in fase di progettazione.
Un altro aspetto molto importante nella gestione della persistenza è l’interazione tra lo strato di logica
applicativa e lo strato di persistenza. In applicazioni Java la comunicazione con la base di dati avviene
utilizzando API specifiche come ODB (Open Database Connectivity) o JDBC (Java Database Connectivity).
Una delle strategie più valide e utilizzate è l’uso del pattern Data Access Object (DAO) per la creazione di
oggetti che costituiscono uno strato di comunicazione con la base di dati. La realizzazione dei DAO avviene
usando API JDBC per rendere disponibili le operazioni CRUD (Create Read Update Delete) allo strato di
logica applicativa. I DAO incapsulano l’accesso alla base di dati e permettono di gestire la maggior parte
degli scenari ma la loro realizzazione necessita di una conoscenza dettagliata della base di dati.
Questo lavoro ha lo scopo di presentare una strategia alternativa per la gestione della persistenza in
applicazioni Java: l’Object-Relational Mapping (ORM). Questa tecnica consiste nel mappare le classi di
dominio dell’applicazione su una base di dati relazionale. Il mapping avviene utilizzando appositi framework
di persistenza che, dopo una fase iniziale di configurazione, permettono di generare una base di dati
relazionale partendo dalle classi di dominio dell’applicazione e di gestirne l’accesso.
In particolare vengono analizzati due importanti framework java per la persistenza: Hibernate e Java
Persistence API.
La trattazione prosegue con una introduzione ai due framework esaminati e con un lungo capitolo dedicato
alle tecniche di sviluppo in applicazioni Hibernate e Java Persistence. Questo capitolo è il cuore
dell’elaborato, vengono spiegati i principali costrutti utili in differenti scenari. Ogni tecnica illustrata è stata
sperimentata e viene accompagnata da un esempio che ne mostra l’utilizzo. Nel terzo capitolo viene
presentata un’applicazione Java Persistence completa e in conclusione ci sono le considerazioni finali.
7
1.1 Hibernate
Hibernate è un framework open source con servizi Object-Relational Mapping (ORM) in Java.
Hibernate ORM (fino alla versione 4.0 conosciuto come Hibernate Core), composto dalle API native di
Hibernate e il suo motore, è disponibile alla versione 4.1, utilizzata in questa trattazione.
Il software è composto da API Java che permettono di gestire la persistenza con il mapping delle classi sulla
base di dati e forniscono interfacce per l’accesso ai dati persistenti.
Hibernate Tools
Hibernate Tools è un insieme di strumenti per Hibernate racchiusi in una suite di plugin Eclipse e dei task
Ant per l'integrazione a stadio di compilazione.
Le funzioni messe a disposizione sono:

Mapping Editor: un editor per i file di mapping XML;

Console: permette di configurare le connessioni alla base di dati ed eseguire query
interattivamente;

Reverse Engineering: la funzione più importante di Hibernate Tools. Consente di generare le classi
di dominio dell'applicazione Java dalle tabelle della base di dati;

Wizards: procedure fornite per la generazione dei file di configurazione di Hibernate e dei file di
configurazione Console;

Task Ant: utilizzato per avviare la generazione dello schema della base di dati, la generazione dei
file di mapping e la generazione del codice Java durante la compilazione.
1.2 Java Persistence API
Le Java Persistence API (JPA) sono un framework Java per la gestione della persistenza.
Attualmente le Java Persistence API sono alla versione 2.0 (JSR 317: Java Persistence 2.0).
Come Hibernate, le Java Persistence API permettono di effettuare il mapping delle classi di dominio sulle
tabelle di una base di dati relazionale e di interagire con esse.
Per utilizzare le Java Persistence API è necessaria un’implementazione.
In questo studio verrà utilizzata l’implementazione di Hibernate analizzando le strategie di mapping
appartenenti allo standard delle Java Persistence API e tralasciando costrutti specifici del software.
L’implementazione di Hibernate delle Java Persistence API è composta da:

Hibernate Annotations: sistema di metadati per il mapping delle classi di dominio;

Hibernate EntityManager: implementazione delle interfacce di programmazione e delle regole
riguardanti il ciclo di vita delle entità definito dalle specifiche JPA 2.0.
8
2. SVILUPPARE CON HIBERNATE E JPA
2.1 Generale
Un’applicazione sviluppata con Hibernate o con le Java Persistence API è composta da quattro elementi:

Classi di dominio Java

File di configurazione

Metadati per il mapping

Base di dati relazionale
Questi elementi vengono realizzati secondo diverse modalità in base allo scenario di sviluppo.
2.2 Scenari di sviluppo
Vengono ora presentati i possibili scenari di sviluppo con Hibernate e le Java Persistence API.
In questo studio verrà analizzato a fondo solo lo scenario top down.
2.2.1 Top Down
Nello sviluppo top down si parte dalle classi di dominio dell'applicazione e si effettua il mapping su tabelle
della base di dati relazionale.
Lo sviluppo top down è molto comodo in assenza di uno schema della base di dati.
Strumenti principali di Hibernate utilizzati:
<hbm2ddl> : permette di creare, aggiornare e convalidare lo schema della base di dati utilizzando i file di
mapping e configurazione di Hibernate.
2.2.2 Bottom up
Scenario di reverse engineering in cui si ha una base di dati legacy e vengono generate le classi di dominio e
i metadati per il mapping.
La generazione delle classi con questo sviluppo è rapida ma lo schema con cui sono organizzate le classi di
dominio non potrà mai avvicinarsi alla costruzione ideale del diagramma delle classi. Il mapping è
condizionato dalle tabelle dello schema e genererà un modello delle classi di dominio degradato.
Strumenti principali di Hibernate utilizzati:
<hbm2java> : permette la generazione delle classi di dominio Java utilizzando i file di reverse engineering
e configurazione di Hibernate.
<hbm2hbmxml> : permette la generazione dei file di mapping delle classi di dominio Java utilizzando I file
di reverse engineering e configurazione di Hibernate.
9
2.2.3 Middle out
Scenario che punta ad utilizzare a fondo le funzioni di Hibernate per lo sviluppo di una nuova applicazione
Java. Il punto di partenza è la scrittura “a mano” dei file XML di mapping dai quali verranno create le tabelle
per la base di dati utilizzando lo strumento <hbm2ddl> e generate le classi Java con <hbm2java>.
Lo scenario di sviluppo middle out non è possibile utilizzando le Java Persistence API.
Questo approccio è consigliabile solo a sviluppatori con una profonda conoscenza di Hibernate.
2.2.4 Meet in the middle
Lo scenario più complesso e difficile. Si tratta di combinare le classi di dominio con uno schema di base di
dati legacy.
Mappare classi di dominio Java arbitrarie su uno schema dato non è possibile, sarà quindi necessario
effettuare refactoring delle classi Java, dello schema della base di dati o di entrambi.
E’ uno scenario piuttosto raro.
2.3 Classi di dominio
Le classi di dominio Java dovranno essere realizzate secondo l’approccio Plain Old Java Object (POJO).
Verranno ora elencate le principali caratteristiche della realizzazione delle classi di dominio, in particolare
sottolineando le differenze tra la realizzazione ideale e l'approccio POJO.
Caratteristiche di una classe POJO:
-
Costruttore senza argomenti
E’ richiesto da Hibernate e deve essere almeno visibile all’interno del package.
Solitamente viene lasciato vuoto e vengono definiti ulteriori costruttori per l’utilizzo all’interno
dell’applicazione.
-
Proprietà identificatore
La classe deve fornire una proprietà con funzione di identificatore.
-
Classe non-final
Le classi non-final sono da preferire per garantire il funzionamento delle operazioni di lazy loading
di Hibernate.
-
Metodi get e set per le proprietà degli oggetti della classe
E’ importante che siano definiti per tutte le proprietà della classe.
I nomi dei metodi devo essere necessariamente nella forma get (o set) seguito dal nome della
proprietà con la prima lettera maiuscola (es. getProprietà() e setProprietà()).
Note:
1 - Il metodo set di proprietà immutabili come quella che ha ruolo di identificatore dell’oggetto
può essere definito privato;
10
2 - Proprietà persistenti che sono collezioni avranno il metodo set privato e il metodo get che
non restituisce un clone della collezione ma la collezione stessa.
-
Metodi di business
-
Metodi speciali
I metodi speciali toString(), equals(), hashCode() e clone() devono essere ridefiniti
in alcune circostanze:
1 – è necessario ridefinire equals() e hashCode() se si vogliono inserire istanze di una classe
persistente in un set;
2 – è necessario ridefinire toString(), equals() e hashCode() se la classe realizza un
nuovo tipo di valore;
3 - è necessario ridefinire clone() se nella classe sono definiti metodi che effettuano side-effect.
Associazioni:
Per la realizzazione di associazioni secondo l'approccio POJO non vengono utilizzate classi TipoLink.

Associazioni a responsabilità singola senza attributi
- molteplicità 0..1 (o 1..1): la classe con responsabilità sull'associazione ha una proprietà riferimento
ad un'istanza della classe persistente a cui è associata;
- molteplicità 0..* (o 1..*): la classe con responsabilità sull'associazione ha una proprietà insieme di
riferimenti ad istanze delle classi persistenti a cui è associata;

Associazioni a responsabilità doppia senza attributi
Come per la realizzazione ideale del diagramma delle classi, viene utilizzata una classe manger che
gestisce le modifiche dell'associazione. La classe manager dovrà però gestire direttamente
riferimenti a istanze delle classi conivolte e non istanze di classi TipoLink.
Nota: la classe manager non deve essere resa persistente.

Associazioni con attributi
Hibernate e JPA non forniscono costrutti specifici per il mapping di associazioni con attributi.
In caso di associzioni con molteplicità massima pari a 1, è possibile utilizzare un approccio “naif”,
trasformando gli attributi dell'associazione in nuove proprietà della classe che ha responsabilità
sull’associazione. Se l'associazione con attributi ha molteplicità massima superiore a 1, si può
considerare di effettuare refactoring aggiungendo una nuova classe di dominio per rappresentare
l’associazione. Una possibile strategia è spiegata nell’apposito paragrafo.
11
Esempio : Classe POJO
File Utente.java
package domain;
import java.util.*;
public class Utente {
private
private
private
private
private
String username;
String password;
Set<String> numTelefono;
Indirizzo indirizzo;
Set<Acquisto> acquisti;
protected Utente() { }
public Utente(String username, String password) {
this.username = username;
this.password = password;
numTelefono = new HashSet<String>();
effettua = new HashSet<Acquisto>();
}
public String getUsername() {
return this.username;
}
private void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
12
public Set<String> getNumTelefono() {
return (Set<String>) this.numTelefono;
}
private void setNumTelefono(Set<String> numTelefono) {
this.numTelefono = numTelefono;
}
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
public void setIndirizzo(Indirizzo indirizzo) {
this.indirizzo = indirizzo;
}
public Set<Acquisto> getAcquisti() {
return (Set<Acquisto>) this.acquisti;
}
private void setAcquisti(Set<Acquisto> acquisti) {
this.acquisti = acquisti;
}
public double calcolaCostoSpedizione(Indirizzo posizione) { ... }
}
2.4 File di configurazione
I file di configurazione contengono informazioni riguardo le impostazioni di connessione al DBMS e la
posizione dei file di mapping che verranno utilizzati per l’applicazione.
Hibernate
Il file di configurazione principale di Hibernate è hibernate.cfg.xml.
Questo file deve essere salvato nella radice del classpath dell’applicazione.
Esempio : File di configurazione di Hibernate
File hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">
org.postgresql.Driver
13
</property>
<property name="connection.url">
jdbc:postgresql://localhost:5432/test
</property>
<property name="connection.username">postgres</property>
<property name="connection.password">password</property>
<!-- Use the C3P0 connection pool provider -->
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<!-- SQL dialect -->
<property name="dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<!-- Mapping files -->
<mapping resource="domain/Utente.hbm.xml"/>
<mapping resource="domain/Acquisto.hbm.xml"/>
</session-factory>
</hibernate-configuration>
L'elemento principale è <hibernate-configuration> che contiene al suo interno l'elemento
<session-factory> che definisce tutte le impostazioni per la connessione ad una base di dati.
Descrizione delle proprietà hibernate.connection:
hibernate.connection.driver_class
Nome del driver JDBC specifico per il DBMS
utilizzato.
hibernate.connection.url
Indirizzo JDBC della base di dati per la connessione.
hibernate.connection.username
Nome utente per la connessione al DBMS.
hibernate.connection.password
Password per la connessione al DBMS.
Seguono proprietà per la gestione di un pool di connessioni JDBC.
Nell'esempio viene utilizzato un software di terze parti, c3p0, per avere migliori performance e stabilità. In
alternativa può essere utilizzato il gestore interno ad Hibernate che utilizza un algoritmo piuttosto
semplice. Per fare ciò si utilizzerà la proprietà hibernate.connection.pool_size specificando il
numero massimo di connessioni come valore.
14
Descrizione delle proprietà hibernate.c3p0:
hibernate.c3p0.min_size
Numero minimo di connessioni JDBC che c3p0
mantiene pronte.
hibernate.c3p0.max_size
Numero massimo di connessioni JDBC nel pool. Se le
connessioni vengono esaurite viene lanciata
un'eccezione a runtime.
hibernate.c3p0.timeout
Periodo di timeout dopo il quale una connessione
inattiva viene rimossa dal pool.
hibernate.c3p0.max_statements
Numero massimo di prepared statement in cache. Il
caching è molto importante per migliorare le
performance di Hibernate.
hibernate.c3p0.idle_test_period
Periodo di tempo in secondi prima che una
connessione venga automaticamente convalidata.
La proprietà dialect specifica la variante di SQL utilizzata dal DBMS.
Gli elementi <mapping> specificano i file di mapping che Hibernate dovrà utilizzare.
Java Persistence API
Il file di configurazione di JPA è chiamato persistence.xml e contiene le stesse informazioni contenute
in hibernate.cfg.xml con alcune differenze.
persistence.xml deve essere salvato in una directory nominata META-INF posizionata nel classpath
dell’applicazione.
Esempio : File di configurazione di JPA
File persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="testPU">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- Not needed, Hibernate supports auto-detection in JSE
<class>domain.Utente</class>
<class>domain.Acquisto</class>
-->
<properties>
<property name="hibernate.archive.autodetection"
15
value="class, hbm"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.connection.driver_class"
value="org.postgresql.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:postgresql://localhost:5432/test"/>
<property name="hibernate.connection.username"
value="postgres"/>
<property name="hibernate.connection.password"
value="password"/>
<property name="hibernate.c3p0.min_size"
value="5"/>
<property name="hibernate.c3p0.max_size"
value="20"/>
<property name="hibernate.c3p0.timeout"
value="300"/>
<property name="hibernate.c3p0.max_statements"
value="50"/>
<property name="hibernate.c3p0.idle_test_period"
value="3000"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
L’elemento principale è <persistence> che contiene la definizione della grammatica, al cui interno ci
possono essere uno o più elementi <persistence-unit> che contengono le informazioni per la
connessione al DBMS. Ogni elemento <persistence-unit> ha un nome, definito dall’attributo name,
che verrà utilizzato per la creazione di EntityManager per l’accesso alla base di dati.
Il primo elemento all’interno di <persistence-unit> è <provider> con il quale viene specificata
l’implementazione di JPA utilizzata all’interno dell’applicazione, in questo caso l’implementazione di
Hibernate.
La specifica delle classi Java contenenti le annotazioni per il mapping può avvenire utilizzando elementi
<class> oppure con la proprietà di Hibernate hibernate.archive.autodetection che
permette di determinare quali file verranno rilevati automaticamente da Hibernate per il mapping.
Nell’esempio il valore dell’elemento hibernate.archive.autodetection è class,hbm, il valore
di default, che permette il rilevamento automatico di classi Java con annotazioni e file di mapping di
Hibernate.
A seguire ci sono tutte le proprietà che specificano le impostazioni per la connessione JDBC alla base di dati,
analoghe a quelle contenute nel file di configurazione di Hibernate.
16
2.5 Mapping
Il mapping delle classi Java di dominio in tabelle di una base di dati relazionale in Hibernate e JPA viene
effettuato utilizzando metadati differenti: Hibernate utilizza file di mapping XML mentre JPA utilizza
annotazioni inserite all’interno del codice delle classi stesse. Entrambi i metodi specificano esattamente
come una classe deve essere resa persistente.
Hibernate
Il file di mapping Hibernate di una classe Classe.java è chiamato Classe.hbm.xml. E' composto da
un elemento principale di nome <hibernate-mapping> che contiene al suo interno un elemento
<class> con tutte le informazioni per il mapping della classe java.
Esempio: File di mapping per la classe Utente.java
File Utente.hbm.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="domain.Utente" table="utente">
<id name="username" column="username" type="string"/>
<property name="password" column="password"
type="string" not-null="true"/>
<set name="effettua">
<key column="username" />
<one-to-many class="domain.Acquisto"/>
</set>
</class>
</hibernate-mapping>
Descrizione di alcuni attributi dell'elemento <class> :
name="ClassName"
Il nome completo della classe persistente.
table="tableName"
Il nome della tabella in cui verrà mappata la classe. Di
default è il nome della classe.
schema="schemaName"
Il nome dello schema della base di dati.
17
Java Persistence API
In JPA i metadati utilizzati per il mapping sono annotazioni indivisibili dal codice Java delle classi di dominio.
Esempio: Annotazioni per il mapping della classe Utente.java
File Utente.java
package domain;
import javax.persistence.*;
import java.util.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<Acquisto> effettua;
protected Utente() {
effettua = new HashSet<Acquisto>();
}
public Utente(String username, String password) {
this.username = username;
this.password = password;
effettua = new HashSet<Acquisto>();
}
@Id
public String getUsername() {
return this.username;
}
private void setUsername(String username) {
this.username = username;
}
@Column(name = "password", nullable = false)
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
@OneToMany(targetEntity = domain.Acquisto.class)
@JoinColumn(name = "username", nullable = false)
public Set<Acquisto> getEffettua() {
return this.effettua;
}
private void setEffettua(Set<Acquisto> effettua) {
this.effettua = effettua;
}
18
public void inserisciAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni("L'oggetto deve essere
inizializzato");
else
effettua.add(acquisto);
}
public void rimuoviAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni("L'oggetto deve essere
inizializzato");
else
effettua.remove(acquisto);
}
}
Per utilizzare le annotazioni in una classe Java è necessario importare il contenuto del package
javax.persistence contenente tutte le classi e interfacce necessarie.
La prima annotazione è @Entity in corrispondenza della dichiarazione di classe che la definisce come
entità, un oggetto Java che verrà reso persistente. A seguire può essere utilizzata l’annotazione @Table
che permette di definire il nome della tabella in cui verrà mappata la classe e altre informazioni opzionali
come lo schema, il catalogo o anche vincoli di chiave. L’annotazione @Table è opzionale, il valore di
default della proprietà name è il nome della classe Java.
Le successive annotazioni sono utilizzate per il mapping delle proprietà e delle associazioni.
Sviluppando con JPA è importante impostare l’accesso ad un’entità.
L’accesso può essere effettuato secondo due modalità:
-
field-based : il provider accede allo stato dell’entità direttamente dalle variabili d’istanza. Per
effettuare questo tipo di accesso è necessario inserire le annotazioni prima dei campi delle
proprietà a cui si riferiscono;
-
property-based : il provider utilizza i metodi get e set per accedere alle proprietà persistenti
dell’entità. Per effettuare l’accesso property-based è necessario inserire le annotazioni prima dei
metodi get delle proprietà.
La tipologia di accesso per una entità viene decisa in base all’impostazione di accesso della proprietà
identificatore.
Nota: Anche sviluppando con Hibernate è possibile impostare la tipologia di accesso alle proprietà di
un’entità. Il comportamento di default di Hibernate è l’accesso property-based, però questa impostazione
può essere cambiata con l’utilizzo dell’attributo access nei file di mapping.
19
2.5.1 Mapping di proprietà identificatore
Le classi mappate devono dichiarare la colonna che è chiave primaria della tabella.
Esempio: proprietà identificatore
File Acquisto.java
package domain;
public class Acquisto {
private long id;
...
public long getId() {
return this.id;
}
private void setId(long id) {
this.id = id;
}
...
}
Hibernate
Viene utilizzato l'elemento <id>.
Descrizione di alcuni attributi dell'elemento <id> :
name="propertyName"
Il nome della proprietà identificatore.
type="typename"
Nome che indica il tipo Hibernate.
column="column_name"
Il nome della colonna che è chiave primaria.
Di default è il nome della proprietà.
L'elemento <id> può avere l'elemento figlio <generator> con cui viene nominata una classe usata per
generare identificatori unici per le istanze della classe persistente. Se la classe generator richiede parametri,
questi verranno passati con elementi <param> figli di <generator>.
Le classi generator implementano l'interfaccia org.hibernate.id.identifiergenerator. E'
possibile fornire una propria implementazione dell'interfaccia o utilizzare le classi built-in.
20
Alcune delle classi generator:

increment : genera identificatori di tipo long, short o int che sono unici solo quando nessun
altro processo sta inserendo dati nella stessa tabella. Non usare in cluster.

identity : supporta colonne identity in DB2, MySQL, MS SQL Server, Sybase e HypersonicSQL.
L'identificatore ritornato è di tipo long, short o int.

sequence : usa sequence in DB2, PostgreSQL, Oracle, SAP DB, McKoi o generator in Interbase.
L'identificatore ritornato è di tipo long, short o int.

assigned : lascia che l'applicazione assegni un identificatore all'oggetto prima che il metodo
save() venga chiamato. E' la strategia di default se l'elemento <generator> non viene
specificato.

native : utilizza identity, sequence o hilo a seconda del DBMS utilizzato.
Esempio : utilizzo dell'elemento <id> con <generator>
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
<id name="id" column="id_acquisto">
<generator class="native"/>
</id>
...
</class>
</hibernate-mapping>
Java Persistence API
In JPA viene utilizzata l’annotazione @Id in corrispondenza della proprietà identificatore.
Per sfruttare una classe di identificatori unici si usa l’annotazione @GeneratedValue per la quale si può
impostare la proprietà strategy per scegliere una classe identificatore specifica, altrimenti assumerà il
valore di default AUTO, analogo alla classe native di Hibernate. Le strategie di generazione più comuni
sono IDENTITY e SEQUENCE, anch’esse analoghe ai corrispettivi Hibernate.
Esempio : utilizzo dell'annotazione @id con @GeneratedValue
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
21
private long id;
...
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
public long getId() {
return this.id;
}
...
}
2.4.1.1 Identificatore composto
Gli identificatori composti sono utilizzati per mappare identificatori formati da più di una proprietà.
In questo caso l’identificatore dell’entità è una classe apposita, le cui proprietà diventeranno colonne della
tabella che identificano.
E’ importante che la classe che rappresenta l’identificatore implementi java.io.Serializable e
ridefinisca i metodi equals() e hashCode().
Esempio: identificatore composto
File Utente.java
package domain;
public class Utente {
private IdUtente id;
...
protected Utente() { }
public IdUtente getId() {
return this.id;
}
private void setId(IdUtente id) {
this.id = id;
}
...
}
File IdUtente.java
package domain;
public class IdUtente implements java.io.Serializable {
22
private String username;
private String password;
protected IdUtente() { }
public IdUtente(String username, String password) {
this.username = username;
this.password = password;
}
public boolean equals(Object o) {
if (o != null && getClass().equals(this.getClass())) {
IdUtente id = (IdUtente) o;
return id.username == username
&& id.password == password;
} else {
return false;
}
}
public int hashCode() {
return username.hashCode() + password.hashCode();
}
}
Hibernate
Viene utilizzato l’elemento <composite-id> specificando l’attributo name per indicare la proprietà
identificatore e l’attributo class per indicare la classe che rapprensenta l’identificatore.
L’elemento <composite-id> ha come figli un numero di elementi <property-key> pari al numero
delle proprietà della classe identificatore.
<composite-id name="propertyName" class="ClassName">
<key-property name="property1"/>
...
<key-property name="propertyN"/>
</composite-id>
Esempio: mapping identificatore composto
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
<composite-id name="id" class="domain.IdUtente">
<key-property name="username" access="field"/>
<key-property name="password" access="field"/>
</composite-id>
...
</class>
</hibernate-mapping>
23
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username , password )
)
Java Persistence API
Il mapping di un identificatore composto con JPA viene effettuato in modo molto simile alle classi
component. La classe che rappresente l’identificatore viene annotata con @Embeddable mentre per la
proprietà id della classe entità viene utilizzata l’annotazione @EmbeddedId.
Esempio: mapping identificatore composto
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
@EmbeddedId
private IdUtente id;
...
protected Utente() { }
public IdUtente getId() {
return this.id;
}
private void setId(IdUtente id) {
this.id = id;
}
...
}
File IdUtente.java
package domain;
import javax.persistence.*;
24
@Embeddable
public class IdUtente implements java.io.Serializable {
private String username;
private String password;
protected IdUtente() { }
...
}
2.5.2 Mapping di proprietà con molteplicità massima 1
Le proprietà delle classi persistenti vengono mappate su colonne della tabella corrispondente.
Esempio: proprietà di una classe Java
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
...
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
...
}
Hibernate
Hibernate utilizza l’elemento <property> per mappare le proprietà della classe persistente.
Descrizione di alcuni attributi dell'elemento <property> :
name="propertyName"
Il nome della proprietà che deve avere la prima lettera minuscola.
type="typename"
Nome che indica il tipo Hibernate.
column="column_name"
Il nome della colonna su cui verrà mappata la proprietà.
unique="true|false"
Attiva la generazione di un vincolo unique per la colonna.
25
not-null="true|false"
Attiva la generazione di un vincolo not null per la colonna.
Esempio: mapping di proprietà
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<property name="password" column="password"
type="string" not-null="true"/>
...
</class>
</hibernate-mapping>
Java Persistence API
JPA considera tutte le proprietà non statiche e non transitorie come proprietà persistenti a meno che non
vengano annotate come @Transient. Non inserire annotazioni in corrispondenza di una proprietà
equivale ad avere l’annotazione @Basic che definisce una proprietà persistente.
Per proprietà di tipo data o tempo viene utilizzata l’annotazione @Temporal con cui viene specificata la
precisione del dato tra DATE, TIME e TIMESTAMP (data attuale, solo ora, o entrambi).
Un’altra annotazione importante è @Column che permette di definire la colonna della tabella in cui verrà
mappata la proprietà.
Il tipo di dato della colonna in cui verrà mappata la proprietà verrà definito in automatico a partire dal tipo
Java.
Esempio: mapping di proprietà
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
private String info;
...
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "id_acquisto")
public long getId() {
return this.id;
}
@Temporal(TemporalType.DATE)
26
public Date getData() {
return this.data;
}
public String getInfo() {
return this.info;
}
...
}
2.5.3 Tipi Hibernate
I tipi Hibernate sono utilizzati per il mapping del sistema di tipi Java e dei tipi definiti dagli sviluppatori nel
sistema di tipi SQL della base di dati.
I tipi Hibernate da utilizzare possono essere definiti con l'attributo type degli elementi XML <id>,
<property>, <component>, altrimenti Hibernate utilizza impostazioni di default. E' possibile utilizzare
tipi Hibernate built-in oppure si possono definire strategie di conversione personalizzate.
Tipi Hibernate di base:
Tipo Hibernate
Tipo Java
Tipo ANSI SQL
integer
int or java.lang.Integer
INTEGER
long
long or java.lang.Long
BIGINT
short
short or java.lang.Short
SMALLINT
float
float or java.lang.Float
FLOAT
double
double or java.lang.Double
DOUBLE
character
java.lang.String
CHAR(1)
byte
byte or java.lang.Byte
TINYINT
boolean
boolean or java.lang.Boolean
BIT
string
java.lang.String
VARCHAR
big_decimal
java.math.BigDecimal
NUMERIC
big_integer
java.math.BigInteger
NUMERIC
27
Tipi Hibernate per date e tempo:
Tipo Hibernate
Tipo Java
Tipo ANSI SQL
date
java.util.Date o
java.sql.Date
DATE
time
java.util.Date o
java.sql.Time
TIME
timestamp
java.util.Date o
java.sql.Timestamp
TIMESTAMP
calendar
java.util.Calendar
TIMESTAMP
calendar_date
java.util.Calendar
DATE
Per convertire un tipo Java in un tipo della base di dati differente, è necessario costruire un tipo Hibernate
personalizzato. Hibernate mette a disposizione alcune interfacce per lo scopo. Tra queste,
org.hibernate.UserType è l'interfaccia di base, ma possono anche essere utilizzate
CompositeUserType, EnhancedUserType, UserCollectionType e UserVersionType per
avere supporto a situazioni specifiche. Per definire un nuovo tipo sarà quindi sufficiente costruire una
nuova classe Java che implementi una delle interfacce specificando all'interno dei vari metodi come la
conversione dovrà avvenire e passarla come valore all'attributo type dell'elemento XML per la proprietà.
2.5.4 Mapping di proprietà con molteplicità massima maggiore di 1
Le proprietà con molteplicità massima maggiore di 1 sono collezioni omogenee di tipi base che non
vengono mappate sulla tabella che rende persistente la classe di cui sono proprietà, ma su un'altra tabella.
Esempio : proprietà Set
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
private Set<String> numTelefono;
...
public Set<String> getNumTelefono() {
return (Set<String>) this.numTelefono;
}
private void setNumTelefono(Set<String> numTelefono) {
this.numTelefono = numTelefono;
28
}
...
}
Hibernate
Nel file di mapping Hibernate della classe verrà inserito un elemento set figlio dell'elemento <class>.
<set name="propertyName" table="table_name">
<key column="column_a"/>
<element type="typename" column="column_b" not-null="true"/>
</set>
Gli attributi dell'elemento <set> specificano il nome della proprietà collezione da mappare (name) e il
nome della tabella in cui verrà mappata (table).
L'elemento <key> permette di specificare la colonna della tabella che rappresenta la collezione (attributo
column) per la quale sarà definito un vincolo di foreign key verso la chiave primaria della tabella che rende
persistente la classe.
Infine l'elemento <element> definisce il tipo di dato per la collezione e il nome della colonna della tabella
per il mapping dei valori. L'attributo not-null di <element> viene utilizzato per definire la chiave
primaria composta da column_a e column_b.
Esempio : mapping di una proprietà Set
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<set name="numTelefono" table="num_telefono">
<key column="username"/>
<element type="string" column="numero" not-null="true"/>
</set>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
CREATE TABLE num_telefono
(
username character varying(255) NOT NULL,
29
numero character varying(255) NOT NULL,
CONSTRAINT num_telefono_pkey PRIMARY KEY (username , numero ),
CONSTRAINT fk126d878df2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Per mappare collezioni ordinate si può utilizzare l'elemento <list>, con una struttura molto simile a
<set> ma con un elemento figlio <list-index> per la generazione di una colonna per il numero
d'ordine.
<list name="propertyName" table="table_name">
<key column="column_a"/>
<list-index column="position_column"/>
<element type="typename" column="column_b"/>
</list>
Per il mapping di diversi tipi di collezione vengono utilizzati elementi specifici.
Interfaccia Java
Implementazione
Elemento Hibernate
java.util.Set
java.util.HashSet
set
java.util.SortedSet
java.util.TreeSet
set
java.util.List
java.util.ArrayList
list
java.util.Collection
java.util.ArrayList
bag
java.util.Map
java.util.HashMap
map
java.util.SortedMap
java.util.TreeMap
map
Per il mapping di array vengono invece utilizzati gli elementi <primitive-array>, per array di dati
primitivi Java, e <array> per array di oggetti. L'utilizzo di array nelle classi di dominio è però sconsigliabile
perchè Hibernate non gestisce le proprietà degli array e questo si traduce in un pesante calo delle
performance per collezioni persistenti.
Java Persistence API
Con JPA viene utilizzata l’annotazione @ElementCollection in corrispondenza della collezione:
@ElementCollection
@CollectionTable(name="table_name",
joinColumns=@JoinColumn(name="column_a")
)
@Column(name="column_b")
30
L’annotazione @CollectionTable definisce la tabella in cui verrà mappata la collezione con nome
table_name e la colonna (joinColumn) della tabella che rappresenta la collezione con nome
column_a. L’annotazione @Column che segue, definisce il nome della colonna per il mapping dei valori
della collezione.
Esempio : mapping JPA di una proprietà Set
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<String> numTelefono;
...
@ElementCollection
@CollectionTable(name = "num_telefono",
joinColumns = @JoinColumn(name = "username")
)
@Column(name = "numero")
public Set<String> getNumTelefono() {
return (Set<String>) this.numTelefono;
}
...
}
Il mapping di collezioni ordinate con JPA avviene aggiungendo l’annotazione @OrderColumn per definire la
colonna per il numero d’ordine.
2.5.5 Mapping di proprietà istanze di una classe che realizza un tipo UML
Alcune proprietà di una classe Java possono essere classi che realizzano tipi UML. Queste ultime, non
rappresentano entità, cioè non hanno un'identità individuale, ma hanno lo scopo di caratterizzare una
classe entità. Una classe che realizza un tipo UML viene mappata sulla tabella della classe entità a cui è
collegata ed ogni sua istanza è identificata dalla chiave primaria della classe entità.
31
Esempio: proprietà istanza di una classe che realizza un tipo UML
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
private Indirizzo indirizzo;
...
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
public void setIndirizzo(Indirizzo indirizzo) {
this.indirizzo = indirizzo;
}
...
}
File Indirizzo.java
package domain;
public class Indirizzo {
private
private
private
private
String via;
int numeroCivico;
String cap;
String citta;
public Indirizzo() { }
public String getVia() {
return via;
}
32
public void setVia(String via) {
this.via = via;
}
public int getNumeroCivico() {
return numeroCivico;
}
public void setNumeroCivico(int numeroCivico) {
this.numeroCivico = numeroCivico;
}
public String getCap() {
return cap;
}
public void setCap(String cap) {
this.cap = cap;
}
public String getCitta() {
return citta;
}
public void setCitta(String citta) {
this.citta = citta;
}
}
Hibernate
Con Hibernate si utilizza l'elemento XML <component>, specificato nel file di mapping della classe entità
per includere nella tabella le colonne che mapperanno le proprietà della classe che realizza il tipo UML
(classe component).
<component name="propertyName" class="ClassName">
<property name="property1" column="column_1"/>
...
<property name="propertyN" column="column_n"/>
</component>
L'elemento <component> avrà come attributi il nome della proprietà istanza della classe component
all'interno della classe entità (name) e il nome della classe component (class). Gli elementi figli
<property> permettono il mapping delle proprietà della classe component.
33
Esempio: mapping di una classe component
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<component name="indirizzo" class="domain.Indirizzo">
<property name="via" column="via"/>
<property name="numeroCivico" column="numero_civico"/>
<property name="cap" column="cap"/>
<property name="citta" column="citta"/>
</component>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
via character varying(255),
numero_civico integer,
cap character varying(255),
citta character varying(255),
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
Java Persistence API
In applicazioni JPA, una classe che realizza un tipo UML non viene definita con @Entity ma con
l’annotazione @Embeddable. Nella classe entità, in corrispondenza della proprietà istanza della classe
embeddable verrà inserità l’annotazione @Embedded.
Esempio: mapping di una classe embedded
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Indirizzo indirizzo;
34
...
@Embedded
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
...
}
File Indirizzo.java
package domain;
import javax.persistence.*;
@Embeddable
public class Indirizzo {
private String via;
@Column(name = "numero_civico")
private int numeroCivico;
private String cap;
private String citta;
...
}
2.5.6 Mapping di associazioni
Il mapping di associazioni con Hibernate e JPA può essere effettuato secondo due modalità:

Accorpamento della relazione ER

Table join per la relazione ER
Verranno ora presentate in dettaglio le diverse metodologie per il mapping dei più importanti tipi di
associazione.
2.5.6.1 Mapping di associazioni senza attributi a responsabilità singola
con molteplicità 1..1 e 0..1
Le classi di dominio ClassA e ClassB sono legate da un'associazione senza attributi con molteplicità 1..1
sulla quale ClassA ha responsabilità. ClassA ha una proprietà propertyB riferimento ad un'istanza di
ClassB.
35
Esempio: associazione senza attributi a responsabilità singola con molteplicità 1..1
File Acquisto.java
package domain;
public class Acquisto {
private long id;
private Date data;
private Utente effettuatoDa;
...
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
public void setEffettuatoDa(Utente effettuatoDa) {
this.effettuatoDa = effettuatoDa;
}
...
}
Accorpamento della relazione ER
Nella tabella table_A viene inserita una colonna column_B e viene definito un vincolo di foreign key tra
la colonna column_B e la chiave primaria di table_B.
Hibernate
Viene utilizzato l’elemento <many-to-one>.
<many-to-one name=”propertyB”
class=”ClassB”
column=”column_B”
not-null=”true”/>
L'attributo not-null=”true” definisce un vincolo NOT NULL per la colonna column_B, in questo
modo si garantisce che ogni istanza di ClassA sia collegata a un'istanza di ClassB. Se questo attributo
non viene definito, si avrà un'associazione con molteplicità 0..1, perchè sarà possibile introdurre istanze di
ClassA con valore null alla colonna column_B, quindi non collegate a nessuna istanza di ClassB.
36
Questo approccio non è consigliato perchè introdurrebbe valori nulli che degraderebbero la qualità dei dati
persistenti.
Esempio: mapping associazione senza attributi a responsabilità singola con molteplicità 1..1
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
<many-to-one name="effettuatoDa"
class="domain.Utente"
column="username"
not-null="true"/>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE acquisto
(
id bigint NOT NULL,
data date,
username character varying(255) NOT NULL,
CONSTRAINT acquisto_pkey PRIMARY KEY (id ),
CONSTRAINT fk98d0bfebf2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
In caso di associazione one-to-one, la strategia è identica al caso di mapping di associazione senza attributi
con molteplicità 1..1 con l'unica differenza dell'attributo unique=”true” per definire un vincolo di
chiave sulla colonna column_B.
<many-to-one name=”propertyB”
class=”ClassB”
column=”column_B”
not-null=”true”
unique=”true”/>
37
Esempio: Mapping di associazione senza attributi a responsabilità singola tipo one-to-one
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
private Indirizzo indirizzo;
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
public void setIndirizzo(Indirizzo indirizzo) {
this.indirizzo = indirizzo;
}
}
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente">
...
<many-to-one name="indirizzo"
class="domain.Indirizzo"
column="id_indirizzo"
unique="true"
not-null="true"/>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
38
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
id_indirizzo bigint NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username ),
CONSTRAINT fkce393bf9c9c5d612 FOREIGN KEY (id_indirizzo)
REFERENCES indirizzo (id_indirizzo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT utente_indirizzo_key UNIQUE (id_indirizzo )
)
CREATE TABLE indirizzo
(
id_indirizzo bigint NOT NULL,
via character varying(255),
numero_civico integer,
cap character varying(255),
citta character varying(255),
CONSTRAINT indirizzo_pkey PRIMARY KEY (id_indirizzo )
)
Java Persistence API
Sviluppando con JPA, si potranno avere i medesimi risultati avuti con Hibernate utilizzando l’annotazione
@ManyToOne con @JoinColumn per specificare la colonna column_B per l’accorpamento.
@ManyToOne
@JoinColumn(name="column_B", nullable=false)
La proprietà nullable=false definisce un vincolo NOT NULL per la colonna propB per garantire che
l’associazione sia di molteplicità 1..1.
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 1..1
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
private Utente effettuatoDa;
...
@ManyToOne
@JoinColumn(name = "username", nullable = false)
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
...
39
}
Per quanto riguarda le associazioni one-to-one, viene utilizzato il metodo spiegato per le associazioni
generiche di tipo 1..1 con l’aggiunta della proprietà unique=true in modo analogo ad Hibernate.
@ManyToOne
@JoinColumn(name="column_B", unique=true, nullable=false)
Esempio: Mapping di associazione senza attributi a responsabilità singola tipo one-to-one
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<String> numTelefono;
private Indirizzo indirizzo;
...
@ManyToOne
@JoinColumn(name = "id_indirizzo", unique = true, nullable = false)
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
...
}
File Indirizzo.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "indirizzo")
public class Indirizzo {
private
private
private
private
private
long id;
String via;
int numeroCivico;
String cap;
String citta;
public Indirizzo() { }
40
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "id_indirizzo")
public long getId() {
return this.id;
}
...
}
Table join per la relazione ER
Viene creata una nuova tabella table_AB per rappresentare la relazione ER. Questa tabella avrà due
colonne, column_A e column_B, ognuna con un vincolo di foreign key verso la chiave primaria di una
tabella dell'associazione.
Hibernate
Viene utilizzato l’elemento <join> con l’elemento <many-to-one>.
<join table=”table_AB”>
<key column=”column_A”/>
<many-to-one name=”propertyB”
class=”ClassB”
column=”column_B”
not-null=”true”/>
</join>
Come nel caso di accorpamento della relazione ER, viene definito l'attributo not-null=”true” per
definire un vincolo NOT NULL sulla colonna column_B di table_AB.
Questa modalità di mapping è spesso utilizzata per le associazioni con molteplicità 0..1 a responsabilità
singola.
Quando un'istanza di ClassA viene resa persistente, viene inserita una tupla in table_A e una tupla in
table_AB. Nel rendere persistente un'istanza di ClassA non associata a nessuna istanza di ClassB
avremo la tupla da inserire in table_AB con il campo di column_B nullo che violerà il vincolo NOT
NULL generando un errore. Per effettuare il mapping di un'associazione 0..1 a responsabilità singola sarà
quindi necessario definire l'attributo optional con valore true per l'elemento <join> che non inserirà
tuple contenenti valori nulli in table_AB.
Per l'elemento <join> è anche possibile definite l'attributo optional con valore true che non
permette l'inserimento di valori nulli in table_AB.
41
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 1..1
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
<join table="acquisto_utente">
<key column="id_acquisto"/>
<many-to-one name="effettuatoDa"
class="domain.Utente"
column="username"
not-null="true"/>
</join>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto )
)
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
CREATE TABLE acquisto_utente
(
id_acquisto bigint NOT NULL,
username character varying(255) NOT NULL,
CONSTRAINT acquisto_utente_pkey PRIMARY KEY (id_acquisto ),
CONSTRAINT fka907cacde7ffd084 FOREIGN KEY (id_acquisto)
REFERENCES acquisto (id_acquisto) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fka907cacdf2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
42
Java Persistence API
La crazione di table join per la relazione ER con JPA avviene utilizzando l’annotazione @ManyToOne con
@JoinTable.
@ManyToOne
@JoinTable(name="table_AB",
joinColumns = @JoinColumn(name="column_A"),
inverseJoinColumns = @JoinColumn(name="column_B", nullable=false)
)
La proprietà inversejoinColumns definisce la colonna con vincolo di foreign key verso la tabella
dell’entità associata.
Questa modalità di mapping genererà lo stesso output SQL di Hibernate, però saranno presenti alcune
importanti differenze riscontrabili solo lavorando con gli oggetti. Il mapping JPA con join table mappa
un’associazione 0..1 e non 1..1. Questa differenza si ha perché lavorando con Hibernate, quando viene resa
persistente un’istanza di ClassA, questa viene mappata su una tupla per la tabella table_A e su una
tupla per la join table table_AB mentre con JPA l’istanza di ClassA non verrà mappata su table_AB e
quindi il vincolo NOT NULL sulla colonna column_B non verrà violato in caso di mancata associazione.
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 0..1
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
private Utente effettuatoDa;
...
@ManyToOne
@JoinTable(name = "acquisto_utente",
joinColumns = @JoinColumn(name="id_acquisto"),
inverseJoinColumns = @JoinColumn(name="username", nullable=false)
)
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
...
}
Una soluzione per il mapping di associazioni con molteplicità 1..1 è di aggiungere un ulteriore vincolo
utilizzando Hibernate Validator, implementazione di JSR 303 – Bean Validation, che definisce un ulteriore
modello di metadati e API per convalidare le classi Java.
43
Basterà inserire l’annotazione @NotNull sul campo che definisce l’associazione per avere un vincolo NOT
NULL che controllerà che il campo dell’istanza della classe Java non sia nullo quando si tenterà di renderla
persistente.
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 1..1
File Acquisto.java
package domain;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
@NotNull
private Utente effettuatoDa;
...
@ManyToOne
@JoinTable(name = "acquisto_utente",
joinColumns = @JoinColumn(name = "id_acquisto"),
inverseJoinColumns = @JoinColumn(name = "username")
)
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
...
}
2.5.6.2 Mapping di associazioni senza attributi a responsabilità singola
con molteplicità 1..* e 0..*
Le classi di dominio ClassA e ClassB sono legate da un'associazione senza attributi con molteplicità 1..*
sulla quale ClassA ha responsabilità.
ClassA ha una proprietà propertyB insieme di riferimenti ad istanze di ClassB.
Esempio: associazione senza attributi a responsabilità singola con molteplicità 1..*
44
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
private Set<Acquisto> effettua;
...
public Set<Acquisto> getEffettua() {
return (Set<Acquisto>) this.effettua;
}
private void setEffettua(Set<Acquisto> effettua) {
this.effettua = effettua;
}
...
}
Accorpamento della relazione ER
Nella tabella table_B viene inserita una colonna column_A e viene definito un vincolo di foreign key tra
column_A e la chiave primaria di table_B.
Hibernate
Viene utilizzato l’elemento <set> con l’elemento <one-to-many>.
<set name=”propertyB”>
<key column=”column_A”/>
<one-to-many class=”ClassB”/>
</set>
Il mapping di un'associazione 1..* a responsabilità singola con l'elemento <one-to-many> è inusuale e
non raccomandata.
Esempio: mapping di associazione senza attributi a responsabilità singola con molteplicità 1..*
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<set name="effettua">
<key column="username" not-null="true"/>
<one-to-many class="domain.Acquisto"/>
</set>
...
</class>
45
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
CREATE TABLE acquisto
(
id bigint NOT NULL,
data date,
username character varying(255) NOT NULL,
CONSTRAINT acquisto_pkey PRIMARY KEY (id ),
CONSTRAINT fk98d0bfebf2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
Questo mapping utilizza l’annotazione @OneToMany con @JoinColumn.
@OneToMany
@JoinColumn(name = "colonnaA", nullable = false)
Come per Hibernate, il mapping di un'associazione 1..* a responsabilità singola con accorpamento è
inusuale e non raccomandato.
Esempio: mapping di associazione senza attributi a responsabilità singola con molteplicità 1..*
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<Acquisto> effettua;
...
@OneToMany
@JoinColumn(name = "username", nullable = false)
public Set<Acquisto> getEffettua() {
return this.effettua;
46
}
...
}
Table join per la relazione ER
Viene creata una nuova tabella per rappresentare la relazione ER. Questa tabella avrà due colonne, ognuna
con un vincolo di foreign key verso la chiave primaria della tabella dell'associazione corrispondente.
Questa modalità di mapping è utilizzata per associazioni con molteplicità 0..* a responsabilità singola.
Hibernate
Viene utilizzato l’elemento <set> insieme all’elemento <many-to-many>.
<set name=”propertyB” table=”table_AB”>
<key column=”column_A”/>
<many-to-many class=”ClassB”
column=”column_B”
unique=”true”/>
</set>
L'utilizzo dell'attributo unique con valore true per l'elemento <many-to-many> definisce un vincolo
di chiave per column_B, cioè dichiara che l'associazione è di tipo many-to-one.
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 1..*
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<set name="effettua" table="utente_acquisto">
<key column="username"/>
<many-to-many class="domain.Acquisto"
column="id_acquisto"
unique="true"/>
</set>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
47
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto )
)
CREATE TABLE utente_acquisto
(
username character varying(255) NOT NULL,
id_acquisto bigint NOT NULL,
CONSTRAINT utente_acquisto_pkey PRIMARY KEY (username , id_acquisto ),
CONSTRAINT fk489e1d71e7ffd084 FOREIGN KEY (id_acquisto)
REFERENCES acquisto (id_acquisto) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk489e1d71f2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT utente_acquisto_id_acquisto_key UNIQUE (id_acquisto )
)
Il mapping di associazioni many-to-many a responsabilità singola viene effettuato in modo analogo al caso
precedente ma eliminando la definizione dell'attributo unique per l'elemento <many-to-many>.
<set name=”propB” table=”tabellaAB”>
<key column=”colonnaA”/>
<many-to-many class=”ClasseB”
column=”colonnaB”/>
</set>
Java Persistence API
Per mappare associazioni 1..* e 0..* con join table si utilizza l’annotazione @OneToMany con
@JoinTable.
@OneToMany
@JoinTable(name = "tabellaAB",
joinColumns = @JoinColumn(name="colonnaA"),
inverseJoinColumns = @JoinColumn(name="colonnaB", nullable=false)
)
Come per il caso di associazioni con molteplicità 1..1 con join table, per le associazioni 1..* è necessario
aggiungere un vincolo @NotNull di Hibernate Validator.
48
Esempio: Mapping di associazione senza attributi a responsabilità singola con molteplicità 0..*
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<Acquisto> effettua;
...
@OneToMany
@JoinTable(name = "utente_acquisto",
joinColumns = @JoinColumn(name = "username"),
inverseJoinColumns = @JoinColumn(name = "id_acquisto", nullable = false)
)
public Set<Acquisto> getEffettua() {
return this.effettua;
}
...
}
2.5.6.3 Mapping di associazione senza attributi a responsabilità doppia tipo one-to-many
/ many-to-one
Il mapping di associazioni a responsabilità doppia viene effettuato utilizzando i costrutti analizzati per il
mapping di associazioni a responsabilità singola per entrambe le classi che partecipano.
L’esempio mostra anche la struttura della classe manager.
Esempio: Classi java per il mapping
File Utente.java
package domain;
public class Utente {
49
private String username;
private String password;
private Set<Acquisto> effettua;
...
public Set<Acquisto> getEffettua() {
return (Set<Acquisto>) this.effettua;
}
private void setEffettua(Set<Acquisto> effettua) {
this.effettua = effettua;
}
public void inserisciAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerEffettua.inserisci(this, acquisto);
}
public void rimuoviAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerEffettua.elimina(this, acquisto);
}
public void inserisciPerManagerEffettua(ManagerEffettua manager) {
if(manager != null) this.effettua.add(manager.getAcquisto());
}
public void eliminaPerManagerEffettua(ManagerEffettua manager) {
if(manager != null) this.effettua.remove(manager.getAcquisto());
}
...
}
File Acquisto.java
package domain;
public class Acquisto {
private long id;
private Date data;
private Utente effettuatoDa;
...
50
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
private void setEffettuatoDa(Utente effettuatoDa) {
this.effettuatoDa = effettuatoDa;
}
public void inserisciUtente(Utente utente)
throws EccezionePrecondizioni {
if(utente == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerEffettua.inserisci(utente, this);
}
public void rimuoviUtente(Utente utente)
throws EccezionePrecondizioni {
if(utente == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerEffettua.elimina(utente, this);
}
public void inserisciPerManagerEffettua(ManagerEffettua manager) {
if(manager != null) this.effettuatoDa = manager.getUtente();
}
public void eliminaPerManagerEffettua(ManagerEffettua manager) {
if(manager != null) this.effettuatoDa = null;
}
public void inserisciArticolo(Articolo articolo)
throws EccezionePrecondizioni {
if(articolo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
comprende.add(articolo);
}
public void rimuoviArticolo(Articolo articolo)
throws EccezionePrecondizioni {
if(articolo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
comprende.remove(articolo);
}
...
51
}
File ManagerEffettua.java
package domain;
public final class ManagerEffettua {
private Utente utente;
private Acquisto acquisto;
private ManagerEffettua(Utente utente, Acquisto acquisto) {
this.utente = utente;
this.acquisto = acquisto;
}
public Utente getUtente() {
return this.utente;
}
public Acquisto getAcquisto() {
return this.acquisto;
}
public static void inserisci(Utente utente, Acquisto acquisto) {
if(utente != null && acquisto != null &&
acquisto.getEffettuatoDa() == null) {
ManagerEffettua manager = new ManagerEffettua(utente, acquisto);
utente.inserisciPerManagerEffettua(manager);
acquisto.inserisciPerManagerEffettua(manager);
}
}
public static void elimina(Utente utente, Acquisto acquisto) {
if(utente != null && acquisto == null &&
acquisto.getEffettuatoDa() != null) {
ManagerEffettua manager = new ManagerEffettua(utente, acquisto);
utente.eliminaPerManagerEffettua(manager);
acquisto.eliminaPerManagerEffettua(manager);
}
}
}
Accorpamento della relazione ER
L'accorpamento della relazione ER prevede l'aggiunta di una colonna username alla tabella acquisto per
associare ad ogni tupla di acquisto una tupla di utente.
52
Hibernate
L'utilizzo dell'attributo not-null=”true” nell'elemento <many-to-one> garantisce che per ogni
tupla di acquisto venga specificato un utente, quindi che l'associazione sia di tipo 1..*.
Inoltre viene utilizzato l’attributo inverse con valore true che viene definito solo per una delle due
classi che partecipano all’associazione. Questo attributo viene utilizzato perché alla modifica
dell’associazione vengono modificate entrambe le classi coinvolte che corrispondono ad un unico valore
nella base di dati. Il mapping con inverse non genererà alcuno statement SQL, in questo modo si evita
che entrambe le classi modifichino i valori della base di dati.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-many/many-to-one
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<set name="effettua" inverse="true">
<key column="username" />
<one-to-many class="domain.Acquisto"/>
</set>
...
</class>
</hibernate-mapping>
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
<many-to-one name="effettuatoDa"
class="domain.Utente"
column="username"
not-null="true"/>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
53
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
username character varying(255) NOT NULL,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto ),
CONSTRAINT fk98d0bfebf2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
Per questo mapping vengono utilizzate le annotazioni @OneToMany e @ManyToOne.
La proprietà mappedBy è equivalente all’attributo inverse=true utilizzato nel mapping con Hibernate.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-many/many-to-one
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<Acquisto> effettua;
...
@OneToMany(mappedBy = "effettuatoDa")
public Set<Acquisto> getEffettua() {
return (Set<Acquisto>) this.effettua;
}
...
}
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
54
private Date data;
private Utente effettuatoDa;
...
@ManyToOne
@JoinColumn(name = "username", nullable = false)
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
...
}
Table join per la relazione ER
In questo caso una tabella utente_acquisto associa tuple di acquisto a tuple di utente.
Hibernate
Viene utilizzato l’elemento <set> con l’elemento <many-to-many>.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-many/many-to-one
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<set name="effettua" table="utente_acquisto" inverse="true">
<key column="username"/>
<many-to-many class="domain.Acquisto"
column="id_acquisto"
unique="true"/>
</set>
...
</class>
</hibernate-mapping>
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
<join table="utente_acquisto">
<key column="id_acquisto"/>
<many-to-one name="effettuatoDa"
class="domain.Utente"
column="username"
not-null="true"/>
</join>
55
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username )
)
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto )
)
CREATE TABLE utente_acquisto
(
id_acquisto bigint NOT NULL,
username character varying(255) NOT NULL,
CONSTRAINT utente_acquisto_pkey PRIMARY KEY (username , id_acquisto ),
CONSTRAINT fk489e1d71e7ffd084 FOREIGN KEY (id_acquisto)
REFERENCES acquisto (id_acquisto) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk489e1d71f2625d59 FOREIGN KEY (username)
REFERENCES utente (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
Anche per questo mapping vengono utilizzate le annotazioni @OneToMany e @ManyToOne e la proprietà
mappedBy su un lato dell’associazione.
L’output SQL generato sarà leggermente diverso da quello dell’esempio Hibernate perché la tabella
utente_acquisto avrà la chiave primaria composta solo dalla colonna id_acquisto.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-many/many-to-one
File Utente.java
package domain;
import javax.persistence.*;
56
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Set<String> numTelefono;
private Indirizzo indirizzo;
private Set<Acquisto> effettua;
...
@OneToMany(mappedBy = "effettuatoDa")
public Set<Acquisto> getEffettua() {
return (Set<Acquisto>) this.effettua;
}
...
}
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
private Utente effettuatoDa;
...
@ManyToOne
@JoinTable(name = "utente_acquisto",
joinColumns = @JoinColumn(name="id_acquisto"),
inverseJoinColumns = @JoinColumn(name="username", nullable=false)
)
public Utente getEffettuatoDa() {
return this.effettuatoDa;
}
...
}
2.5.6.4 Mapping di associazione senza attributi a responsabilità doppia tipo one-to-one
Questo tipo di associazione viene spesso mappato con l’utilizzo dell’elemento <component>, però nel
caso in cui si ha interesse a mantenere una tabella per ogni classe associata, viene utilizzato questo tipo di
mapping. In questo caso è possibile solo il mapping con accorpamento della relazione ER.
57
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-one
File Utente.java
package domain;
public class Utente {
private String username;
private String password;
private Indirizzo indirizzo;
...
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
private void setIndirizzo(Indirizzo indirizzo) {
this.indirizzo = indirizzo;
}
public void inserisciIndirizzo(Indirizzo indirizzo)
throws EccezionePrecondizioni {
if(indirizzo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerResidenza.inserisci(this, indirizzo);
}
public void rimuoviIndirizzo(Indirizzo indirizzo)
throws EccezionePrecondizioni {
if(indirizzo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerResidenza.elimina(this, indirizzo);
}
public void inserisciPerManagerResidenza(ManagerResidenza manager) {
if(manager != null) this.indirizzo = manager.getIndirizzo();
}
58
public void eliminaPerManagerResidenza(ManagerResidenza manager) {
if(manager != null) this.indirizzo = null;
}
...
}
File Indirizzo.java
package domain;
public class Indirizzo {
private long id;
private String via;
private int numeroCivico;
private String cap;
private String citta;
private Utente utente;
...
public Utente getUtente() {
return this.utente;
}
private void setUtente(Utente utente) {
this.utente=utente;
}
public void inserisciUtente(Utente utente)
throws EccezionePrecondizioni {
if(utente == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerResidenza.inserisci(utente, this);
}
public void rimuoviUtente(Utente utente)
throws EccezionePrecondizioni {
if(utente == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerResidenza.elimina(utente, this);
}
public void inserisciPerManagerResidenza(ManagerResidenza manager) {
if(manager != null) this.utente = manager.getUtente();
}
59
public void eliminaPerManagerResidenza(ManagerResidenza manager) {
if(manager != null) this.utente = null;
}
...
}
File ManagerResidenza.java
package domain;
public class ManagerResidenza {
private Utente utente;
private Indirizzo indirizzo;
private ManagerResidenza(Utente utente, Indirizzo indirizzo) {
this.utente = utente;
this.indirizzo = indirizzo;
}
public Utente getUtente() {
return this.utente;
}
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
public static void inserisci(Utente utente, Indirizzo indirizzo) {
if(utente != null && indirizzo != null &&
utente.getIndirizzo() == null && indirizzo.getUtente() == null) {
ManagerResidenza manager = new ManagerResidenza(utente, indirizzo);
utente.inserisciPerManagerResidenza(manager);
indirizzo.inserisciPerManagerResidenza(manager);
}
}
public static void elimina(Utente utente, Indirizzo indirizzo) {
if(utente != null && indirizzo != null &&
utente.getIndirizzo() != null && indirizzo.getUtente() != null) {
ManagerResidenza manager = new ManagerResidenza(utente, indirizzo);
utente.eliminaPerManagerResidenza(manager);
indirizzo.eliminaPerManagerResidenza(manager);
}
}
}
60
Hibernate
Il mapping per la classe Utente.java è identico a quello presentato per associazioni a responsabilità
singola, mentre per il mapping della classe Indirizzo.java è stato utilizzato l’elemento <one-toone>, presente solo in questi casi, per cui è specificato l’attributo property-ref per identificare la
proprietà della classe Utente.java che rappresenta l’associazione.
File Utente.hbm.xml
<hibernate-mapping>
<class name="domain.Utente" table="utente">
...
<many-to-one name="indirizzo"
class="domain.Indirizzo"
column="indirizzo"
unique="true"
not-null="true"/>
...
</class>
</hibernate-mapping>
File Indirizzo.hbm.xml
<hibernate-mapping>
<class name="domain.Indirizzo" table="indirizzo">
...
<one-to-one name="utente"
class="domain.Utente"
property-ref="indirizzo"/>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE utente
(
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
id_indirizzo bigint NOT NULL,
CONSTRAINT utente_pkey PRIMARY KEY (username ),
CONSTRAINT fkce393bf9c9c5d612 FOREIGN KEY (id_indirizzo)
REFERENCES indirizzo (id_indirizzo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT utente_indirizzo_key UNIQUE (id_indirizzo )
)
CREATE TABLE indirizzo
(
61
id_indirizzo bigint NOT NULL,
via character varying(255),
numero_civico integer,
cap character varying(255),
citta character varying(255),
CONSTRAINT indirizzo_pkey PRIMARY KEY (id_indirizzo )
)
Java Persistence API
Per questo mapping viene inserita in entrambe le classi l’annotazione @OneToOne, utilizzata solo in questi
casi particolari.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo one-to-one
File Utente.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "utente")
public class Utente {
private String username;
private String password;
private Indirizzo indirizzo;
...
@OneToOne
@JoinColumn(name = "id_indirizzo", unique = true, nullable = false)
public Indirizzo getIndirizzo() {
return this.indirizzo;
}
...
}
File Indirizzo.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "indirizzo")
public class Indirizzo {
private long id;
private String via;
private int numeroCivico;
62
private String cap;
private String citta;
private Utente utente;
...
@OneToOne(mappedBy = "indirizzo")
public Utente getUtente() {
return this.utente;
}
...
}
2.5.6.5 Mapping di associazione senza attributi a responsabilità doppia tipo many-tomany
Il Mapping di associazioni di questo tipo può essere effettuato solo con table join.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo many-to-many
File Acquisto.java
package domain;
public class Acquisto {
private long id;
private Date data;
private Set<Articolo> comprende;
...
public Set<Articolo> getComprende() {
return (Set<Articolo>)this.comprende;
}
private void setComprende(Set<Articolo> comprende) {
this.comprende = comprende;
}
public void inserisciArticolo(Articolo articolo)
throws EccezionePrecondizioni {
if(articolo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
63
else
ManagerComprende.inserisci(this, articolo);
}
public void rimuoviArticolo(Articolo articolo)
throws EccezionePrecondizioni {
if(articolo == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerComprende.elimina(this, articolo);
}
public void inserisciPerManagerComprende(ManagerComprende manager) {
if(manager != null) this.comprende.add(manager.getArticolo());
}
public void eliminaPerManagerComprende(ManagerComprende manager) {
if(manager != null) this.comprende.remove(manager.getArticolo());
}
...
}
File Articolo.java
package domain;
public class Articolo {
private String codice;
private String descrizione;
private double prezzo;
private Set<Acquisto> compreso;
...
public Set<Acquisto> getCompreso() {
return (Set<Acquisto>) this.compreso;
}
private void setCompreso(Set<Acquisto> compreso) {
this.compreso = compreso;
}
public void inserisciAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerComprende.inserisci(acquisto, this);
}
64
public void rimuoviAcquisto(Acquisto acquisto)
throws EccezionePrecondizioni {
if(acquisto == null)
throw new EccezionePrecondizioni(
"L'oggetto deve essere inizializzato");
else
ManagerComprende.elimina(acquisto, this);
}
public void inserisciPerManagerComprende(ManagerComprende manager) {
if(manager != null) this.compreso.add(manager.getAcquisto());
}
public void eliminaPerManagerComprende(ManagerComprende manager) {
if(manager != null) this.compreso.remove(manager.getAcquisto());
}
...
}
File ManagerComprende.java
package domain;
public final class ManagerComprende {
private Acquisto acquisto;
private Articolo articolo;
private ManagerComprende(Acquisto acquisto, Articolo articolo) {
this.acquisto = acquisto;
this.articolo = articolo;
}
public Acquisto getAcquisto() {
return this.acquisto;
}
public Articolo getArticolo() {
return this.articolo;
}
public static void inserisci(Acquisto acquisto, Articolo articolo) {
if(acquisto != null && articolo != null) {
ManagerComprende manager = new ManagerComprende(acquisto, articolo);
acquisto.inserisciPerManagerComprende(manager);
articolo.inserisciPerManagerComprende(manager);
}
}
public static void elimina(Acquisto acquisto, Articolo articolo) {
if(acquisto != null && articolo != null) {
65
ManagerComprende manager = new ManagerComprende(acquisto, articolo);
acquisto.eliminaPerManagerComprende(manager);
articolo.eliminaPerManagerComprende(manager);
}
}
}
Hibernate
In entrambi i file di mapping vengono utilizzato l’elemento <set> con <many-to-many>.
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
<set name="comprende" table="acquisto_articolo">
<key column="id_acquisto"/>
<many-to-many class="domain.Articolo" column="cod_articolo"/>
</set>
...
</class>
</hibernate-mapping>
File Articolo.hbm.xml
<hibernate-mapping>
<class name="domain.Articolo" table="articolo">
...
<set name="compreso" table="acquisto_articolo" inverse="true">
<key column="cod_articolo"/>
<many-to-many class="domain.Acquisto" column="id_acquisto"/>
</set>
...
</class>
</hibernate-mapping>
SQL output
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto )
)
CREATE TABLE articolo
(
66
cod_articolo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
CONSTRAINT articolo_pkey PRIMARY KEY (cod_articolo )
)
CREATE TABLE acquisto_articolo
(
cod_articolo character varying(255) NOT NULL,
id_acquisto bigint NOT NULL,
CONSTRAINT acquisto_articolo_pkey PRIMARY KEY (id_acquisto , cod_articolo ),
CONSTRAINT fk182708299cdd2f60 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk18270829e7ffd084 FOREIGN KEY (id_acquisto)
REFERENCES acquisto (id_acquisto) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
Viene utilizzata l’annotazione @ManyToMany per entrambe le classi.
Esempio: Mapping di associazione senza attributi a responsabilità doppia tipo many-to-many
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private long id;
private Date data;
private Set<Articolo> comprende;
...
@ManyToMany
@JoinTable(name = "acquisto_articolo",
joinColumns = @JoinColumn(name = "id_acquisto"),
inverseJoinColumns = @JoinColumn(name = "cod_articolo")
)
public Set<Articolo> getComprende() {
return (Set<Articolo>)this.comprende;
}
...
}
67
File Articolo.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "articolo")
public class Articolo {
private String codice;
private String descrizione;
private double prezzo;
private Set<Acquisto> compreso;
...
@ManyToMany(mappedBy = "comprende")
public Set<Acquisto> getCompreso() {
return (Set<Acquisto>) this.compreso;
}
...
}
2.5.6.6 Mapping di associazioni con attributi
Come è stato scritto nel paragrafo sulla realizzazione delle classi di dominio, Hibernate e JPA non forniscono
costrutti specifici per il mapping di associazioni con attributi.
In caso di associzioni con molteplicità massima pari a 1, trasformando gli attributi dell'associazione in nuove
proprietà della classe che ha responsabilità, queste verranno mappate come normali proprietà.
Viene ora presentata una possibile strategia per la costruzione di una classe che rappresenti l’associazione
in caso di associazioni con molteplicità massima maggiore di 1. L’identificatore della classe è composto dagli
identificatori delle classi che partecipano all’associazione ed è incapsulato in una classe statica annidata. Le
altre proprietà della classe sono i riferimenti alle classi associate e gli attributi dell’associazione stessa.
Particolarmente importante è il costruttore della classe che oltre ad impostare i valori delle proprietà e i
valori della classe identificatore annidata, gestisce anche l’inserimento dei valori nei campi della classe o
delle classi che hanno responsabilità sull’associazione, garantendo l’integrità referenziale.
I riferimenti alle classi che partecipano all’associazione sono mappati su colonne in cui non potranno essere
effettuate operazioni di inserimento o aggiornamento, in questo modo le istanze della classe che
rappresenta l’associazione non saranno modificabili e verranno gestire unicamente dal costruttore.
68
Esempio: Mapping di associazione con attributi
File Acquisto.java
package domain;
import javax.persistence.*;
public class Acquisto {
private Long id;
private Date data;
private Set<ArticoloAcquistato> comprende;
public Acquisto() {
this.data = new Date();
this.comprende = new HashSet();
}
...
public void inserisciArticolo(ArticoloAcquistato articoloAcquistato) {
comprende.add(articoloAcquistato);
}
...
}
File Articolo.java
package domain;
public abstract class Articolo {
protected String codice;
protected String descrizione;
protected double prezzo;
protected Articolo() { }
...
}
69
File ArticoloAcquistato.java
package domain;
public class ArticoloAcquistato {
public static class Id implements java.io.Serializable {
private Long idAcquisto;
private String codiceArticolo;
protected Id() { }
protected Id(Long idAcquisto, String codiceArticolo) {
this.idAcquisto = idAcquisto;
this.codiceArticolo = codiceArticolo;
}
public boolean equals(Object o) {
if (o != null && getClass().equals(this.getClass())) {
Id id = (Id) o;
return id.idAcquisto == idAcquisto
&& id.codiceArticolo == codiceArticolo;
} else {
return false;
}
}
public int hashCode() {
return idAcquisto.hashCode() + codiceArticolo.hashCode();
}
}
private
private
private
private
Id id = new Id();
Acquisto acquisto;
Articolo articolo;
int quantita;
protected ArticoloAcquistato() { }
public ArticoloAcquistato(Acquisto acquisto,
Articolo articolo, int quantita)
throws EccezionePrecondizioni {
if(acquisto == null || articolo == null)
throw new EccezionePrecondizioni
("L'oggetto deve essere inizializzato");
if(quantita <= 0)
throw new EccezionePrecondizioni
("La quantità deve essere positiva");
// Set fields
this.acquisto = acquisto;
this.articolo = articolo;
this.quantita = quantita;
70
// Set identifier values
this.id.idAcquisto = acquisto.getId();
this.id.codiceArticolo = articolo.getCodice();
// Guarantee referential integrity
acquisto.inserisciArticolo(this);
}
...
}
Hibernate
Nel file di mapping ArticoloAcquistato.hbm.xml viene utilizzato l’elemento <composite-id>
per mappare la classe annidata come chiave primaria e gli elementi <many-to-one> specificano gli
attributi insert=false e update=false che rendono i valori delle colonne non modificabili.
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
<id name="id" column="id_acquisto">
<generator class=""/>
</id>
<property name="data" column="data" type="date"/>
<set name="comprende" inverse="true">
<key column="id_acquisto"/>
<one-to-many class="domain.ArticoloAcquistato"/>
</set>
</class>
</hibernate-mapping>
File Articolo.hbm.xml
<hibernate-mapping>
<class name="domain.Articolo" table="articolo">
<id name="codice" column="cod_articolo" type="string"/>
<property name="descrizione" column="descrizione" type="string"/>
<property name="prezzo" column="prezzo" type="double"/>
</class>
</hibernate-mapping>
File ArticoloAcquistato.hbm.xml
<hibernate-mapping>
<class name="domain.ArticoloAcquistato"
71
table="articolo_acquistato" mutable="false">
<composite-id name="id"
class="domain.ArticoloAcquistato$Id">
<key-property name="idAcquisto"
access="field"
column="id_acquisto"/>
<key-property name="codiceArticolo"
access="field"
column="cod_articolo"/>
</composite-id>
<property name="quantita" column="quantita" type="int" not-null="true"/>
<many-to-one name="acquisto"
column="id_acquisto"
not-null="true"
insert="false"
update="false"/>
<many-to-one name="articolo"
column="cod_articolo"
not-null="true"
insert="false"
update="false"/>
</class>
</hibernate-mapping>
Java Persistence API
Nella classe che rappresenta l’associazione viene utilizzata l’annotazione @EmbeddedId per mappare
l’identificatore composto e due annotazioni @ManyToOne con proprietà insertable=false e
updatable=false in modo analogo al mapping Hibernate.
File Acquisto.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private Long id;
private Date data;
private Set<ArticoloAcquistato> comprende;
public Acquisto() {
this.data = new Date();
this.comprende = new HashSet();
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
72
@Column(name = "id_acquisto")
public Long getId() {
return this.id;
}
@Temporal(TemporalType.DATE)
@Column(name = "data")
public Date getData() {
return this.data;
}
@OneToMany(mappedBy = "acquisto")
public Set<ArticoloAcquistato> getComprende() throws EccezioneCardMin {
return comprende;
}
...
}
File Articolo.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "articolo")
public abstract class Articolo {
protected
protected
protected
protected
String codice;
String descrizione;
double prezzo;
Articolo() { }
public Articolo(String codice, String descrizione, double prezzo) {
this.codice = codice;
this.descrizione = descrizione;
this.prezzo = prezzo;
}
@Id
@Column(name = "cod_articolo")
public String getCodice() {
return codice;
}
@Column(name = "descrizione")
public String getDescrizione() {
return descrizione;
}
@Column(name = "prezzo")
public double getPrezzo() {
return prezzo;
}
...
73
}
File ArticoloAcquistato.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "articolo_acquistato")
public class ArticoloAcquistato {
@Embeddable
public static class Id implements java.io.Serializable {
@Column(name = "id_acquisto")
private Long idAcquisto;
@Column(name = "cod_articolo")
private String codiceArticolo;
protected Id() { }
protected Id(Long idAcquisto, String codiceArticolo) {
this.idAcquisto = idAcquisto;
this.codiceArticolo = codiceArticolo;
}
public boolean equals(Object o) {
if (o != null && getClass().equals(this.getClass())) {
Id id = (Id) o;
return id.idAcquisto == idAcquisto
&& id.codiceArticolo == codiceArticolo;
} else {
return false;
}
}
public int hashCode() {
return idAcquisto.hashCode() + codiceArticolo.hashCode();
}
}
@EmbeddedId
private Id id = new Id();
@ManyToOne
@JoinColumn(name = "id_acquisto",
insertable = false,
updatable = false)
private Acquisto acquisto;
@ManyToOne
@JoinColumn(name = "cod_articolo",
insertable = false,
updatable = false)
private Articolo articolo;
74
@Column(name = "quantita")
private int quantita;
protected ArticoloAcquistato() { }
public ArticoloAcquistato(Acquisto acquisto,
Articolo articolo, int quantita)
throws EccezionePrecondizioni {
if(acquisto == null || articolo == null)
throw new EccezionePrecondizioni
("L'oggetto deve essere inizializzato");
if(quantita <= 0)
throw new EccezionePrecondizioni
("La quantità deve essere positiva");
this.acquisto = acquisto;
this.articolo = articolo;
this.quantita = quantita;
this.id.idAcquisto = acquisto.getId();
this.id.codiceArticolo = articolo.getCodice();
acquisto.inserisciArticolo(this);
}
...
}
2.5.6.7 Cascading
L'opzione cascade permette di decidere se operazioni effettuate su istanze una classe persitente che ha
responsabilità su un'associazione avranno effetto anche sulle istanze di classi persistenti ad essa associate.
Hibernate
Il cascading viene attivato con l'attributo cascade per gli elementi XML che definiscono associazioni.
Valori più importanti per l’attributo cascade:
cascade=”none”
Comportamento di default.
Nessuna operazione ha effetto sulle istanze di classi associate.
cascade=”save-update”
Le operazioni di salvataggio o aggiornamento hanno effetto sulle
istanze di classi associate
cascade=”delete”
Le operazioni di eliminazione hanno effetto sulle istanze di classi
associate
cascade=”all”
Tutte le operazioni hanno effetto sulle istanze di classi associate
75
Java Persistence API
Le strategie di cascading utilizzabili con JPA sono simili a quelle utilizzabili con Hibernate ma con sintassi
leggermente differente.
La modalità di cascading viene impostata con la proprietà cascade.
Valori più importanti per la proprietà cascade:
CascadeType.PERSIST
CascadeType.REFRESH
CascadeType.REMOVE
CascadeType.ALL
Le operazioni di salvataggio hanno effetto sulle istanze di classi
associate quando viene chiamato il metodo persist()
Le operazioni di aggiornamento hanno effetto sulle istanze di
classi associate quando viene chiamato il metodo refresh()
Le operazioni di eliminazione hanno effetto sulle istanze di classi
associate quando viene chiamato il metodo delete()
Tutte le operazioni hanno effetto sulle istanze di classi associate
2.5.6.8 Strategie di fetching
Le strategie di fetching definiscono il comportamento dinamico del framework di persistenza, in particolare
le modalità di caricamento in memoria principale di oggetti associati.
La scelta della strategia di fetching è molto importante per migliorare le performance del software.
In questo paragrafo verranno spiegate le differenti strategie di Hibernate e JPA, mentre alcune
considerazioni riguardo l'utilizzo di esse sono inserite nel paragrafo 2.9.
Hibernate
Hibernate mette a disposizione diverse strategie di fetching suddivise in base a quando e come esso viene
effettuato.
Strategie che definiscono quando viene effettuato il fetching:

Lazy collection fetching: una collezione viene caricata in memoria quando l'applicazione invoca
un'operazione su quella collezione. Questo è il comportamento di default, il valore dell'attributo
lazy per l'elemento che definisce il mapping dell'associazione è "true".

Immediate fetching: un'associazione o collezione viene caricata immediatamente quando viene
caricato l'oggetto associato. Questa strategia di mapping viene impostata specificando
lazy="false" nell'elemento che definisce il mapping dell'associazione.

"Extra-lazy" collection fetching: singoli elementi della collezione vengono acceduti dalla base di dati
secondo necessità. L'intera collezione viene caricata in memoria solo se strettamente necessario.
Questa strategia può essere utile in caso di grandi collezioni, viene impostata specificando
lazy="extra" nell'elemento che definisce il mapping per la collezione.

Proxy fetching: questa strategia utilizza proxy, dei segnaposto creati da Hibernate a tempo di
esecuzione per la gestione di associazioni a valore singolo. Quando viene richiesta l'istanza di una
entità, Hibernate può creare un proxy che identifica l'istanza e rimandare l'effettivo caricamento
76
dell'oggetto a quando verrà invocato un metodo su di esso diverso dal metodo get della proprietà
identificatore.
Esempio:
Acquisto acquisto = (Acquisto) session.load(
Acquisto.class, new Long(123));
acquisto.getId();
acquisto.getData();
Nell'esempio, il caricamento in memoria dell'oggetto acquisto viene effettuato quando viene
invocato il metodo getData(), prima viene utilizzato un proxy.
Il proxy fetching è impostato quando il valore dell'attributo lazy per l'elemento che definisce il
mapping dell'associazione è "true".

"No-proxy" fetching: il caricamento in memoria di associazioni a valore singolo viene effettuato
quando la variabile dell'istanza viene acceduta. Questa strategia di mapping viene impostata
specificando lazy="no-proxy" nell'elemento che definisce il mapping per l'associazione.
Strategie che definiscono come viene effettuato il fetching:

Select fetching: l'istanza associata o collezione viene recuperata con una seconda query.
A differenza dell'immediate fetching, la seconda query verrà eseguita quando l'associazione verrà
acceduta. Questo è il comportamento di default, il valore dell'attributo fetch per l'elemento che
definisce il mapping dell'associazione è "select".

Join fetching: l'istanza associata o collezione viene recuperata con la stessa query, usando un
OUTER JOIN. Questa strategia di mapping viene impostata specificando fetch="join"
nell'elemento che definisce il mapping per l'associazione.

Subselect fetching: quando viene impostata questa strategia su una collezione, verrà utilizzata una
seconda query per recuperare la collezione e tutte le collezioni delle entità caricate con la prima
query. Questa strategia di mapping viene impostata specificando fetch="subselect"
nell'elemento che definisce il mapping per l'associazione.

Batch fetching: è un'ottimizzazione del select fetching. La seconda select può essere utilizzata per il
recupero di N istanze di entità o collezioni. Questa strategia di mapping viene impostata
specificando batch-size="N" nell'elemento che definisce il mapping per l'associazione.
Esempio: definizione della strategia di fetching
File Acquisto.hbm.xml
<hibernate-mapping>
<class name="domain.Acquisto" table="acquisto">
...
77
<set name="comprende" inverse="true" lazy="false">
<key column="id_acquisto"/>
<one-to-many class="domain.ArticoloAcquistato"/>
</set>
...
</class>
</hibernate-mapping>
Java Persistence API
Le API Java Persistence mettono a disposizione solo due strategie di mapping: lazy o eager.
Il lazy fetching è l'impostazione di default mentre l'eager fetching è equivalente all'immediate fetching di
Hibernate. La strategia di fetching viene definita con la proprietà fetch per l'annotazione che definisce il
mapping dell'associazione impostandone il valore a FetchType.LAZY o FetchType.EAGER.
Esempio: definizione della strategia di fetching
File Acquisto.java
package dominio;
@Entity
@Table(name = "acquisto")
public class Acquisto {
private Long id;
private Date data;
@OneToMany(mappedBy = "acquisto", fetch = FetchType.EAGER)
private Set<ArticoloAcquistato> comprende;
...
}
2.5.7 Mapping di ereditarietà
Hibernate e JPA permettono il mapping di gerarchie di ereditarietà disgiunte e disgiunte complete.
Le strategie di base per il mapping di ereditarietà sono tre:

una tabella per gerarchia di classi

una tabella per sottoclasse

una tabella per classe concreta
78
Esempio: Classi java per mapping di ereditarietà
File Articolo.java
package domain.articolo;
public abstract class Articolo {
protected String codice;
protected String descrizione;
protected double prezzo;
protected Articolo() { }
...
}
File Abbigliamento.java
package domain.abbigliamento;
import domain.articolo.*;
public class Abbigliamento extends Articolo {
protected Set<String> materiali;
protected Abbigliamento() { }
...
}
File Calzature.java
package domain.calzature;
79
import domain.articolo.*;
public class Calzature extends Articolo {
protected String paese;
protected Calzature() { }
...
}
2.5.7.1 Una tabella per gerarchia di classi
Viene generata una sola tabella per tutta la gerarchia di classi.
Alla tabella della superclasse vengono aggiunte colonne per mappare le proprietà delle sottoclassi e una
colonna con funzione di discriminante.
Hibernate
Il file di mapping della superclasse contiene anche i metadati di mapping delle sottoclassi. Viene utilizzato
l’elemento <discriminator> per mappare la colonna con funzione di discriminante ed elementi
<subclass> per mappare le sottoclassi.
Questa strategia ha una limitazione: le colonne dichiarate dalle sottoclassi, non possono avere vincoli NOT
NULL.
Nota: è importante che l'elemento <discriminator> sia definito subito dopo l'elemento <id>.
File Articolo.hbm.xml
<hibernate-mapping>
<class name="domain.articolo.Articolo" table="articolo">
<id name="codice" column=" cod_articolo" type="string"/>
<discriminator column="tipo" type="string"/>
<property name="descrizione" column="descrizione" type="string"/>
<property name="prezzo" column="prezzo" type="double"/>
<subclass name="domain.abbigliamento.Abbigliamento"
discriminator-value="A">
<set name="materiali" table="materiali">
<key column="abbigliamento"/>
<element type="string" column="materiale"/>
</set>
</subclass>
<subclass name="domain.calzature.Calzature"
discriminator-value="C">
<property name="paese" column="paese" type="string"/>
</subclass>
</class>
</hibernate-mapping>
80
SQL output
CREATE TABLE articolo
(
cod_articolo character varying(255) NOT NULL,
tipo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
paese character varying(255),
CONSTRAINT articolo_pkey PRIMARY KEY (cod_articolo )
)
CREATE TABLE materiali
(
abbigliamento character varying(255) NOT NULL,
materiale character varying(255),
CONSTRAINT fk289940225a93a6c FOREIGN KEY (abbigliamento)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
La superclasse contiene l’annotazione @Inheritance con cui viene specificata la strategia di mapping
della gerarchia di classi con l’attributo strategy con valore InheritanceType.SINGLE_TABLE.
Le annotazioni @DiscriminatorColumn e @DiscriminatorValue servono rispettivamente a
definire la colonna discriminante e il valore che avranno le istanze delle sottoclassi per quella colonna.
JPA permette di utilizzare questa strategia anche per mappare gerarchie non complete.
In questo caso verrà aggiunta l’annotazione @DiscriminatorValue alla superclasse (concreta).
File Articolo.java
package domain.articolo;
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "tipo",
discriminatorType = DiscriminatorType.STRING
)
public abstract class Articolo {
protected String codice;
protected String descrizione;
protected double prezzo;
...
81
}
File Abbigliamento.java
package domain.abbigliamento;
import domain.articolo.*;
import javax.persistence.*;
@Entity
@DiscriminatorValue("A")
public class Abbigliamento extends Articolo {
protected Set<String> materiali;
...
}
File Calzature.java
package domain.calzature;
import domain.articolo.*;
import javax.persistence.*;
@Entity
@DiscriminatorValue("C")
public class Calzature extends Articolo {
protected String paese;
...
}
2.5.7.2 Una tabella per sottoclasse
Viene generata una tabella per la superclasse e una tabella per ogni sottoclasse.
Alle sottoclassi verrà aggiunta una colonna per la chiave primaria e un vincolo di foreign key verso la chiave
primaria della superclasse.
Hibernate
Le tabelle per le sottoclassi hanno un'associazione di chiave primaria con la tabella della superclasse.
All’interno del file di mapping della superclasse vengono inseriti elementi <joined-subclass> per
mappare le sottoclassi.
82
File Articolo.hbm.xml
<hibernate-mapping>
<class name="domain.articolo.Articolo" table="articolo">
<id name="codice" column="cod_articolo" type="string"/>
<property name="descrizione" column="descrizione" type="string"/>
<property name="prezzo" column="prezzo" type="double"/>
<joined-subclass name="domain.abbigliamento.Abbigliamento"
table="abbigliamento">
<key column="cod_articolo"/>
<set name="materiali" table="materiali">
<key column="abbigliamento"/>
<element type="string" column="materiale"/>
</set>
</joined-subclass>
<joined-subclass name="domain.calzature.Calzature"
table="calzature">
<key column="cod_articolo"/>
<property name="paese" column="paese" type="string"/>
</joined-subclass>
</class>
</hibernate-mapping>
SQL output
CREATE TABLE articolo
(
cod_articolo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
CONSTRAINT articolo_pkey PRIMARY KEY (cod_articolo )
)
CREATE TABLE abbigliamento
(
cod_articolo character varying(255) NOT NULL,
CONSTRAINT abbigliamento_pkey PRIMARY KEY (cod_articolo ),
CONSTRAINT fk5bfe290c17adf557 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE calzature
(
cod_articolo character varying(255) NOT NULL,
paese character varying(255),
CONSTRAINT calzature_pkey PRIMARY KEY (cod_articolo ),
83
CONSTRAINT fkd9a6916917adf557 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE materiali
(
abbigliamento character varying(255) NOT NULL,
materiale character varying(255),
CONSTRAINT fk289940225a93a6c FOREIGN KEY (abbigliamento)
REFERENCES abbigliamento (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
Questa strategia di mapping viene specificata dal valore InheritanceType.JOINED della proprietà
strategy per l’annotazion@Inheritance.
File Articolo.java
package domain.articolo;
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Articolo {
protected String codice;
protected String descrizione;
protected double prezzo;
...
}
File Abbigliamento.java
package domain.abbigliamento;
import domain.articolo.*;
import javax.persistence.*;
@Entity
public class Abbigliamento extends Articolo {
protected Set<String> materiali;
...
}
84
File Calzature.java
package domain.calzature;
import domain.articolo.*;
import javax.persistence.*;
@Entity
public class Calzature extends Articolo {
protected String paese;
...
}
2.5.7.3 Una tabella per classe concreta
Viene generata una tabella per ogni classe concreta della gerarchia.
In ogni tabella vengono definite tutte le proprietà della classe, comprese quelle ereditate.
Questa strategia può essere utilizzata per mappare gerarchie di ereditarietà disgiunte non complete.
Hibernate
E' l'unica strategia che permette di mappare gerarchie disgiunte non complete con Hibernate.
Se la gerarchia di ereditarietà da mappare è disgiunta completa, la superclasse sarà astratta e verrà definito
l'attributo abstract=”true” per l'elemento <class>.
Se la gerarchia da mappare è disgiunta non completa, la superclasse verrà mappata su una tabella
aggiuntiva, necessaria per rendere persistenti le istanze della superclasse.
Come nelle strategie precedenti, i metadati per il mapping delle sottoclassi viene specificato nel file di
mapping della superclasse. Vengono utilizzati elementi <union-subclass> per le sottoclassi.
Una limitazione di questa strategia è il fatto che non è possibile utilizzare generatori di identificatori.
File Articolo.hbm.xml
<hibernate-mapping>
<class name="domain.articolo.Articolo" table="articolo" abstract="true">
<id name="codice" column="cod_articolo" type="string"/>
<property name="descrizione" column="descrizione" type="string"/>
<property name="prezzo" column="prezzo" type="double"/>
<union-subclass name="domain.abbigliamento.Abbigliamento"
table="abbigliamento">
<set name="materiali" table="materiali">
<key column="abbigliamento"/>
<element type="string" column="materiale"/>
</set>
</union-subclass>
85
<union-subclass name="domain.calzature.Calzature"
table="calzature">
<property name="paese" column="paese" type="string"/>
</union-subclass>
</class>
</hibernate-mapping>
SQL output
CREATE TABLE abbigliamento
(
cod_articolo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
CONSTRAINT abbigliamento_pkey PRIMARY KEY (cod_articolo )
)
CREATE TABLE calzature
(
cod_articolo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
paese character varying(255),
CONSTRAINT calzature_pkey PRIMARY KEY (cod_articolo )
)
CREATE TABLE materiali
(
abbigliamento character varying(255) NOT NULL,
materiale character varying(255),
CONSTRAINT fk289940225a93a6c FOREIGN KEY (abbigliamento)
REFERENCES abbigliamento (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java Persistence API
L’annotazione @Inheritance specifica l’attributo strategy con valore
InheritanceType.TABLE_PER_CLASS.
In caso di gerarchie disgiunte non complete, i metadati di mapping non hanno bisogno di modifiche.
File Articolo.java
package domain.articolo;
import javax.persistence.*;
86
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Articolo {
protected String codice;
protected String descrizione;
protected double prezzo;
...
}
File Abbigliamento.java
package domain.abbigliamento;
import domain.articolo.*;
import javax.persistence.*;
@Entity
public class Abbigliamento extends Articolo {
protected Set<String> materiali;
protected Abbigliamento() { }
...
}
File Calzature.java
package domain.calzature;
import domain.articolo.*;
import javax.persistence.*;
@Entity
public class Calzature extends Articolo {
protected String paese;
protected Calzature() { }
...
}
87
2.6 Interfacce di Hibernate
L'accesso alla base di dati avviene utilizzando quattro principali interfacce fornite da Hibernate:
SessionFactory, Session, Transaction e Query.
2.6.1 SessionFactory (org.hibernate.SessionFactory)
Con questa interfaccia è possibile la creazione di istanze di Session. Solitamente in un'applicazione viene
creata una singola istanza di SessionFactory per la creazione di istanze di Session che vengono
passate ai thread che si occupano delle richieste dei client.
Metodi principali:

void close()
Distrugge la SessionFactory e rilascia le risorse.

Session openSession()
Apre una Session.
2.6.2 Session (org.hibernate.Session)
Session permette di gestire una sessione di comunicazione con la base di dati durante la quale è
possibile utilizzare metodi specifici per la modifica dei dati persistenti lavorando con oggetti.
Metodi principali:

Transaction beginTransaction()
Crea un nuovo oggetto Transaction.

Connection close()
Termina la sessione restituendo la connessione JDBC.

Query createQuery(String queryString)
Crea un nuovo oggetto Query dalla query in Hibernate Query Language (HQL) passata come
argomento.

SQLQuery createSQLQuery(String queryString)
Crea un nuovo oggetto SQLQuery dalla query in SQL nativo passata come argomento.

void delete(Object object)
Rimuove un'istanza persistente dalla base di dati.

Object get(Class clazz, Serializable id)
Come load()carica una tupla della base di dati, però se non viene trovata, non lancia eccezioni
ma ritorna null.
88

Object load(Class theClass, Serializable id)
Carica una tupla di una tabella della base di dati in un'istanza della classe corrispondente
utilizzando il suo identificatore. Se non viene trovata una tupla corrispondente all'identificatore
passato come argomento, viene lanciata un'eccezione non recuperabile.

Serializable save(Object object)
Rende persistente l'oggetto passato come argomento e ritorna un identificatore per l'oggetto.

void update(Object object)
Aggiorna un'istanza persistente con l'identificatore dell'oggetto passato come argomento.
2.6.3 Transaction (org.hibernate.Transaction)
Con l'interfaccia Transaction vengono gestite transizioni associate a sessioni.
Una nuova istanza di Transaction viene solitamente creata con il metodo beginTransaction() di
Session che imposta automaticamente l'autocommit a false.
Metodi principali:

void begin()
Inizia una nuova transazione.

void commit()
Esegue il commit della transazione e termina l'unità di lavoro.

void rollback()
Esegue il roll back della transazione.
2.6.4 Query (org.hibernate.Query)
E' una rappresentazione object-oriented di una query Hibernate.
Una nuova istanza di Query viene creata dal metodo createQuery() di Session e permette la
creazione di una query in HQL, il linguaggio di Hibernate per le query, da una stringa passata come
argomento.
Metodi principali:

int executeUpdate()
Esegue uno statement di aggiornamento o eliminazione.

List list()
Ritorna il risultato della query come istanza di List.

Iterator iterate()
Ritorna il risultato della query come istanza di Iterator.
89

Object uniqueResult()
Esegue la query e ritorna il singolo risultato come istanza di Object.
L'interfaccia Query è estesa dall'interfaccia SQLQuery che permette di creare query in SQL nativo. Una
nuova istanza di SQLQuery viene creata dal metodo createSQLQuery() di Session.
2.7 Java Persistence API
Nelle applicazioni Java Persistence l’interazione con la base di dati è possibile utilizzando quattro principali
interfacce equivalenti a quelle di Hibernate: EntityManagerFactory, EntityManager,
EntityTransaction, Query.
2.7.1 EntityManagerFactory
(javax.persistence.EntityManagerFactory)
Permette la creazione di istanze di EntityManager per iniziare unità di lavoro, in modo equivalente a
SessionFactory con Hibernate. Una volta chiusa la EntityManagerFactory tutte le sue istanze di
EntityManager verranno considerate chiuse.
Metodi principali:

void close()
Chiude la EntityManagerFactory e rilascia le risorse.

EntityManager createEntityManager()
Apre un EntityManager.
2.7.2 EntityManager (javax.persistence.EntityManager)
L’interfaccia EntityManager fornisce metodi per interagire con il contesto persistente.
Ad ogni istanza di EntityManager è associato un contesto persistente, un insieme di istanze di entità nel
quale per ogni entità persistente c’è un’unica istanza di entità.
Metodi principali:

void close()
Chiude l’EntityManager.

Query createQuery(java.lang.String qlString)
Crea un’istanza di Query per eseguire una query in Java Persistence Query Language (JPQL).
90

<T> TypedQuery<T> createQuery(java.lang.String qlString,
java.lang.Class resultClass)
Crea un’istanza di TypedQuery, sottointerfaccia di Query, per eseguire una query in Java
Persistence Query Language (JPQL). resultClass specifica la classe del risultato della query.

Query createNativeQuery(java.lang.String sqlString)
Crea un nuovo oggetto Query per eseguire uno statement in SQL nativo.

Query createNativeQuery(java.lang.String sqlString,
java.lang.Class resultClass)
Crea un nuovo oggetto Query per eseguire una query in SQL nativo.
Con resultClass viene specificata la classe del risultato della query.

<T> T find(java.lang.Class<T> entityClass,
java.lang.Object primaryKey)
Carica una tuple dalla base di dati utilizzando la chiave primaria.

void flush()
Sincronizza il contesto di persistenza con la base di dati sottostante.

EntityTransaction getTransaction()
Crea un nuovo oggetto EntityTransaction.

void persist(java.lang.Object entity)
Rende persistente un oggetto.

void refresh(java.lang.Object entity)
Aggiorna lo stato di un oggetto dal database, sovrascrivendo eventuali modifiche all’entità.

void remove(java.lang.Object entity)
Rimuove l’istanza di entità.
2.7.3 EntityTransaction
(javax.persistence.EntityTransaction)
EntityTransaction permette la gestione di transazioni. Una nuova istanza di
EntityTransaction viene creata con il metodo getTransaction() di EntityManager.
Metodi principali:

void begin()
Inizia una nuova transazione.

void commit()
Esegue il commit della transazione e termina l'unità di lavoro.
91

void rollback()
Esegue il roll back della transazione.
2.7.4 Query (javax.persistence.Query)
Query è l’interfaccia per il controllo e l’esecuzione di query. Con questa interfaccia possono essere
eseguite istanze di query JPQL, il linguaggio di JPA per le query, e istanze di SQL nativo.
Metodi principali:

int executeUpdate()
Esegue uno statement di aggiornamento o eliminazione.

java.util.List getResultList()
Esegue la query e ritorna il risultato come istanza di List.

java.lang.Object getSingleResult()
Esegue la query e ritorna il singolo risultato come istanza di Object.
2.8 Hibernate Query Language e Java Persistence Query Language
Hibernate Query Language (HQL) e Java Persistence Query Language (JPQL) sono due linguaggi utilizzati
rispettivamente in applicazioni Hibernate e Java Persistence per ottimizzare l’interazione con la base di dati.
HQL e JPQL sono strutturati in maniera molto simile a SQL ma sono completamente object-oriented, quindi
utilizzano i nomi delle classi di dominio invece dei nomi delle tabelle della base di dati e i nomi delle
proprietà invece dei nomi delle colonne. Permettono inoltre di utilizzare concetti come l’ereditarietà e le
associazioni tra classi.
HQL è un’estensione dello standard definito per JPQL nelle Java Persistence API, quindi ogni statement
JPQL è anche valido per HQL.
L’utilizzo di HQL e JPQL aumenta la portabilità del software perché il linguaggio è indipendente dalla
piattaforma: quando viene eseguito uno statement, questo viene convertito in uno o più statement in SQL
nativo per la base di dati configurata.
Le operazioni disponibili con HQL e JPQL sono l’interrogazione della base di dati mediante query e
operazioni di aggiornamento ed eliminazione.
2.8.1 Creazione di un oggetto query
Hibernate
La creazione di istanze di org.hibernate.Query viene effettuata utilizzando istanze di Session.
92
Il metodo dell’interfaccia Session per la creazione di istanze di Query è
createQuery(String queryString) che prende come argomento una stringa che rappresenta la
query HQL.
Esempio: Creazione di un’istanza di Query
Query hqlQuery = session.createQuery("from Utente");
La creazione di una query HQL è molto più compatta ed immedita rispetto alla creazione di una query in
SQL nativo. In questo caso verrà utilizzato il metodo createSQLQuery(String queryString).
Esempio: Creazione di un’istanza di SQLQuery
Query sqlQuery =
session.createSQLQuery(
"select {utente.*} from utente {utente}"
).addEntity("utente", Utente.class);
E’ importante notare come per la crezione di una query in SQL nativo sia necessario chiamare il metodo
addEntity(String tableAlias, Class entityType) per specificare l’entità che verrà
gestita nella query.
In particolare esistono due più importanti metodi addEntity():
-
SQLQuery addEntity(Class entityType)
Imposta la classe dell’entità coinvolta nella query.
SQLQuery addEntity(String tableAlias, Class entityType)
Imposta la classe dell’entità specificando l’alias utilizzato nella query.
Si ricorda che SQLQuery è sottointerfaccia di Query.
Java Persistence API
Per la creazione di istanze di javax.persistence.Query è necessario utilizzare un oggetto
EntityManager.
Viene utilizzato il metodo createQuery(String qlString) che prende come argomento una
stringa che rappresenta la query JPQL.
Esempio: Creazione di un’istanza di Query
Query ejbQuery = em.createQuery("select u from Utente u");
E’ anche disponibile il metodo createQuery(java.lang.String qlString
java.lang.Class resultClass) per specificare la classe del risultato della query.
93
Le query in SQL nativo vengono create con il metodo createNativeQuery(
java.lang.String sqlString) , disponibile anche con la possibilità di specificare la classe
risultante della query createNativeQuery(java.lang.String sqlString,
java.lang.Class resultClass).
Esempio: Creazione di istanze di Query in SQL nativo
Query sqlQuery =
em.createNativeQuery(
"select u.id_utente, u.nome, u.cognome from utente u",
Utente.class
);
2.8.2 Binding dei parametri
Il binding dei parametri a runtime permette alla base di dati di gestire in modo efficiente prepared
statement precompilate, migliorando le performance.
Il binding di parametri può essere di tipo posizionale o utilizzare parametri con nome.
I parametri con nome vengono specificati da due punti seguiti dal nome del parametro.
Esempio: Binding di parametri con nome
String queryString =
"from Articolo articolo where articolo.descrizione like :search";
I parametri per il binding di tipo posizionale vengono specificati da un punto interrogativo in HQL e da un
punto interrogativo seguito dal numero del parametro in JPQL.
Esempio: Binding di parametri di tipo posizionale con HQL
String queryString =
"from Articolo articolo where articolo.descrizione like ?";
Esempio: Binding di parametri di tipo posizionale con JPQL
String queryString =
"select articolo from Articolo articolo where articolo.descrizione like ?1";
Hibernate
Il binding di un valore nel parametro con nome viene effettuato con metodi setXXX() che prendono
come argomento un tipo specifico.
Query q = session.createQuery(queryString)
.setString("search", searchString);
94
Per il binding di tipo posizionale si utilizzano metodi analoghi al binding di parametri con nome che
prendono però come argomento la posizione del parametro e il valore da assegnare.
Query q = session.createQuery(queryString)
.setString(0, searchString);
Java Persistence API
Il binding avviene in modo leggermente diverso rispetto ad Hibernate.
Viene utilizzato il metodo generico setParameter(java.lang.String name,
java.lang.Object value) che riconosce automaticamente il tipo di dato.
Si potrebbero avere problemi con tipi che rappresentano date o tempo, in questo caso si utilizzerà una
variante più specifica del metodo, per esempio setParameter(java.lang.String name,
java.util.Date value, TemporalType temporalType).
Query q = em.createQuery(queryString)
.setParameter("search", searchString);
Il binding di parametri di tipo posizionale viene effettuato utilizzando il metodo
setParameter(int position, java.lang.Object value).
Query q = em.createQuery(queryString)
.setParameter(1, searchString);
Nota: Anche Hibernate supporta il metodo setParameter() per entrambi i tipi di binding.
2.8.3 Gestione dei risultati
Il metodo più utilizzato per gestire il risultato di una query è di caricare in memoria l’intero risultato.
Hibernate
In applicazioni Hibernate viene utilizzato il metodo list() di Query che esegue la query e ritorna il
risultato come java.util.List.
Esempio: Utilizzo del metodo list()
List result = hqlQuery.list();
Hibernate mette a disposizione altre due strategie per l’accesso ai risultati di una query:
-
Iteratore: chiamando il metodo iterate() della classe Query viene ritornato un oggetto
Iterator per la lettura della collezione di oggetti che è risultato della query. Utilizzando il
metodo iterate() vengono caricati dalla base di dati solo le chiavi primarie degli oggetti e
successivamente viene cercata la parte restante dello stato degli oggetti nella cache del contesto di
persistenza.
95
-
Scrollable resultset: questa strategia permette di creare un iteratore gestito dal DBMS per accedere
ai dati senza caricare tutto il risultato della query in memoria. Viene utilizzato il metodo
scroll() di Query che ritorna un oggetto ScrollableResults con cui è possibile utilizzare
metodi specifici per accedere alla base di dati.
Java Persistence API
In applicazioni Java Persistence il metodo getResultList() di Query restituisce il risultato come
istanza di java.util.List in modo analogo al metodo list() di Hibernate.
Esempio: Utilizzo del metodo getResultList()
List result = jpqlQuery.getResultList();
Nel caso in cui si è a conoscenza che il risultato della query è un singolo oggetto, si potrà utilizzare il
metodo uniqueResult().
2.8.4 Operazioni di aggiornamento ed eliminazione
Le operazioni di aggiornamento ed eliminazione con HQL e JPQL vengono effettuate tramite update
statement e delete statement, molto simili a statement SQL ma utilizzando nomi di entità e proprietà.
Sono presenti alcune regole da seguire per la scrittura di update statement e delete statement:
-
Non è possibile effettuare join;
È possibile eseguire statement per un’unica entità;
L’uso di alias è opzionale;
Sono permesse le sottoquery nella clausola where.
L’esecuzione di statement HQL e JPQL avviene chiamando il metodo executeUpdate() di Query che
ritorna il numero di oggetti entità modificati.
Negli esempi presentati in seguito verranno mostrati update statement e delete statement che agiscono su
una gerarchia di classi disgiunta e completa già presentata nel paragrafo dedicato al mapping di
ereditarietà: le classi Abbigliamento e Calzature sono sottoclassi della classe astratta Articolo.
Hibernate
Nell’esempio di update statement HQL viene modificata la proprietà descrizione di un oggetto
Articolo. Viene utilizzato il binding del parametro con nome descr.
Esempio: update statement HQL
Query hqlquery = session.createQuery("update Articolo a "
+ "set a.descrizione = :descr "
+ "where a.codice = 'A0001'");
hqlquery.setString("descr", "Articolo A0001");
96
int updatedItems = hqlquery.executeUpdate();
In questo caso viene eliminato l’oggetto istanza di Abbigliamento con codice “A0001”.
Esempio: delete statement HQL
Query hqlquery = session.createQuery("delete Abbigliamento a "
+ "where a.codice = :code");
hqlquery.setString("code", "A0001");
int updatedItems = hqlquery.executeUpdate();
Java Persistence API
Come sarà possibile notare, gli esempi di statement JPQL non presentano alcuna differenza rispetto ai
corrispettivi esempi HQL.
Esempio: update statement JPQL
Query jpqlquery = em.createQuery("update Articolo a "
+ "set a.descrizione = :descr "
+ "where a.codice = 'A0001'");
jpqlquery.setParameter("descr", "Articolo A0001");
int updatedItems = jpqlquery.executeUpdate();
Esempio: delete statement JPQL
Query jpqlquery = em.createQuery("delete Abbigliamento a "
+ "where a.codice = :code");
jpqlquery. setParameter("code", "A0001");
int updatedItems = jpqlquery.executeUpdate();
2.8.5 Query
Le query scritte utilizzando HQL e JPQL, come già scritto in precedenza, non sono molto diverse nella
struttura rispetto alle query SQL. I select statement sono composti dalla target list, dalla clausola from e
dalla clausola where. In HQL la target list è opzionale ma questo non crea una grande differenza perché
sarà possibile ometterla solo nel caso in cui non si vorrà effettuare una proiezione (select *).
Esempio: Query HQL
from Utente
Esempio: Query JPQL
select u from Utente u
97
Per una spiegazione approfondita sulle regole di scrittura di query HQL e JPQL si consiglia di consultare la
documentazione ufficiale.
2.8 Generazione dello schema della base di dati
Hibernate
La generazione automatica dello schema della base di dati avviene utilizzando lo strumento hbm2ddl, in
particolare la classe org.hibernate.tool.hbm2ddl.SchemaExport.
Metodi di utilizzo dello strumento hbm2ddl:
1. Eseguire <hbm2ddl> in un target Ant per generare lo schema della base di dati durante la
procedura di compilazione.
2. Eseguire SchemaExport all’interno del codice dell’applicazione.
Esempio:
Configuration cfg = new Configuration().configure();
SchemaExport schemaExport = new SchemaExport(cfg);
schemaExport.create(false, true);
3. Impostare la proprietà di configurazione hibernate.hbm2ddl.auto a create per avviare la
generazione automatica dello schema alla creazione di SessionFactory.
Esempio:
File hibernate.hbm.xml
<hibernate-configuration>
<session-factory>
...
<property name="hibernate.hbm2ddl.auto">create</property>
...
</session-factory>
</hibernate-configuration>
Valori disponibili per la proprietà hibernate.hbm2ddl.auto:
create
create-drop
update
validate
Genera lo schema distruggendo eventuali dati presenti.
Genera lo schema e lo distrugge quando viene chiusa la SessionFactory.
Aggiorna lo schema.
Convalida lo schema. Non effettua cambiamenti.
98
Java Persistence API
Le applicazioni Java Persistence che utilizzano Hibernate come provider fanno anch’esse uso di hbm2ddl
come strumento per la creazione dello schema della base di dati. In particolare i metodi più utilizzati sono i
target Ant e l’impostazione della proprietà hibernate.hbm2ddl.auto nel file di configurazione
persistence.xml.
Esempio:
File persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="testPU">
...
<properties>
...
<property name="hibernate.hbm2ddl.auto" value="create"/>
...
</properties>
</persistence-unit>
</persistence>
2.9 Creazione e funzionamento di un progetto
Hibernate
Il primo passo per iniziare un progetto Hibernate è effettuare il download dell’ultima versione di Hibernate
nel formato release bundle dal sito ufficiale www.hibernate.org. Nell’archivio è presente la directory lib
all’interno della quale sono presenti le API Hibernate. Per la creazione dell’applicazione verranno inclusi tra
le librerie del progetto i file jar presenti nella directory requied all’interno di lib.
Tra le librerie del progetto dovrà anche essere incluso il driver JDBC della base di dati utilizzata e il software
per la creazione di un pool di connessioni (es. c3p0).
A questo punto si potrà procedere con la realizzazione delle classi di dominio secondo l’approccio POJO e la
creazione dei file XML per il mapping delle classi stesse.
Infine verrà creato il file XML di configurazione di Hibernate, hibernate.cfg.xml.
L’inizializzazione di Hibernate avviene istanziando un oggetto della classe Configuration
(org.hibernate.cfg.Configuration) e richiamando i metodi configure(), che configura le
impostazioni dal file hibernate.cfg.xml, e buildSessionFactory() per la creazione di
un’istanza di SessionFactory che potrà essere utilizzata per l’intera applicazione.
La creazione e la distruzione di SessionFactory può essere affidata ad una classe HibernateUtil.
99
Esempio: HibernateUtil per la creazione di SessionFactory
File HibernateUtil.java :
package persistence;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void shutdown() {
//Close caches and connection pools
getSessionFactory().close();
}
}
Con SessionFactory si potranno creare istanza di Session con il metodo openSession() e
richiamare i vari metodi per la comunicazione con la base di dati. Ogni unità di lavoro utilizzerà un’istanza di
Session e verrà conclusa dal metodo close().
Durante la fase di progettazione è importante considerare anche il comportamento dinamico di Hibernate
per decidere la strategia di fetching adeguata ad ognuna delle associazioni mappate.
Alcune strategie potrebbero migliorare le prestazioni del software, altre peggiorarle o provocare la
generazione di eccezioni. La scelta di una strategia di fetching dovrà essere effettuata tenendo conto della
quantità di dati da caricare in memoria principale, del numero e della struttura delle query utilizzate da
Hibernate e di come i dati verranno in seguito gestiti dall'applicazione. L'utilizzo di lazy fetching è sempre
consigliato anche se potrebbe creare problemi nella gestione dei dati generando l'eccezione
LazyInitializationException. In generale, in caso di lazy fetching, bisogna sempre ricordare che
l'accesso ai dati deve essere effettuato all'interno di una session. In alcuni casi sarà necessario utilizzare
immediate fetching, bisognerà fare attenzione alla quantità di dati che verranno caricati con una
songola richiesta al framework perchè un'errato utilizzo di questa strategia può causare una reazione a
100
catena che porta al caricamento una gran quantità di tuple della base di dati peggiorando molto le
prestazioni del software. Ogni situazione deve quindi essere analizzata dal punto di vista dell'intero modello
della base di dati.
Le strategie che definiscono come viene effettuato il fetching possono spesso migliorare le performance
diminuendo il numero di query che il framework utilizza per il recupero dei dati. La scelta di queste
strategie di ottimizzazione necessita di uno studio specifico degli scenari in cui può trovarsi l'applicazione.
Esempio: main per un’applicazione
File Main.java
package app;
import domain.*;
import org.hibernate.*;
import persistence.HibernateUtil;
public class Main {
public static void main(String[] args) {
//First unit of work
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
Utente utente1 = new Utente("utente1", "password1");
nuovoUtente.inserisciNumTelefono("123456789");
Articolo a1 = new Articolo("A1", "articolo1", 1.00);
Articolo a2 = new Articolo("A2", "articolo2", 2.50);
Articolo a3 = new Articolo("A3", "articolo3", 5.00);
String cod1 = (String) session.save(a1);
session.save(a2);
session.save(a3);
session.save(utente1);
tx.commit();
session.close();
//Second unit of work
session = HibernateUtil.getSessionFactory().openSession();
tx = session.beginTransaction();
Acquisto ac1 = new Acquisto();
try {
ac1.inserisciArticolo(a1);
ac1.inserisciArticolo(a2);
utente1.inserisciAcquisto(ac1);
}
101
catch(EccezionePrecondizioni e) {
e.printStackTrace();
}
session.save(ac1);
session.update(utente1);
a1 = (Articolo) session.get(Articolo.class, cod1);
a1.setPrezzo(1.10);
tx.commit();
session.close();
//Third unit of work
session = HibernateUtil.getSessionFactory().openSession();
tx = session.beginTransaction();
List articoli = session.createSQLQuery(
"SELECT * " +
"FROM articolo " +
"ORDER BY cod_articolo;").addEntity(Articolo.class).list();
System.out.println(
"Numero articoli: " + articoli.size() + "\n");
for (Iterator iter= articoli.iterator(); iter.hasNext(); ) {
Articolo a = (Articolo) iter.next();
System.out.println("codice: " + a.getCodice() +
"\ndescrizione: " + a.getDescrizione() +
"\nprezzo: " + a.getPrezzo() + "\n");
}
List acquisti = session.createSQLQuery(
"SELECT * " +
"FROM acquisto " +
"ORDER BY id_acquisto;").addEntity(Acquisto.class).list();
System.out.println("Numero acquisti: " + acquisti.size());
for (Iterator iter= acquisti.iterator(); iter.hasNext(); ) {
Acquisto ac = (Acquisto) iter.next();
System.out.println("id: " + ac.getId() +
"\ndata: " + ac.getData() + "\n\n");
}
tx.commit();
session.close();
//Shutting down the application
HibernateUtil.shutdown();
}
}
102
Java Persistence API
Le librerie necessarie alla creazione di un progetto Java Persistence con Hibernate sono contenute
all’interno del release bundle di Hibernate. Tra le librerie del progetto verranno inclusi i file jar contenuti
nella directory required e il file hibernate-entitymanager contenuto nella directory jpa, oltre al driver JDBC
della base di dati utilizzata e il software per la creazione di un pool di connessioni.
E’ ora possibile sviluppare l’applicazione scrivendo le classi di dominio POJO con annotazioni e il file di
configurazione persistence.xml contenuto in una directory nominata META-INF.
L’inizializzazione avverrà alla creazione dell’istanza di EntityManagerFactory per l’applicazione. Per
fare ciò verrà utilizzato il metodo statico createEntityManagerFactory() della classe
javax.persistence.Persistence che prende come argomento il nome della persistence unit per
la EntityManagerFactory.
Avendo a disposizione un’istanza di EntityManagerFactory sarà possibile creare diverse istanze di
EntityManager con il metodo createEntityManager() per poter interagire con la base di dati.
Al termine dell’applicazione verrà chiusa la EntityManagerFactory con il metodo close().
Le considerazioni fatte per Hibernate riguardanti la scelta delle strategie di fetching per le associazioni
valgono anche in questo caso, con la differenza che lo standard JPA non fornisce una gran varietà di
strategie. In base all'implementazione delle API Java Persistence potranno essere disponibili strategie
alternative.
Esempio: main per un’applicazione
File Main.java
package app;
import domain.*;
import javax.persistence.*;
public class Main {
public static void main(String[] args) {
/// Start EntityManagerFactory
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("testPU");
// First unit of work
EntityManager em1 = emf.createEntityManager();
EntityTransaction tx1 = em1.getTransaction();
tx1.begin();
Utente utente1 = new Utente("utente1", "password1");
nuovoUtente.inserisciNumTelefono("123456789");
Articolo a1 = new Articolo("A1", "articolo1", 1.00);
Articolo a2 = new Articolo("A2", "articolo2", 2.50);
Articolo a3 = new Articolo("A3", "articolo3", 5.00);
103
em1.persist(a1);
String cod1 = a1.getCodice();
em1.persist(a2);
em1.persist(a3);
em1.persist(utente1);
tx1.commit();
em1.close();
//Second unit of work
EntityManager em2 = emf.createEntityManager();
EntityTransaction tx2 = em2.getTransaction();
tx2.begin();
Acquisto ac1 = new Acquisto();
try {
ac1.inserisciArticolo(a1);
ac1.inserisciArticolo(a2);
utente1.inserisciAcquisto(ac1);
}
catch(EccezionePrecondizioni e) {
e.printStackTrace();
}
em2.persist(ac1);
em2.refresh(utente1)
a1 = (Abbigliamento) em2.getReference(Abbigliamento.class, cod1);
a1.setPrezzo(55.5);
tx2.commit();
em2.close();
//Third unit of work
EntityManager em3 = emf.createEntityManager();
EntityTransaction tx3 = em3.getTransaction();
tx3.begin();
List articoli = em3.createNativeQuery(
"SELECT * " +
"FROM articolo " +
"ORDER BY cod_articolo;",
Articolo.class).getResultList();
System.out.println(
"Numero articoli: " + articoli.size() + "\n");
for (Iterator iter= articoli.iterator(); iter.hasNext(); ) {
Articolo a = (Articolo) iter.next();
System.out.println("codice: " + a.getCodice() +
"\ndescrizione: " + a.getDescrizione() +
"\nprezzo: " + a.getPrezzo() + "\n");
}
List acquisti = em3.createNativeQuery(
"SELECT * " +
"FROM acquisto " +
"ORDER BY id_acquisto;",
104
Acquisto.class).getResultList();
System.out.println("Numero acquisti: " + acquisti.size());
for (Iterator iter= acquisti.iterator(); iter.hasNext(); ) {
Acquisto ac = (Acquisto) iter.next();
System.out.println("id: " + ac.getId() +
"\ndata: " + ac.getData() + "\n\n");
}
tx3.commit();
em3.close();
// Shutting down the application
emf.close();
}
}
105
3. APPLICAZIONE DI ESEMPIO
Viene ora presentata una applicazione di esempio realizzata utilizzando le Java Persistence API.
L’applicazione è composta dalle classi di dominio che contengono le annotazioni per il mapping, una base di
dati generata automaticamente dal software Hibernate che implementa le Java Persistence API, le classi
delle attività per le operazioni e una interfaccia grafica realizzata con il framework Java Swing.
3.1 Requisiti
L’applicazione riguarda la gestione di un negozio di scarpe ed abbigliamento. Degli articoli in vendita presso
il negozio interessa conoscere il codice (una stringa), una descrizione (una stringa) ed il prezzo per singola
unità (un reale positivo). Gli articoli sono solo di due tipi: articoli di abbigliamento e calzature. Dei primi
interessa sapere con quali materiali sono realizzati (un insieme non vuoto di stringhe), mentre delle
alzature interessa conoscere il paese in cui sono state fabbricate (una stringa). Sul prezzo di una calzatura
può essere effettuato uno sconto quando questa è acquistata insieme a determinati capi di abbigliamento.
Per ogni calzatura interessa conoscere quindi quali sono gli articoli di abbigliamento che danno
diritto allo sconto sul prezzo della calzatura se acquistati insieme ad essa, e l'entità di tale sconto in
percentuale (un intero compreso fra 5 e 80). Si noti che per ogni calzatura esiste sempre almeno un articolo
di abbigliamento associato che dà diritto allo sconto, e che tale sconto dipende dall'articolo a cui la
calzatura è associata (ad esempio, se si acquista la calzatura X insieme all'articolo di abbigliamento Y si ha
diritto ad uno sconto del 10% sul prezzo di X, mentre se la si acquista insieme all'articolo di abbigliamento Z
si ha diritto ad uno sconto del 20%, sempre sul prezzo di X). Per ogni acquisto che viene effettuato presso il
negozio interessa conoscere la data, l'importo complessivo effettivamente pagato (un reale positivo), quali
articoli sono stati acquistati (almeno uno), indicando per ciascuno di questi la quantità acquistata (un intero
positivo). Inoltre, per ogni calzatura acquistata, si vuole anche conoscere lo sconto praticato nell'acquisto
(un intero compreso fra 0 e 100, dove 0 indica in effetti che non c'è stato sconto).
L’applicazione è composta da tre attività.
La prima attività permette di inserire nuovi acquisti nella base di dati. Inizialmente è possibile scegliere gli
articoli da inserire nell’acquisto, successivamente viene inserito in nuovo acquisto nella base di dati e alla
fine viene stampato l’importo totale dell’acquisto.
La seconda attività permette di visualizzare una lista completa degli acquisti contenuti nella base di dati che
comprende l’ID degli acquisti e la data di creazione.
L’ultima è l’attività di controllo della correttezza degli acquisti e produzione di informazioni statistiche, che
prende come parametro un acquisto a. L'attività inizia verificando che l'importo complessivo
effettivamente pagato per a sia minore od uguale del totale che si ottiene sommando i costi dei singoli
articoli acquistati tramite a (ovviamente considerando per ogni articolo la quantità di articoli acquistati). Se
il risultato è false si stampa un messaggio di errore e si termina. Se il risultato è true, allora
concorrentemente partono le seguenti sottoattività: (i) dato l'acquisto a, si restituisce l'insieme di calzature
acquistate tramite a per le quali è stato praticato uno sconto (maggiore di 0); (ii) dato l'acquisto a, si
restituisce l'insieme dei materiali con cui sono realizzati gli articoli facenti parte di a. Una volta che tali
sottoattività sono completate, si stampa il contenuto dei due insiemi risultato delle sottoattività.
106
3.2 Classi di dominio
Le classi di dominio devono essere realizzate secondo l’approccio POJO.
Le fasi di analisi e progetto vengono effettuate senza particolari accorgimenti.
Diagramma UML delle classi:
Responsabilità sulle associazioni:
Associazione
comprende
sconto
Classe
Acquisto
Articolo
Calzature
Abbigliamento
ha responsabilità
SI
NO
SI
NO
Dal diagramma UML delle classi è possibile notare che sono presenti quattro entità fondamentali:
Acquisto, Articolo, Abbigliamento e Calzature. Le classi Articolo, Abbigliamento e
Calzature compongono una gerarchia di classi disgiunta e completa. Sono inoltre presenti due
associazioni con attributi: comprende e sconto.
Alcune considerazioni riguardo la realizzazione delle classi POJO e il mapping.
Le associazioni con attributi verranno realizzare utilizzando la strategia spiegata nel paragrafo 2.5.6.6.
107
L’associazione comprende viene creata utilizzando la classe ArticoloAcquistato che contiene un
riferimento ad un’istanza di Acquisto, un riferimento ad un’istanza di Articolo e l’attributo
dell’associazione. Avendo la responsabilità su comprende, la classe Acquisto avrà un campo collezione di
riferimenti a istanze di ArticoloAcquistato. L’associazione sconto viene realizzata in modo analogo
all’associazione comprende, utilizzando la classe ScontoInfo.
E’ importante spiegare un aspetto del mapping di associazioni. Per poter utilizzare i metodi della classe
Acquisto, totale() e calcolaSconto(), che coinvolgono buona parte delle tabelle della base di
dati, è stato necessario mappare alcune collezioni con la proprietà fetch=FetchType.EAGER che
imposta la strategia di eager fetching, cioè caricamento in memoria immeditato dei dati delle collezioni.
Questa strategia peggiora molto le performance rispetto alla strategia di default lazy fetching, ma è utile
per notare il livello di isolamento che JPA può fornire dalla base di dati.
La gerarchia di classi composta da Articolo, Abbigliamento e Calzature è stata mappata con la
strategia una tabella per sottoclasse.
File Acquisto.java
package dominio;
import java.util.*;
import javax.persistence.*;
import dominio.calzature.Calzature;
import dominio.abbigliamento.Abbigliamento;
@Entity
@Table(name = "acquisto")
public class Acquisto {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id_acquisto")
private Long id;
@Temporal(TemporalType.DATE)
@Column(name = "data")
private Date data;
@OneToMany(mappedBy = "acquisto", fetch = FetchType.EAGER)
private Set<ArticoloAcquistato> comprende;
public Acquisto() {
this.data = new Date();
this.comprende = new HashSet();
}
public Long getId() {
return this.id;
}
private void setId(Long id) {
this.id = id;
}
public Date getData() {
return this.data;
}
108
private void setData(Date data) {
this.data = data;
}
public Set<ArticoloAcquistato> getComprende() throws EccezioneCardMin {
if(comprende.size() == 0)
throw new EccezioneCardMin("Cardinalità minima violata");
else
return (Set<ArticoloAcquistato>) comprende;
}
private void setComprende(Set<ArticoloAcquistato> comprende) {
this.comprende = comprende;
}
public void inserisciArticolo(ArticoloAcquistato articoloAcquistato) {
comprende.add(articoloAcquistato);
}
public double totale() throws EccezioneCardMin {
double result = 0;
Iterator<ArticoloAcquistato> it = comprende.iterator();
while(it.hasNext()) {
ArticoloAcquistato ai = (ArticoloAcquistato) it.next();
if(ai.getArticolo() instanceof Calzature) {
Calzature c = (Calzature) ai.getArticolo();
int sconto = this.calcolaSconto(c);
result += ((c.getPrezzo()*(100-sconto))/100)*ai.getQuantita();
}
else
result += ai.getArticolo().getPrezzo()*ai.getQuantita();
}
return result;
}
public int calcolaSconto(Calzature calzature) throws EccezioneCardMin {
int sconto = 0;
Iterator<ScontoInfo> it1 = calzature.getSconto().iterator();
while (it1.hasNext()) {
ScontoInfo si = (ScontoInfo) it1.next();
Abbigliamento a = si.getAbbigliamento();
Iterator<ArticoloAcquistato> it2 = comprende.iterator();
while (it2.hasNext()) {
ArticoloAcquistato aa = (ArticoloAcquistato) it2.next();
if (aa.getArticolo().equals(a)) {
int percentuale = si.getPercentuale();
if(percentuale > sconto)
sconto = percentuale;
}
}
}
return sconto;
}
}
109
File Articolo.java
package dominio.articolo;
import javax.persistence.*;
@Entity
@Table(name = "articolo")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Articolo {
@Id
@Column(name = "cod_articolo")
protected String codice;
@Column(name = "descrizione")
protected String descrizione;
@Column(name = "prezzo")
protected double prezzo;
protected Articolo() { }
public Articolo(String codice, String descrizione, double prezzo) {
this.codice = codice;
this.descrizione = descrizione;
this.prezzo = prezzo;
}
public String getCodice() {
return codice;
}
private void setCodice(String codice) {
this.codice = codice;
}
public String getDescrizione() {
return descrizione;
}
public void setDescrizione(String descrizione) {
this.descrizione = descrizione;
}
public double getPrezzo() {
return prezzo;
}
public void setPrezzo(double prezzo) {
this.prezzo = prezzo;
}
}
110
File Abbigliamento.java
package dominio.abbigliamento;
import java.util.*;
import javax.persistence.*;
import dominio.articolo.Articolo;
import dominio.EccezioneCardMin;
@Entity
public class Abbigliamento extends Articolo {
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "materiali",
joinColumns = @JoinColumn(name = "cod_abbigliamento"))
@Column(name = "materiale")
protected Set<String> materiali;
protected Abbigliamento() { }
public Abbigliamento(String codice, String descrizione, double prezzo) {
super(codice, descrizione, prezzo);
this.materiali = new HashSet();
}
public Set<String> getMateriali() throws EccezioneCardMin {
if(materiali.size() == 0)
throw new EccezioneCardMin("Cardinalità minima violata");
else
return materiali;
}
private void setMateriali(Set<String> materiali) {
this.materiali = materiali;
}
public void inserisciMateriali(String materiale) {
this.materiali.add(materiale);
}
public void eliminaMateriali(String materiale) {
this.materiali.remove(materiale);
}
}
File Calzature.java
package dominio.calzature;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import dominio.EccezioneCardMin;
import dominio.articolo.Articolo;
111
import dominio.ScontoInfo;
@Entity
public class Calzature extends Articolo {
@Column(name = "paese")
protected String paese;
@OneToMany(mappedBy = "calzature", fetch = FetchType.EAGER)
protected Set<ScontoInfo> sconto;
protected Calzature() { }
public Calzature(String codice, String descrizione,
double prezzo, String paese) {
super(codice, descrizione, prezzo);
this.paese = paese;
this.sconto = new HashSet();
}
public String getPaese() {
return paese;
}
public void setPaese(String paese) {
this.paese = paese;
}
public Set<ScontoInfo> getSconto() throws EccezioneCardMin {
if(sconto.size() == 0)
throw new EccezioneCardMin("Cardinalità minima violata");
else
return this.sconto;
}
private void setSconto(Set<ScontoInfo> sconto) {
this.sconto = sconto;
}
public void inserisciSconto(ScontoInfo scontoInfo) {
sconto.add(scontoInfo);
}
}
File ArticoloAcquistato.java
package dominio;
import javax.persistence.*;
import dominio.articolo.Articolo;
@Entity
@Table(name = "articolo_acquistato")
public class ArticoloAcquistato {
112
@Embeddable
public static class Id implements java.io.Serializable {
@Column(name = "id_acquisto")
private Long idAcquisto;
@Column(name = "cod_articolo")
private String codiceArticolo;
protected Id() { }
protected Id(Long idAcquisto, String codiceArticolo) {
this.idAcquisto = idAcquisto;
this.codiceArticolo = codiceArticolo;
}
public boolean equals(Object o) {
if (o != null && getClass().equals(this.getClass())) {
Id id = (Id) o;
return id.idAcquisto == idAcquisto
&& id.codiceArticolo == codiceArticolo;
} else {
return false;
}
}
public int hashCode() {
return idAcquisto.hashCode() + codiceArticolo.hashCode();
}
}
@EmbeddedId
private Id id = new Id();
@ManyToOne
@JoinColumn(name = "id_acquisto",
insertable = false,
updatable = false)
private Acquisto acquisto;
@ManyToOne
@JoinColumn(name = "cod_articolo",
insertable = false,
updatable = false)
private Articolo articolo;
@Column(name = "quantita")
private int quantita;
protected ArticoloAcquistato() { }
public ArticoloAcquistato(Acquisto acquisto,
Articolo articolo, int quantita)
throws EccezionePrecondizioni {
if(acquisto == null || articolo == null)
throw new EccezionePrecondizioni
("Gli oggetti devono essere inizializzati");
if(quantita <= 0)
throw new EccezionePrecondizioni
("La quantità deve essere positiva");
this.acquisto = acquisto;
this.articolo = articolo;
this.quantita = quantita;
113
this.id.idAcquisto = acquisto.getId();
this.id.codiceArticolo = articolo.getCodice();
acquisto.inserisciArticolo(this);
}
private Id getId() {
return this.id;
}
private void setId(Id id) {
this.id = id;
}
public Acquisto getAcquisto() {
return this.acquisto;
}
private void setAcquisto(Acquisto acquisto) {
this.acquisto = acquisto;
}
public Articolo getArticolo() {
return this.articolo;
}
private void setArticolo(Articolo articolo) {
this.articolo = articolo;
}
public int getQuantita() {
return this.quantita;
}
private void setQuantita(int quantita) {
this.quantita = quantita;
}
}
File ScontoInfo.java
package dominio;
import javax.persistence.*;
import dominio.calzature.Calzature;
import dominio.abbigliamento.Abbigliamento;
@Entity
@Table(name = "sconto_info")
public class ScontoInfo {
@Embeddable
public static class Id implements java.io.Serializable {
114
@Column(name =
private String
@Column(name =
private String
"cod_calzature")
codiceCalzature;
"cod_abbigliamento")
codiceAbbigliamento;
protected Id() { }
protected Id(String codiceCalzature, String codiceAbbigliamento) {
this.codiceCalzature = codiceCalzature;
this.codiceAbbigliamento = codiceAbbigliamento;
}
public boolean equals(Object o) {
if (o != null && getClass().equals(this.getClass())) {
Id id = (Id) o;
return id.codiceCalzature == codiceCalzature
&& id.codiceAbbigliamento == codiceAbbigliamento;
} else {
return false;
}
}
public int hashCode() {
return codiceCalzature.hashCode() + codiceAbbigliamento.hashCode();
}
}
@EmbeddedId
private Id id = new Id();
@ManyToOne
@JoinColumn(name = "cod_calzature",
insertable = false,
updatable = false)
private Calzature calzature;
@ManyToOne
@JoinColumn(name = "cod_abbigliamento",
insertable = false,
updatable = false)
private Abbigliamento abbigliamento;
@Column(name = "percentuale")
private int percentuale;
protected ScontoInfo() { }
public ScontoInfo(Calzature calzature,
Abbigliamento abbigliamento, int percentuale)
throws EccezionePrecondizioni {
if(calzature == null || abbigliamento == null)
throw new EccezionePrecondizioni
("L'oggetto deve essere inizializzato");
if(percentuale < 5 || percentuale > 80)
throw new EccezionePrecondizioni
("La percentuale di sconto deve essere compresa tra 5 e 80");
this.calzature = calzature;
this.abbigliamento = abbigliamento;
this.percentuale = percentuale;
this.id.codiceCalzature = calzature.getCodice();
this.id.codiceAbbigliamento = abbigliamento.getCodice();
115
calzature.inserisciSconto(this);
}
private Id getId() {
return this.id;
}
private void setId(Id id) {
this.id = id;
}
public Calzature getCalzature() {
return this.calzature;
}
private void setCalzature(Calzature calzature) {
this.calzature = calzature;
}
public Abbigliamento getAbbigliamento() {
return this.abbigliamento;
}
private void setAbbigliamento(Abbigliamento abbigliamento) {
this.abbigliamento = abbigliamento;
}
public int getPercentuale() {
return this.percentuale;
}
private void setPercentuale(int percentuale) {
this.percentuale = percentuale;
}
}
3.3 Eccezioni
Per la gestione delle eccezioni generate dalle classi di dominio vengono utilizzate due classi che estendono
Exception. EccezionePrecondizioni viene utilizzata per eccezioni riguardanti la violazione delle
precondizioni, mentre EccezioneCardMin viene utilizzata per le eccezioni generate in caso di
violazione dei vincoli di cardinalità minima.
File EccezionePrecondizioni.java
package dominio;
public class EccezionePrecondizioni extends Exception {
private String messaggio;
public EccezionePrecondizioni(String m) {
messaggio = m;
}
116
public EccezionePrecondizioni() {
messaggio = "Si e’ verificata una violazione delle precondizioni";
}
public String toString() {
return messaggio;
}
}
File EccezioneCardMin.java
package dominio;
public class EccezioneCardMin extends Exception {
private String messaggio;
public EccezioneCardMin(String m) {
messaggio = m;
}
public String toString() {
return messaggio;
}
}
3.4 Persistenza
Lo strato di persistenza viene configurato in fase di sviluppo e successivamente gestito dal software JPA.
Il file di configurazione di JPA, persistence.xml, contiene una persistence unit chiamata testPU
contenente i dati per la connessione ad una base di dati PostgreSQL. Il valore della proprietà
hibernate.hbm2ddl.auto viene impostato sul valore create in fase di sviluppo per la creazione
della base di dati e successivamente impostato su update per l’esecuzione dell’applicazione completa.
File persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="testPU">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- Not needed, Hibernate supports auto-detection in JSE
<class>dominio.Acquisto</class>
<class>dominio.ArticoloAcquistato</class>
<class>dominio.ScontoInfo</class>
<class>dominio.abbigliamento.Abbigliamento</class>
<class>dominio.articolo.Articolo</class>
117
<class>dominio.calzature.Calzature</class>
-->
<properties>
<property name="hibernate.archive.autodetection"
value="class, hbm"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.connection.driver_class"
value="org.postgresql.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:postgresql://localhost:5432/test"/>
<property name="hibernate.connection.username"
value="postgres"/>
<property name="hibernate.connection.password"
value="password"/>
<property name="hibernate.c3p0.min_size"
value="5"/>
<property name="hibernate.c3p0.max_size"
value="20"/>
<property name="hibernate.c3p0.timeout"
value="300"/>
<property name="hibernate.c3p0.max_statements"
value="50"/>
<property name="hibernate.c3p0.idle_test_period"
value="3000"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
Per l’interazione con la base di dati viene utilizzata una classe JPAUtil, analoga alla classe
HibernateUtil mostrata nel paragrafo 2.9. JPAUtil permette di utilizzare un’unica istanza di
EntityManagerFactory per creare istanze di EntityManager nelle attività atomiche.
File JPAUtil.java
package persistence;
import javax.persistence.*;
public class JPAUtil {
private static EntityManagerFactory entityManagerFactory = initJPAUtil();
private static EntityManagerFactory initJPAUtil(){
// Start EntityManagerFactory
return Persistence.createEntityManagerFactory("testPU");
}
public static EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
118
public static void shutdown() {
//Close caches and connection pools
getEntityManagerFactory().close();
}
}
Lo strato di persistenza è costituito da una base di dati PostgreSQL generata automaticamente dal software
JPA utilizzando le annotazioni inserite all’interno delle classi di dominio.
SQL output
CREATE TABLE acquisto
(
id_acquisto bigint NOT NULL,
data date,
CONSTRAINT acquisto_pkey PRIMARY KEY (id_acquisto )
);
CREATE TABLE articolo_acquistato
(
cod_articolo character varying(255) NOT NULL,
id_acquisto bigint NOT NULL,
quantita integer,
CONSTRAINT articolo_acquistato_pkey PRIMARY KEY (cod_articolo , id_acquisto ),
CONSTRAINT fk1522eb4254099cc9 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk1522eb42bbef361b FOREIGN KEY (id_acquisto)
REFERENCES acquisto (id_acquisto) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE articolo
(
cod_articolo character varying(255) NOT NULL,
descrizione character varying(255),
prezzo double precision,
CONSTRAINT articolo_pkey PRIMARY KEY (cod_articolo )
);
CREATE TABLE abbigliamento
(
cod_articolo character varying(255) NOT NULL,
CONSTRAINT abbigliamento_pkey PRIMARY KEY (cod_articolo ),
119
CONSTRAINT fk8b9d58ec54099cc9 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE calzature
(
paese character varying(255),
cod_articolo character varying(255) NOT NULL,
CONSTRAINT calzature_pkey PRIMARY KEY (cod_articolo ),
CONSTRAINT fk5118b14954099cc9 FOREIGN KEY (cod_articolo)
REFERENCES articolo (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE materiali
(
cod_abbigliamento character varying(255) NOT NULL,
materiale character varying(255),
CONSTRAINT fk28994022da5688ee FOREIGN KEY (cod_abbigliamento)
REFERENCES abbigliamento (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE sconto_info
(
cod_abbigliamento character varying(255) NOT NULL,
cod_calzature character varying(255) NOT NULL,
percentuale integer,
CONSTRAINT sconto_info_pkey PRIMARY KEY (cod_abbigliamento , cod_calzature ),
CONSTRAINT fk2b958e38a179445 FOREIGN KEY (cod_calzature)
REFERENCES calzature (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk2b958e3da5688ee FOREIGN KEY (cod_abbigliamento)
REFERENCES abbigliamento (cod_articolo) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
3.5 Attività
Le classi che realizzano le attività non presentano particolari costrutti.
In alcuni casi le informazioni utili alle attività vengono scambiate con istanze di classi RecordAcquisto,
RecordArticolo e RecordCalzature.
File RecordAcquisto.java
package attivita;
import java.util.Date;
120
public class RecordAcquisto {
private Long id;
private Date data;
public RecordAcquisto(Long id, Date data) {
this.id = id;
this.data = data;
}
public Long getId() {
return this.id;
}
public Date getData() {
return this.data;
}
}
File RecordArticolo.java
package attivita;
public class RecordArticolo {
private String codice;
private String categoria;
private int quantita;
public RecordArticolo(String codice, String categoria, int quantita) {
this.codice = codice;
this.categoria = categoria;
this.quantita = quantita;
}
public String getCodice() {
return this.codice;
}
public String getCategoria() {
return this.categoria;
}
public int getQuantita() {
return this.quantita;
}
}
File RecordCalzature.java
package attivita;
public class RecordCalzature {
121
private String codice;
private int sconto;
public RecordCalzature(String codice, int sconto) {
this.codice = codice;
this.sconto = sconto;
}
public String getCodice() {
return this.codice;
}
public int getSconto() {
return this.sconto;
}
}
122
3.5.1 AttivitàComplesse
La prima attività complessa è AttivitàInserisciAcquisto che permette di inserire nuovi acquisti
nella base di dati. Viene eseguita inizialmente SelezionaArticoli, che visualizza un’interfaccia grafica
per selezionare gli articoli da inserire nell’acquisto. Una volta premuto il bottone Calcola importo totale,
vengono letti gli articoli inseriti e viene creato il nuovo acquisto e inserito nella base di dati.
Successivamente viene calcolato l’importo totale dell’acquisto e l’insieme delle calzature inserite con lo
sconto applicato. Infine viene eseguito VisualizzaReportAcquisto che visualizza una finestra
contenete l’ID dell’acquisto, gli articoli inseriti con lo sconto applicato e l’importo totale.
Diagramma UML dell’attività AttivitaInserisciAcquisto:
File AttivitaInserisciAcquisto.java
package attivita.complesse;
import dominio.Acquisto;
import java.util.*;
123
import javax.swing.SwingUtilities;
import javax.swing.JOptionPane;
import java.lang.reflect.InvocationTargetException;
import
import
import
import
import
import
import
import
_framework.*;
attivita.atomiche.*;
attivita.*;
dominio.EccezioneCardMin;
dominio.abbigliamento.Abbigliamento;
dominio.calzature.Calzature;
gui.SelezionaArticoli;
gui.VisualizzaReportAcquisto;
public class AttivitaInserisciAcquisto implements Runnable {
private
private
private
private
private
private
private
private
boolean eseguita = false;
List<Abbigliamento> abbigliamento;
List<Calzature> calzature;
SelezionaArticoli selezionaArticoli = null;
Acquisto acquisto;
VisualizzaReportAcquisto visualizzaReport = null;
Set<RecordCalzature> calzatureAcquistate;
double totaleAcquisto;
public synchronized void run() {
if (eseguita) {
return;
}
eseguita = true;
CaricaArticoli caricaArticoli = new CaricaArticoli();
Executor.perform(caricaArticoli);
abbigliamento = caricaArticoli.getRisultatoAbbigliamento();
calzature = caricaArticoli.getRisultatoCalzature();
Runnable target1 = new Runnable() {
@Override
public void run() {
selezionaArticoli = new SelezionaArticoli(
abbigliamento, calzature);
}
};
try {
SwingUtilities.invokeAndWait(target1);
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
selezionaArticoli.aspettaOK();
List<RecordArticolo> listaArticoli = selezionaArticoli.leggiArticoli();
if (!listaArticoli.isEmpty()) {
124
CreaNuovoAcquisto creaNuovoAcquisto =
new CreaNuovoAcquisto(listaArticoli);
Executor.perform(creaNuovoAcquisto);
acquisto = creaNuovoAcquisto.getRisultato();
CalcolaImportoTotale calcolaTotale =
new CalcolaImportoTotale(acquisto);
Executor.perform(calcolaTotale);
totaleAcquisto = calcolaTotale.getRisultato();
CalcolaSconto calcolaSconto = new CalcolaSconto(acquisto);
Executor.perform(calcolaSconto);
calzatureAcquistate = calcolaSconto.getRisultato();
Runnable target2 = new Runnable() {
@Override
public void run() {
visualizzaReport =
new VisualizzaReportAcquisto(
acquisto.getId(), selezionaArticoli.leggiTabella(),
calzatureAcquistate, totaleAcquisto);
}
};
SwingUtilities.invokeLater(target2);
}
else {
JOptionPane.showMessageDialog(
null, "ERRORE: E' necessario inserire "
+ "almeno un articolo per creare un nuovo acquisto");
}
}
public synchronized boolean estEseguita() {
return eseguita;
}
}
La seconda attività complessa è AttivitaVisualizzaAcquisti. Questa attività esegue l’attivita
atomica CaricaAcquisti che restituisce una lista di RecordArticolo che vengono utilizzati
successivamente per costruire la tabella contenuta nell’interfaccia grafica VisualizzaAcquisti.
125
Diagramma UML dell’attività AttivitaVisualizzaAcquisti:
File AttivitaVisualizzaAcquisti.java
package attivita.complesse;
import java.util.*;
import
import
import
import
_framework.*;
attivita.*;
attivita.atomiche.*;
gui.VisualizzaAcquisti;
public class AttivitaVisualizzaAcquisti implements Runnable {
private boolean eseguita = false;
private List<RecordAcquisto> acquisti;
public synchronized void run() {
if (eseguita)
return;
eseguita = true;
CaricaAcquisti caricaAcquisti = new CaricaAcquisti();
Executor.perform(caricaAcquisti);
acquisti = caricaAcquisti.getRisultato();
VisualizzaAcquisti finestra = new VisualizzaAcquisti(acquisti);
}
public synchronized boolean estEseguita() {
return eseguita;
}
}
126
L’attività AttivitaEffettuaControlli è composta da una prima parte simile ad
AttivitaVisualizzaAcquisti, vengono caricate dalla base di dati le informazioni di tutti gli
acquisti per costruire una tabella visualizzata da SelezionaAcquisto. Dopo aver selezionato un
Acquisto dalla tabella e premuto il bottone Inizia controlli, viene eseguita inizialmente l’attività atomica
VerificaImporto che controlla che l’importo totale dell’acquisto sia corretto. Successivamente se
VerificaImporto ha avuto esito positivo, vengono eseguite in concorrenza le attività
SottoramoAbbigliamento e SottoramoCalzature per calcolare l’insieme di materiali utilizzati
per i capi di abbigliamento e le calzature per le quali è stato effettuato uno sconto.
Diagramma UML dell’attività AttivitaEffettuaControlli:
127
File AttivitaEffettuaControlli.java
package attivita.complesse;
import
import
import
import
java.util.*;
javax.swing.SwingUtilities;
javax.swing.JOptionPane;
java.lang.reflect.InvocationTargetException;
import
import
import
import
import
import
_framework.Executor;
attivita.*;
attivita.atomiche.*;
dominio.Acquisto;
gui.SelezionaAcquisto;
gui.VisualizzaRisultatoControllo;
public class AttivitaEffettuaControlli implements Runnable {
private
private
private
private
private
boolean eseguita = false;
List<RecordAcquisto> acquisti;
Object[] calzatureScontate;
Object[] materiali;
SelezionaAcquisto selezionaAcquisto = null;
public synchronized void run() {
if (eseguita) {
return;
}
eseguita = true;
CaricaAcquisti caricaAcquisti = new CaricaAcquisti();
Executor.perform(caricaAcquisti);
acquisti = caricaAcquisti.getRisultato();
Runnable target1 = new Runnable() {
@Override
public void run() {
selezionaAcquisto = new SelezionaAcquisto(acquisti);
}
};
try {
SwingUtilities.invokeAndWait(target1);
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
selezionaAcquisto.aspettaOK();
Long idAcquisto = selezionaAcquisto.leggiIdAcquisto();
if (idAcquisto != null) {
CaricaAcquisto caricaAcquisto =
new CaricaAcquisto(idAcquisto.longValue());
Executor.perform(caricaAcquisto);
128
Acquisto acquisto = caricaAcquisto.getRisultato();
VerificaImporto verificaImporto = new VerificaImporto(acquisto);
Executor.perform(verificaImporto);
boolean importoCorretto = verificaImporto.getRisultato();
if (importoCorretto == true) {
SottoramoCalzature sottoramoCalzature =
new SottoramoCalzature(acquisto);
SottoramoAbbigliamento sottoramoAbbigliamento =
new SottoramoAbbigliamento(acquisto);
Thread t1 = new Thread(sottoramoCalzature);
Thread t2 = new Thread(sottoramoAbbigliamento);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
calzatureScontate = sottoramoCalzature.getRisultato();
materiali = sottoramoAbbigliamento.getRisultato();
VisualizzaRisultatoControllo visualizzarisultato =
new VisualizzaRisultatoControllo(
calzatureScontate, materiali);
} else {
JOptionPane.showMessageDialog(
null, "ERRORE: Verifica dell'importo "
+ "totale non superata");
}
} else {
JOptionPane.showMessageDialog(
null, "ERRORE: E' necessario selezionare "
+ "un acquisto per effettuare i controlli");
}
}
public synchronized boolean estEseguita() {
return eseguita;
}
}
129
File SottoramoAbbigliamento.java
package attivita.complesse;
import dominio.Acquisto;
import javax.persistence.*;
import java.util.*;
import
import
import
import
_framework.*;
attivita.atomiche.RestituisciMaterialiAbbigliamento;
dominio.abbigliamento.Abbigliamento;
persistence.JPAUtil;
public class SottoramoAbbigliamento implements Runnable {
private boolean eseguita = false;
private Acquisto acquisto;
private Object[] materiali;
public SottoramoAbbigliamento(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void run() {
if (eseguita) {
return;
}
eseguita = true;
RestituisciMaterialiAbbigliamento restituisciMateriali =
new RestituisciMaterialiAbbigliamento(acquisto);
Executor.perform(restituisciMateriali);
materiali = restituisciMateriali.getRisultato();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Object[] getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return materiali;
}
}
File SottoramoCalzature.java
package attivita.complesse;
import dominio.Acquisto;
import javax.persistence.*;
130
import java.util.*;
import
import
import
import
_framework.*;
attivita.RecordCalzature;
attivita.atomiche.RestituisciCalzatureScontate;
persistence.JPAUtil;
public class SottoramoCalzature implements Runnable {
private boolean eseguita = false;
private Acquisto acquisto;
private Object[] calzatureScontate;
public SottoramoCalzature(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void run() {
if (eseguita) {
return;
}
eseguita = true;
RestituisciCalzatureScontate restituisciCalzature =
new RestituisciCalzatureScontate(acquisto);
Executor.perform(restituisciCalzature);
calzatureScontate = restituisciCalzature.getRisultato();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Object[] getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return calzatureScontate;
}
}
3.5.2 Attività atomiche
Per la realizzazione delle attività atomiche viene utilizzato il pattern funtore.
Le attività atomiche implementano l’interfaccia Task e i task vengono eseguiti dalla classe Executor.
131
File Task.java
package _framework;
public interface Task {
public void esegui(Executor e);
}
File Executor.java
package _framework;
public class Executor {
private Executor() {
}
public synchronized static void perform(Task t) {
t.esegui(new Executor());
}
}
Le attività atomiche che interagiscono con la base di dati ricevono l’istanza di EntityManagerFactory
gestita da JPAUtil per creare una istanza di EntityManager e successivamente iniziare transazioni
per la comunicazione. Le altre attività atomiche non presentano soluzioni particolari.
File CaricaArticoli.java
package attivita.atomiche;
import
import
import
import
dominio.calzature.Calzature;
dominio.abbigliamento.Abbigliamento;
java.util.*;
javax.persistence.*;
import _framework.*;
import persistence.JPAUtil;
import attivita.*;
public class CaricaArticoli implements Task {
private boolean eseguita = false;
private LinkedList<Abbigliamento> listaAbbigliamento =
new LinkedList<Abbigliamento>();
private LinkedList<Calzature> listaCalzature =
new LinkedList<Calzature>();
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
132
}
eseguita = true;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
List abbigliamento = em.createNativeQuery(
"SELECT * "
+ "FROM articolo, abbigliamento "
+ "WHERE articolo.cod_articolo = abbigliamento.cod_articolo;",
Abbigliamento.class).getResultList();
List calzature = em.createNativeQuery(
"SELECT * "
+ "FROM articolo, calzature "
+ "WHERE articolo.cod_articolo = calzature.cod_articolo;",
Calzature.class).getResultList();
tx.commit();
em.close();
Iterator it1 = abbigliamento.iterator();
while (it1.hasNext()) {
Abbigliamento a = (Abbigliamento) it1.next();
listaAbbigliamento.add(a);
}
Iterator it2 = calzature.iterator();
while (it2.hasNext()) {
Calzature c = (Calzature) it2.next();
listaCalzature.add(c);
}
}
public synchronized boolean estEseguita() {
return eseguita;
}
public List<Abbigliamento> getRisultatoAbbigliamento() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return (List<Abbigliamento>) listaAbbigliamento.clone();
}
public List<Calzature> getRisultatoCalzature() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return (List<Calzature>) listaCalzature.clone();
}
}
133
File CreaNuovoAcquisto.java
package attivita.atomiche;
import
import
import
import
dominio.Acquisto;
dominio.EccezionePrecondizioni;
dominio.ArticoloAcquistato;
java.util.*;
import
import
import
import
import
import
import
_framework.*;
javax.persistence.*;
persistence.JPAUtil;
attivita.RecordArticolo;
dominio.abbigliamento.Abbigliamento;
dominio.calzature.Calzature;
javax.swing.JOptionPane;
public class CreaNuovoAcquisto implements Task {
private boolean eseguita = false;
private List<RecordArticolo> listaArticoli;
private Acquisto nuovoAcquisto;
public CreaNuovoAcquisto(List<RecordArticolo> listaArticoli) {
this.listaArticoli = listaArticoli;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
nuovoAcquisto = new Acquisto();
em.persist(nuovoAcquisto);
Iterator<RecordArticolo> it = listaArticoli.iterator();
while(it.hasNext()) {
RecordArticolo ra = (RecordArticolo) it.next();
String codice = ra.getCodice();
String categoria = ra.getCategoria();
int quantita = ra.getQuantita();
if(categoria.equals("Abbigliamento")) {
Abbigliamento abbigliamento = (Abbigliamento)
em.getReference(Abbigliamento.class, codice);
ArticoloAcquistato articoloAcquistato = null;
try {
articoloAcquistato = new ArticoloAcquistato(
nuovoAcquisto, abbigliamento, quantita);
} catch(EccezionePrecondizioni ex) {
134
JOptionPane.showMessageDialog(null, ex.getMessage());
}
if(articoloAcquistato != null) {
em.persist(articoloAcquistato);
}
}
else if(categoria.equals("Calzature")) {
Calzature calzature = (Calzature)
em.getReference(Calzature.class, codice);
ArticoloAcquistato articoloAcquistato = null;
try {
articoloAcquistato = new ArticoloAcquistato(
nuovoAcquisto, calzature, quantita);
} catch(EccezionePrecondizioni ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
if(articoloAcquistato != null) {
em.persist(articoloAcquistato);
}
}
}
tx.commit();
em.close();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Acquisto getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return this.nuovoAcquisto;
}
}
File CalcolaImportoTotale.java
package attivita.atomiche;
import java.util.*;
import javax.swing.JOptionPane;
import _framework.*;
import dominio.Acquisto;
import dominio.EccezioneCardMin;
public class CalcolaImportoTotale implements Task {
private boolean eseguita = false;
private Acquisto acquisto;
private double totale;
135
public CalcolaImportoTotale(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
try {
totale = acquisto.totale();
} catch (EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
}
public synchronized boolean estEseguita() {
return eseguita;
}
public double getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return totale;
}
}
File CalcolaSconto.java
package attivita.atomiche;
import
import
import
import
import
import
dominio.Acquisto;
dominio.EccezioneCardMin;
dominio.ArticoloAcquistato;
java.util.*;
javax.persistence.*;
javax.swing.JOptionPane;
import
import
import
import
_framework.*;
attivita.RecordCalzature;
dominio.calzature.Calzature;
persistence.JPAUtil;
public class CalcolaSconto implements Task {
private boolean eseguita = false;
private Acquisto acquisto;
private HashSet<RecordCalzature> insiemeCalzature;
public CalcolaSconto(Acquisto acquisto) {
this.acquisto = acquisto;
136
this.insiemeCalzature = new HashSet<RecordCalzature>();
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
long idAcquisto = acquisto.getId();
Set<ArticoloAcquistato> articoli = null;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
articoli = (Set<ArticoloAcquistato>) acquisto.getComprende();
}
catch(EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
Iterator<ArticoloAcquistato> it = articoli.iterator();
while(it.hasNext()) {
ArticoloAcquistato aa = (ArticoloAcquistato) it.next();
if(aa.getArticolo() instanceof Calzature) {
Calzature c = (Calzature) aa.getArticolo();
String codice = c.getCodice();
int sconto = 0;
try {
sconto = acquisto.calcolaSconto(c);
}
catch(EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
RecordCalzature rc = new RecordCalzature(codice, sconto);
insiemeCalzature.add(rc);
}
}
tx.commit();
em.close();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Set<RecordCalzature> getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return (Set<RecordCalzature>) this.insiemeCalzature.clone();
137
}
}
File CaricaAcquisti.java
package attivita.atomiche;
import java.util.*;
import javax.persistence.*;
import
import
import
import
_framework.*;
persistence.JPAUtil;
attivita.RecordAcquisto;
dominio.Acquisto;
public class CaricaAcquisti implements Task {
private boolean eseguita = false;
private LinkedList<RecordAcquisto> listaAcquisti =
new LinkedList<RecordAcquisto>();
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
List acquisti = em.createNativeQuery(
"SELECT id_acquisto, data "
+ "FROM acquisto "
+ "ORDER BY id_acquisto;",
Acquisto.class).getResultList();
tx.commit();
em.close();
Iterator it = acquisti.iterator();
while (it.hasNext()) {
Acquisto ac = (Acquisto) it.next();
Long id = ac.getId();
Date data = ac.getData();
RecordAcquisto ra = new RecordAcquisto(id, data);
listaAcquisti.add(ra);
}
}
public synchronized boolean estEseguita() {
return eseguita;
138
}
public List<RecordAcquisto> getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return (List<RecordAcquisto>) listaAcquisti.clone();
}
}
File CaricaAcquisto.java
package attivita.atomiche;
import javax.persistence.*;
import _framework.*;
import dominio.Acquisto;
import persistence.JPAUtil;
public class CaricaAcquisto implements Task {
public boolean eseguita;
private long idAcquisto;
private Acquisto acquisto;
public CaricaAcquisto(long idAcquisto) {
this.idAcquisto = idAcquisto;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
acquisto = em.find(Acquisto.class, idAcquisto);
tx.commit();
em.close();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Acquisto getRisultato() {
if (!eseguita) {
139
throw new RuntimeException("Risultato non pronto!");
}
return this.acquisto;
}
}
File VerificaImporto.java
package attivita.atomiche;
import
import
import
import
import
import
dominio.Acquisto;
dominio.EccezioneCardMin;
dominio.ArticoloAcquistato;
java.util.*;
javax.persistence.*;
javax.swing.JOptionPane;
import _framework.*;
import persistence.JPAUtil;
public class VerificaImporto implements Task {
public boolean eseguita;
private Acquisto acquisto;
private boolean importoCorretto;
public VerificaImporto(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
Set<ArticoloAcquistato> articoli = null;
double totale = 0.0;
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
articoli = (Set<ArticoloAcquistato>) acquisto.getComprende();
}
catch(EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
try {
totale = acquisto.totale();
140
} catch (EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
double somma = 0.0;
Iterator<ArticoloAcquistato> it = articoli.iterator();
while(it.hasNext()) {
ArticoloAcquistato aa = (ArticoloAcquistato) it.next();
double prezzo = aa.getArticolo().getPrezzo();
somma += prezzo*aa.getQuantita();
}
tx.commit();
em.close();
if(somma >= totale) {
importoCorretto = true;
}
else
importoCorretto = false;
}
public synchronized boolean estEseguita() {
return eseguita;
}
public boolean getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return importoCorretto;
}
}
File RestituisciCalzatureScontate.java
package attivita.atomiche;
import
import
import
import
import
import
dominio.Acquisto;
dominio.EccezioneCardMin;
dominio.ArticoloAcquistato;
java.util.*;
javax.persistence.*;
javax.swing.JOptionPane;
import _framework.*;
import dominio.calzature.Calzature;
import persistence.JPAUtil;
public class RestituisciCalzatureScontate implements Task {
private boolean eseguita = false;
private Acquisto acquisto;
private Object[] calzatureScontate;
141
public RestituisciCalzatureScontate(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
Set<ArticoloAcquistato> articoli = null;
HashSet<String> appoggio = new HashSet<String>();
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
articoli = (Set<ArticoloAcquistato>) acquisto.getComprende();
} catch (EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
Iterator<ArticoloAcquistato> it = articoli.iterator();
while (it.hasNext()) {
ArticoloAcquistato aa = (ArticoloAcquistato) it.next();
if (aa.getArticolo() instanceof Calzature) {
Calzature c = (Calzature) aa.getArticolo();
int sconto = 0;
try {
sconto = acquisto.calcolaSconto(c);
} catch (EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
if (sconto > 0) {
appoggio.add(c.getCodice());
}
}
}
tx.commit();
em.close();
calzatureScontate = appoggio.toArray();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Object[] getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
142
return calzatureScontate;
}
}
File RestituisciMaterialiAbbigliamento.java
package attivita.atomiche;
import
import
import
import
import
import
dominio.Acquisto;
dominio.EccezioneCardMin;
dominio.ArticoloAcquistato;
java.util.*;
javax.swing.JOptionPane;
javax.persistence.*;
import _framework.*;
import dominio.abbigliamento.Abbigliamento;
import persistence.JPAUtil;
public class RestituisciMaterialiAbbigliamento implements Task {
private boolean eseguita = false;
private Acquisto acquisto;
private Object[] materiali;
public RestituisciMaterialiAbbigliamento(Acquisto acquisto) {
this.acquisto = acquisto;
}
public synchronized void esegui(Executor e) {
if (eseguita || e == null) {
return;
}
eseguita = true;
Set<ArticoloAcquistato> articoli = null;
HashSet<String> appoggio = new HashSet<String>();
EntityManagerFactory emf = JPAUtil.getEntityManagerFactory();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
articoli = (Set<ArticoloAcquistato>) acquisto.getComprende();
}
catch(EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
Iterator<ArticoloAcquistato> it1 = articoli.iterator();
while(it1.hasNext()) {
ArticoloAcquistato aa = it1.next();
143
if(aa.getArticolo() instanceof Abbigliamento) {
Abbigliamento a = (Abbigliamento) aa.getArticolo();
Set<String> mat = null;
try {
mat = a.getMateriali();
}
catch(EccezioneCardMin ex) {
JOptionPane.showMessageDialog(null, ex.getMessage());
}
Iterator<String> it2 = mat.iterator();
while(it2.hasNext()) {
String materiale = it2.next();
appoggio.add(materiale);
}
}
}
tx.commit();
em.close();
materiali = appoggio.toArray();
}
public synchronized boolean estEseguita() {
return eseguita;
}
public Object[] getRisultato() {
if (!eseguita) {
throw new RuntimeException("Risultato non pronto!");
}
return materiali;
}
}
3.6 Interfaccia grafica
L’interfaccia grafica dell’applicazione è costruita utilizzando il framework Java Swing.
FinestraPrincipale viene eseguita all’avvio dell’applicazione e visualizza una finestra con il menù
principale composto da tre bottoni che permettono di selezionare l’attività da eseguire.
Interaccia grafica FinestraPrincipale:
144
File FinestraPrincipale.java
package gui;
import java.awt.GridLayout;
import javax.swing.*;
import java.util.*;
import attivita.*;
public class FinestraPrincipale extends JFrame {
private
private
private
private
final
final
final
final
JPanel jPanel1;
JButton jButton1 = new JButton("Inserisci nuovo acquisto");
JButton jButton2 = new JButton("Visualizza acquisti");
JButton jButton3 = new JButton("Effettua controlli");
public FinestraPrincipale() {
super("Test JPA - Menù Principale");
jPanel1 = new JPanel(new GridLayout(3, 1));
jPanel1.add(jButton1);
jPanel1.add(jButton2);
jPanel1.add(jButton3);
FinestraPrincipaleListener listener = new FinestraPrincipaleListener();
jButton1.addActionListener(listener);
jButton1.setActionCommand(listener.jButton1);
jButton2.addActionListener(listener);
jButton2.setActionCommand(listener.jButton2);
jButton3.addActionListener(listener);
jButton3.setActionCommand(listener.jButton3);
this.getContentPane().add(jPanel1);
this.setSize(350, 150);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
}
File FinestraPrincipaleListener.java
package gui;
import java.awt.event.*;
import javax.swing.*;
import attivita.complesse.*;
public class FinestraPrincipaleListener implements ActionListener {
final static String jButton1 = "Inserisci nuovo acquisto";
145
final static String jButton2 = "Visualizza acquisti";
final static String jButton3 = "Effettua controlli";
public void actionPerformed(ActionEvent ev) {
String com = ev.getActionCommand();
if(com == jButton1) {
inserisciAcquisto();
}
else if(com == jButton2) {
visualizzaAcquisti();
}
else if(com == jButton3) {
effettuaControlli();
}
}
private void visualizzaAcquisti() {
AttivitaVisualizzaAcquisti visualizza = new
AttivitaVisualizzaAcquisti();
Thread t = new Thread(visualizza);
t.start();
}
private void inserisciAcquisto() {
AttivitaInserisciAcquisto inserisci = new AttivitaInserisciAcquisto();
Thread t = new Thread(inserisci);
t.start();
}
private void effettuaControlli() {
AttivitaEffettuaControlli controlli = new AttivitaEffettuaControlli();
Thread t = new Thread(controlli);
t.start();
}
}
La finestra SelezionaArticoli viene utilizzata nell’attività AttivitaInserisciAcquisto per
selezionare gli articoli da inserire nel nuovo acquisto. I dati vengono visualizzati utilizzando componenti
JTable.
E’ presente un metodo leggiArticoli() che resituisce una lista di RecordArticolo costruita dalla
tabella degli articoli inseriti, da utilizzare per la creazione di un nuovo acquisto e un metodo
aspettaOK() che mette l’attività AttivitaInserisciAcquisto in attesa che l’utente prema il
bottone Calcola importo totale. Dopo la pressione del bottone il listener eseguirà il metodo notify() per
permettere all’attività di continuare la sua esecuzione e leggere la lista di RecordArticolo.
La classe listener si occupa di inserire i dati nella tabella degli articoli inseriti.
146
Interaccia grafica SelezionaArticoli:
File SelezionaArticoli.java
package gui;
import
import
import
import
import
import
dominio.calzature.Calzature;
dominio.abbigliamento.Abbigliamento;
java.awt.*;
java.util.*;
javax.swing.*;
attivita.*;
public class SelezionaArticoli extends JFrame {
private final JButton jButton1
private final JButton jButton2
private final JButton jButton3
private final JButton jButton4
private final JLabel jLabel1 =
private final JLabel jLabel2 =
JTable jTable1 = new JTable();
JTable jTable2 = new JTable();
JTable jTable3 = new JTable();
private final JPanel jPanel1;
= new JButton("Inserisci");
= new JButton("Inserisci");
= new JButton("Rimuovi");
= new JButton("Calcola importo totale");
new JLabel("Lista articoli");
new JLabel("Articoli inseriti");
147
private
private
private
private
private
private
private
private
private
private
private
private
private
private
final
final
final
final
final
final
final
final
final
final
final
final
final
final
JPanel jPanel2;
JPanel jPanel3;
JPanel jPanel4;
JPanel jPanel5;
JPanel jPanel6;
JPanel jPanel7;
JPanel jPanel8;
JPanel jPanel9;
JPanel jPanel10;
JPanel jPanel11;
JPanel jPanel12;
JScrollPane jScrollPane1;
JScrollPane jScrollPane2;
JScrollPane jScrollPane3;
public SelezionaArticoli(java.util.List<Abbigliamento> listaAbbigliamento,
java.util.List<Calzature> listaCalzature) {
super("Test JPA - Inserisci nuovo acquisto");
jPanel1 = new JPanel(new GridLayout(1, 2, 10, 10));
jPanel1.setBorder(
BorderFactory.createTitledBorder("Inserisci acquisto"));
jPanel2 = new JPanel();
jPanel2.setLayout(new BoxLayout(jPanel2, BoxLayout.Y_AXIS));
jPanel2.setBorder(BorderFactory.createLineBorder(Color.black));
jPanel3 = new JPanel();
jPanel3.setLayout(new BoxLayout(jPanel3, BoxLayout.Y_AXIS));
jPanel4 = new JPanel();
jPanel4.setLayout(new BoxLayout(jPanel4, BoxLayout.Y_AXIS));
jPanel4.setBorder(
BorderFactory.createTitledBorder("Abbigliamento"));
jPanel5 = new JPanel();
jPanel5.setLayout(new BoxLayout(jPanel5, BoxLayout.Y_AXIS));
jPanel5.setBorder(
BorderFactory.createTitledBorder("Calzature"));
jPanel6 = new JPanel();
jPanel6.setLayout(new BoxLayout(jPanel6, BoxLayout.Y_AXIS));
jPanel6.setBorder(BorderFactory.createLineBorder(Color.black));
jPanel7 = new JPanel(new FlowLayout());
jPanel8 = new JPanel(new FlowLayout());
jPanel9 = new JPanel(new FlowLayout());
jPanel10 = new JPanel(new FlowLayout());
jPanel11 = new JPanel(new FlowLayout());
jPanel12 = new JPanel(new FlowLayout());
Object[][] tabellaAbbigliamento =
new Object[listaAbbigliamento.size()][3];
Iterator<Abbigliamento> it1 = listaAbbigliamento.iterator();
int i = 0;
while (it1.hasNext()) {
Abbigliamento a = (Abbigliamento) it1.next();
tabellaAbbigliamento[i][0] = a.getCodice();
tabellaAbbigliamento[i][1] = a.getDescrizione();
tabellaAbbigliamento[i][2] = (Double) a.getPrezzo();
i = i + 1;
}
148
jTable1.setModel(new javax.swing.table.DefaultTableModel(
tabellaAbbigliamento,
new String[]{
"Codice articolo", "Descrizione", "Prezzo"
}) {
Class[] types = new Class[]{
java.lang.String.class, java.lang.String.class,
java.lang.Double.class
};
boolean[] canEdit = new boolean[]{
false, false, false
};
public Class getColumnClass(int columnIndex) {
return types[columnIndex];
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit[columnIndex];
}
});
Object[][] tabellaCalzature = new Object[listaCalzature.size()][3];
Iterator<Calzature> it2 = listaCalzature.iterator();
int j = 0;
while (it2.hasNext()) {
Calzature c = (Calzature) it2.next();
tabellaCalzature[j][0] = c.getCodice();
tabellaCalzature[j][1] = c.getDescrizione();
tabellaCalzature[j][2] = (Double) c.getPrezzo();
j = j + 1;
}
jTable2.setModel(new javax.swing.table.DefaultTableModel(
tabellaCalzature,
new String[]{
"Codice articolo", "Descrizione", "Prezzo"
}) {
Class[] types = new Class[]{
java.lang.String.class, java.lang.String.class,
java.lang.Double.class
};
boolean[] canEdit = new boolean[]{
false, false, false
};
public Class getColumnClass(int columnIndex) {
return types[columnIndex];
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit[columnIndex];
}
});
149
jTable3.setModel(new javax.swing.table.DefaultTableModel(
new Object[][]{},
new String[]{
"Codice articolo", "Categoria",
"Descrizione", "Prezzo", "Quantità"
}) {
Class[] types = new Class[]{
java.lang.String.class, java.lang.String.class,
java.lang.String.class, java.lang.Double.class,
java.lang.Integer.class
};
boolean[] canEdit = new boolean[]{
false, false, false, false, true, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit[columnIndex];
}
});
jScrollPane1 = new JScrollPane(jTable1);
jTable1.setFillsViewportHeight(true);
jScrollPane2 = new JScrollPane(jTable2);
jTable2.setFillsViewportHeight(true);
jScrollPane3 = new JScrollPane(jTable3);
jTable3.setFillsViewportHeight(true);
jPanel12.add(jLabel2);
jPanel11.add(jLabel1);
jPanel10.add(jButton4);
jPanel9.add(jButton3);
jPanel8.add(jButton2);
jPanel7.add(jButton1);
jPanel6.add(jPanel12);
jPanel6.add(jScrollPane3);
jPanel6.add(jPanel9);
jPanel4.add(jScrollPane1);
jPanel4.add(jPanel7);
jPanel5.add(jScrollPane2);
jPanel5.add(jPanel8);
jPanel3.add(jPanel6);
jPanel3.add(jPanel10);
jPanel2.add(jPanel11);
jPanel2.add(jPanel4);
jPanel2.add(jPanel5);
jPanel1.add(jPanel2);
jPanel1.add(jPanel3);
SelezionaArticoliListener listener =
new SelezionaArticoliListener(this);
jButton1.addActionListener(listener);
150
jButton1.setActionCommand(listener.jButton1);
jButton2.addActionListener(listener);
jButton2.setActionCommand(listener.jButton2);
jButton3.addActionListener(listener);
jButton3.setActionCommand(listener.jButton3);
jButton4.addActionListener(listener);
jButton4.setActionCommand(listener.jButton4);
this.getContentPane().add(jPanel1);
this.setSize(1000, 700);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
public java.util.List<RecordArticolo> leggiArticoli() {
LinkedList<RecordArticolo> listaArticoli =
new LinkedList<RecordArticolo>();
for (int i=0; i<jTable3.getRowCount(); i++) {
String codice = (String) jTable3.getValueAt(i, 0);
String categoria = (String) jTable3.getValueAt(i, 1);
Integer quantita = (Integer) jTable3.getValueAt(i, 4);
RecordArticolo ra = new RecordArticolo(codice, categoria, quantita);
listaArticoli.add(ra);
}
return listaArticoli;
}
public javax.swing.JTable leggiTabella() {
return this.jTable3;
}
public void aspettaOK() {
synchronized (getContentPane()) {
try {
getContentPane().wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
File SelezionaArticoliListener.java
package gui;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
public class SelezionaArticoliListener implements ActionListener {
151
private SelezionaArticoli frame;
final static String jButton1 = "Inserisci1";
final static String jButton2 = "Inserisci2";
final static String jButton3 = "Rimuovi";
final static String jButton4 = "Calcola totale";
public SelezionaArticoliListener(SelezionaArticoli frame) {
this.frame = frame;
}
public void actionPerformed(ActionEvent ev) {
String com = ev.getActionCommand();
if (com == jButton1) {
inserisciAbbigliamento();
} else if (com == jButton2) {
inserisciCalzature();
} else if (com == jButton3) {
rimuovi();
} else if (com == jButton4) {
calcolaTotale();
}
}
public void inserisciAbbigliamento() {
int[] righeSelezionate = frame.jTable1.getSelectedRows();
javax.swing.table.DefaultTableModel model =
(javax.swing.table.DefaultTableModel) frame.jTable3.getModel();
for (int i=0; i<righeSelezionate.length; i++) {
boolean inserito = false;
for(int j=0; j<frame.jTable3.getRowCount(); j++) {
if(frame.jTable3.getValueAt(j, 0) ==
frame.jTable1.getValueAt(righeSelezionate[i], 0)) {
Integer quantita = (Integer) frame.jTable3.getValueAt(j, 4);
int n = quantita.intValue() + 1;
frame.jTable3.setValueAt(new Integer(n), j, 4);
inserito = true;
}
}
if(inserito == false) {
String codice = (String) frame.jTable1.getValueAt(
righeSelezionate[i], 0);
String categoria = "Abbigliamento";
String descrizione = (String) frame.jTable1.getValueAt(
righeSelezionate[i], 1);
Double prezzo = (Double) frame.jTable1.getValueAt(
righeSelezionate[i], 2);
model.addRow(new Object[]{
codice, categoria, descrizione, prezzo, 1});
}
}
}
public void inserisciCalzature() {
int[] righeSelezionate = frame.jTable2.getSelectedRows();
javax.swing.table.DefaultTableModel model =
(javax.swing.table.DefaultTableModel) frame.jTable3.getModel();
for (int i=0; i<righeSelezionate.length; i++) {
152
boolean inserito = false;
for(int j=0; j<frame.jTable3.getRowCount(); j++) {
if(frame.jTable3.getValueAt(j, 0) ==
frame.jTable2.getValueAt(righeSelezionate[i], 0)) {
Integer quantita = (Integer) frame.jTable3.getValueAt(j, 4);
int n = quantita.intValue() + 1;
frame.jTable3.setValueAt(new Integer(n), j, 4);
inserito = true;
}
}
if(inserito == false) {
String codice = (String) frame.jTable2.getValueAt(
righeSelezionate[i], 0);
String categoria = "Calzature";
String descrizione = (String) frame.jTable2.getValueAt(
righeSelezionate[i], 1);
Double prezzo = (Double) frame.jTable2.getValueAt(
righeSelezionate[i], 2);
model.addRow(new Object[]{
codice, categoria, descrizione, prezzo, 1});
}
}
}
public void rimuovi() {
int[] righeSelezionate = frame.jTable3.getSelectedRows();
if(righeSelezionate.length==0)
return;
javax.swing.table.DefaultTableModel model =
(javax.swing.table.DefaultTableModel) frame.jTable3.getModel();
for (int i=righeSelezionate.length-1; i>=0; i--) {
model.removeRow(righeSelezionate[i]);
}
}
public void calcolaTotale() {
synchronized (frame.getContentPane()) {
frame.getContentPane().notify();
frame.dispose();
}
}
}
153
VisualizzaReportAcquisto è la finestra che visualizza i dati dell’acquisto inserito nella base di dati.
Vengono visualizzati l’ID dell’acquisto, una tabella contenente gli articoli inseriti e l’importo totale.
Interaccia grafica VisualizzaReportArticolo:
File VisualizzaReportAcquisto.java
package gui;
import
import
import
import
import
javax.swing.*;
javax.swing.table.TableColumn;
java.awt.FlowLayout;
java.awt.GridLayout;
java.util.*;
import attivita.RecordArticolo;
import attivita.RecordCalzature;
import gui.VisualizzaReportAcquistoListener;
public class VisualizzaReportAcquisto extends JFrame {
private final JLabel jLabel1 = new JLabel("Acquisto inserito");
private final JLabel jLabel2;
private final JLabel jLabel3 = new JLabel("Articoli inseriti");
154
private
private
private
private
private
private
private
private
private
private
private
private
final
final
final
final
final
final
final
final
final
final
final
final
JLabel jLabel4 = new JLabel("Totale: ");
JTextField jTextField1;
JPanel jPanel1;
JPanel jPanel2;
JPanel jPanel3;
JPanel jPanel4;
JPanel jPanel5;
JPanel jPanel6;
JPanel jPanel7;
JScrollPane jScrollPane1;
JButton jButton1 = new JButton("Torna al menù principale");
JTable jTable1;
public VisualizzaReportAcquisto(long idAcquisto, JTable tabellaArticoli,
Set<RecordCalzature> calzatureAcquistate, double totale) {
super("Test JPA - Inserisci nuovo acquisto");
jPanel1 = new JPanel();
jPanel1.setLayout(new BoxLayout(jPanel1, BoxLayout.Y_AXIS));
jPanel1.setBorder(
BorderFactory.createTitledBorder("Visualizza totale"));
jPanel2 = new JPanel(new FlowLayout());
jPanel3 = new JPanel(new FlowLayout());
jPanel4 = new JPanel(new FlowLayout());
jPanel5 = new JPanel(new FlowLayout());
jPanel6 = new JPanel(new FlowLayout());
jPanel7 = new JPanel(new FlowLayout());
jLabel2 = new JLabel("ID Acquisto: " + idAcquisto);
jTable1 = tabellaArticoli;
javax.swing.table.DefaultTableModel model =
(javax.swing.table.DefaultTableModel)
jTable1.getModel();
Object[] colonnaSconto = new Object[tabellaArticoli.getRowCount()];
model.addColumn("Sconto", colonnaSconto);
model.setColumnCount(6);
Iterator<RecordCalzature> it = calzatureAcquistate.iterator();
while (it.hasNext()) {
RecordCalzature rc = (RecordCalzature) it.next();
String codice = rc.getCodice();
int sconto = rc.getSconto();
for (int i = 0; i < jTable1.getRowCount(); i++) {
if (jTable1.getValueAt(i, 0).equals(codice)) {
jTable1.setValueAt(sconto + "%", i, 5);
}
}
}
jTable1.setFocusable(false);
jTextField1 = new JTextField(String.valueOf(totale), 5);
jTextField1.setFocusable(false);
jScrollPane1 = new JScrollPane(jTable1);
jTable1.setFillsViewportHeight(true);
155
jPanel7.add(jLabel1);
jPanel6.add(jLabel2);
jPanel2.add(jLabel3);
jPanel4.add(jLabel4);
jPanel4.add(jTextField1);
jPanel5.add(jButton1);
jPanel1.add(jPanel7);
jPanel1.add(new JSeparator(SwingConstants.HORIZONTAL));
jPanel1.add(jPanel6);
jPanel1.add(jPanel2);
jPanel1.add(jScrollPane1);
jPanel1.add(jPanel3);
jPanel1.add(jPanel4);
jPanel1.add(jPanel5);
VisualizzaReportAcquistoListener listener =
new VisualizzaReportAcquistoListener(this);
jButton1.addActionListener(listener);
this.getContentPane().add(jPanel1);
this.setSize(700, 600);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
}
File VisualizzaReportAcquistoListener.java
package gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class VisualizzaReportAcquistoListener implements ActionListener {
private VisualizzaReportAcquisto frame;
public VisualizzaReportAcquistoListener(VisualizzaReportAcquisto frame) {
this.frame = frame;
}
public void actionPerformed(ActionEvent ev) {
frame.dispose();
}
}
156
La finestra VisualizzaAcquisti è composta da una tabella per visualizzare l’ID e la data di tutti gli
acquisti.
Interaccia grafica VisualizzaAcquisti:
File VisualizzaAcquisti.java
package gui;
import java.awt.FlowLayout;
import javax.swing.*;
import java.util.*;
import attivita.RecordAcquisto;
public class VisualizzaAcquisti extends JFrame {
private
private
private
private
private
final
final
final
final
final
JButton jButton1 = new JButton("Torna al menù principale");
JPanel jPanel1;
JPanel jPanel2;
JScrollPane jScrollPane1;
JTable jTable1 = new JTable();
public VisualizzaAcquisti(List<RecordAcquisto> listaAcquisti) {
super("Test JPA - Visualizza acquisti");
jPanel1 = new JPanel();
jPanel1.setLayout(new BoxLayout(jPanel1, BoxLayout.Y_AXIS));
157
jPanel1.setBorder(
BorderFactory.createTitledBorder("Visualizza acquisti"));
jPanel2 = new JPanel(new FlowLayout());
Object[][] tabellaAcquisti = new Object[listaAcquisti.size()][2];
Iterator<RecordAcquisto> it = listaAcquisti.iterator();
int i = 0;
while(it.hasNext()) {
RecordAcquisto ra = (RecordAcquisto) it.next();
tabellaAcquisti[i][0] = ra.getId();
tabellaAcquisti[i][1] = ra.getData();
i = i+1;
}
jTable1.setModel(new javax.swing.table.DefaultTableModel(
tabellaAcquisti,
new String [] {
"ID acquisto", "Data"
}
) {
Class[] types = new Class [] {
java.lang.Long.class, java.lang.String.class
};
boolean[] canEdit = new boolean [] {
false, false
};
public Class getColumnClass(int columnIndex) {
return types [columnIndex];
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
});
jTable1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jTable1.setFocusable(false);
jScrollPane1 = new JScrollPane(jTable1);
jTable1.setFillsViewportHeight(true);
jPanel2.add(jButton1);
jPanel1.add(jScrollPane1);
jPanel1.add(jPanel2);
jButton1.addActionListener(new VisualizzaAcquistiListener(this));
this.getContentPane().add(jPanel1);
this.setSize(500,500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
}
158
File VisualizzaAcquistiListener.java
package gui;
import java.awt.event.*;
public class VisualizzaAcquistiListener implements ActionListener {
private VisualizzaAcquisti frame;
public VisualizzaAcquistiListener(VisualizzaAcquisti frame) {
this.frame = frame;
}
public void actionPerformed(ActionEvent ev) {
frame.dispose();
}
}
La finestra SelezionaAcquisto è visualizzata durante l’esecuzione dell’attività
AttivitaEffettuaControlli. E’ molto simile alla finestra VisualizzaAcquisti, con la
differenza che è presente un metodo leggiIdAcquisto() per leggere l’ID dell’acquisto alla riga
selezionata della tabella, permettendo di selezionare un acquisto sul quale verranno effettuati i controlli. E’
inoltre presente un metodo aspettaOK() che mette l’attività AttivitaEffettuaControlli in
attesa che l’utente prema il bottone Inizia controlli per poter successivamente leggere l’ID dell’acquisto
selezionato dalla tabella.
Interaccia grafica SelezionaAcquisto:
159
File SelezionaAcquisto.java
package gui;
import java.awt.FlowLayout;
import javax.swing.*;
import java.util.*;
import attivita.RecordAcquisto;
public class SelezionaAcquisto extends JFrame {
private
private
private
private
private
final
final
final
final
final
JButton jButton1 = new JButton("Inizia controlli");
JPanel jPanel1;
JPanel jPanel2;
JScrollPane jScrollPane1;
JTable jTable1 = new JTable();
public SelezionaAcquisto(List<RecordAcquisto> listaAcquisti) {
super("Test JPA - Effettua controlli");
jPanel1 = new JPanel();
jPanel1.setLayout(new BoxLayout(jPanel1, BoxLayout.Y_AXIS));
jPanel1.setBorder(
BorderFactory.createTitledBorder("Effettua controlli"));
jPanel2 = new JPanel(new FlowLayout());
Object[][] tabellaAcquisti = new Object[listaAcquisti.size()][2];
Iterator<RecordAcquisto> it = listaAcquisti.iterator();
int i = 0;
while(it.hasNext()) {
RecordAcquisto ra = (RecordAcquisto) it.next();
tabellaAcquisti[i][0] = ra.getId();
tabellaAcquisti[i][1] = ra.getData();
i = i+1;
}
jTable1.setModel(new javax.swing.table.DefaultTableModel(
tabellaAcquisti,
new String [] {
"ID acquisto", "Data"
}
) {
Class[] types = new Class [] {
java.lang.Long.class, java.lang.String.class
};
boolean[] canEdit = new boolean [] {
false, false
};
public Class getColumnClass(int columnIndex) {
return types [columnIndex];
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
});
160
jTable1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jScrollPane1 = new JScrollPane(jTable1);
jTable1.setFillsViewportHeight(true);
jPanel2.add(jButton1);
jPanel1.add(jScrollPane1);
jPanel1.add(jPanel2);
jButton1.addActionListener(new SelezionaAcquistoListener(this));
this.getContentPane().add(jPanel1);
this.setSize(500,500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
public Long leggiIdAcquisto() throws RuntimeException {
int[] righeSelezionate = jTable1.getSelectedRows();
if(righeSelezionate.length == 0)
return null;
else {
Long idAcquisto = (Long) jTable1.getValueAt(righeSelezionate[0], 0);
return idAcquisto;
}
}
public void aspettaOK() {
synchronized (getContentPane()) {
try {
getContentPane().wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
File SelezionaAcquistoListener.java
package gui;
import java.awt.event.*;
public class SelezionaAcquistoListener implements ActionListener {
private SelezionaAcquisto frame;
public SelezionaAcquistoListener(SelezionaAcquisto frame) {
this.frame = frame;
}
161
public void actionPerformed(ActionEvent ev) {
synchronized (frame.getContentPane()) {
frame.getContentPane().notify();
frame.dispose();
}
}
}
La finestra VisualizzaRisultatoControllo visualizza i risultati delle attività atomiche di controllo.
Interaccia grafica VisualizzaRisultatoControllo:
File VisualizzaRisultatoControllo.java
package gui;
import
import
import
import
javax.swing.*;
java.util.*;
java.awt.GridLayout;
java.awt.FlowLayout;
import attivita.RecordCalzature;
public class VisualizzaRisultatoControllo extends JFrame {
private
private
private
private
final
final
final
final
JButton jButton1
JLabel jLabel1 =
JLabel jLabel2 =
JLabel jLabel3 =
= new JButton("Torna al menù principale");
new JLabel("Risultato Controlli");
new JLabel("Controllo importo totale: OK");
new JLabel("Calzature scontate");
162
private final JLabel jLabel4 =
new JLabel("Materiali utilizzati per l'abbigliamento");
private final JList jList1;
private final JList jList2;
private final JScrollPane jScrollPane1;
private final JScrollPane jScrollPane2;
private final JPanel jPanel1;
private final JPanel jPanel2;
private final JPanel jPanel3;
private final JPanel jPanel4;
private final JPanel jPanel5;
private final JPanel jPanel6;
private final JPanel jPanel7;
private final JPanel jPanel8;
private final JPanel jPanel9;
public VisualizzaRisultatoControllo(
Object[] calzatureScontate,
Object[] materiali) {
super("Test JPA - Effettua controlli");
jPanel1 = new JPanel();
jPanel1.setLayout(new BoxLayout(jPanel1, BoxLayout.Y_AXIS));
jPanel1.setBorder(
BorderFactory.createTitledBorder("Effettua controlli"));
jPanel2 = new JPanel(new GridLayout(1, 2, 10, 10));
jPanel3 = new JPanel();
jPanel3.setLayout(new BoxLayout(jPanel3, BoxLayout.Y_AXIS));
jPanel4 = new JPanel();
jPanel4.setLayout(new BoxLayout(jPanel4, BoxLayout.Y_AXIS));
jPanel5 = new JPanel(new FlowLayout());
jPanel6 = new JPanel(new FlowLayout());
jPanel7 = new JPanel(new FlowLayout());
jPanel8 = new JPanel(new FlowLayout());
jPanel9 = new JPanel(new FlowLayout());
jList1 = new JList(calzatureScontate);
jList1.setFocusable(false);
jList2 = new JList(materiali);
jList2.setFocusable(false);
jScrollPane1 = new JScrollPane(jList1);
jScrollPane2 = new JScrollPane(jList2);
jPanel9.add(jLabel1);
jPanel8.add(jLabel2);
jPanel7.add(jButton1);
jPanel6.add(jLabel4);
jPanel5.add(jLabel3);
jPanel4.add(jPanel6);
jPanel4.add(jScrollPane2);
jPanel3.add(jPanel5);
jPanel3.add(jScrollPane1);
163
jPanel2.add(jPanel3);
jPanel2.add(jPanel4);
jPanel1.add(jPanel9);
jPanel1.add(new JSeparator(SwingConstants.HORIZONTAL));
jPanel1.add(jPanel8);
jPanel1.add(jPanel2);
jPanel1.add(jPanel7);
VisualizzaRisultatoControlloListener listener =
new VisualizzaRisultatoControlloListener(this);
jButton1.addActionListener(listener);
this.getContentPane().add(jPanel1);
this.setSize(500, 400);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
this.setVisible(true);
}
}
File VisualizzaRisultatoControlloListener.java
package gui;
import java.awt.event.*;
public class VisualizzaRisultatoControlloListener implements ActionListener {
private VisualizzaRisultatoControllo frame;
public VisualizzaRisultatoControlloListener(
VisualizzaRisultatoControllo frame) {
this.frame = frame;
}
public void actionPerformed(ActionEvent ev) {
frame.dispose();
}
}
164
3.7 Main
La classe Main avvia l’applicazione. Al suo interno è presente il metodo main() che richiama
getEntityManager() della classe JPAUtil per inizializzare la EntityManagerFactory.
Successivamente esegue FinestraPrincipale.
File Main.java
package applicazione;
import javax.swing.SwingUtilities;
import gui.FinestraPrincipale;
import persistence.JPAUtil;
public class Main {
public static void main(String[] args) {
JPAUtil.getEntityManagerFactory();
Runnable target = new Runnable() {
@Override
public void run() {
new FinestraPrincipale();
}
};
SwingUtilities.invokeLater(target);
}
}
165
4. CONSIDERAZIONI FINALI
Per concludere questa analisi sui framework Java per la persistenza è necessario fare alcune considerazioni.
Hibernate e le Java Persistence API sono due software molto validi che forniscono gli stessi servizi. Oggi le
hibernate annotations vengono utilizzate maggiormente rispetto ai file di mapping XML perché il codice
sviluppato con annotazioni è più leggibile, più rapido da scrivere e meno ridondante. Bisogna però notare
che i software sviluppati con JPA non permettono una piena distinzione tra le classi di dominio e i metadati
per il mapping.
Analizzando le procedure di Object-Relational Mapping, bisogna considerare il fatto che i framework di
persistenza possono essere sfruttati al meglio in scenari top down in cui si ha totale libertà sulla base di
dati. Gli altri scenari sono spesso troppo laboriosi e producono risultati insoddisfacenti come nel caso di
scenari bottom up in cui le classi di dominio generate dal framework compongono un modello derivante dal
modello delle tabelle della base di dati che non sfrutterà funzioni importanti della programmazione ObjectOriented.
E’ interessante confrontare l’utilizzo di framework per la persistenza con un approccio Data Access Object
(DAO) più tradizionale. Le classi DAO permettono di costruire uno strato di persistenza con cui interagire
con la base di dati liberamente a differenza dei framework di persistenza in cui il mapping condiziona la
costruzione della base di dati.
Lo sviluppo con Hibernate e le Java Persistence API ha però diversi vantaggi:
1. Lo sviluppo ed eventuali modifiche al software sono molto più rapide;
2. Il programmatore deve essere a conoscenza dei dettagli della base di dati solo nella fase di
configurazione, successivamente potrà dedicarsi completamente alla logica applicativa del
software;
3. Permette di non lavorare con librerie di basso livello Java DataBase Connectivity (JDBC);
4. Sfruttando i linguaggi HQL e JPQL il software sarà molto portabile perché indipendente dalla
tipologia di SQL nativa del DBMS.
In conclusione, è possibile affermare che un approccio alla persistenza con Data Access Object permette
una gestione più libera e una progettazione del software più precisa, mentre i framework per la persistenza
lavorano ad un livello più alto permettendo spesso uno sviluppo più comodo e veloce che potrebbe essere
utile in alcune situazioni.
166
Riferimenti bibliografici
Christian Bauer, Gavin King, Java Persistence with Hibernate, Manning Publications, 2007
Documentazione ufficiale Hibernate
www.hibernate.org/docs
The Java EE 6 Tutorial – Part VI Persistence
docs.oracle.com
167
168