5. Versione 1: Architettura Spring / JPA
Proponiamo di scrivere un'applicazione console e un'applicazione grafica per generare le buste paga degli operatori dell'infanzia impiegati presso la "Maison de la petite enfance" in un comune. L'applicazione avrà la seguente architettura:
![]() |
5.1. DB Il database
I dati statici necessari per generare la busta paga saranno memorizzati in un database che chiameremo dbpam. Questo database potrebbe contenere le seguenti tabelle:
Struttura:
chiave primaria | |
numero di versione – aumenta ad ogni modifica della riga | |
Numero di previdenza sociale del dipendente – univoco | |
Cognome del dipendente | |
nome | |
il loro indirizzo | |
la sua città | |
il loro codice postale | |
Chiave esterna sul campo [ID] della tabella [INDEMNITES] |
Il suo contenuto potrebbe essere il seguente:

Struttura:
chiave primaria | |
numero di versione – aumenta ad ogni modifica della riga | |
Percentuale: Contributo sociale generale + Contributo al rimborso del debito sociale | |
percentuale: contributo sociale generale deducibile | |
percentuale: previdenza sociale, vedovanza, vecchiaia | |
percentuale: pensione integrativa + assicurazione contro la disoccupazione |
Il contenuto potrebbe essere il seguente:
![]()
Le aliquote dei contributi previdenziali sono indipendenti dal dipendente. La tabella precedente ha una sola riga.
chiave primaria | |||
numero di versione – aumenta ad ogni modifica della riga | |||
Indice di elaborazione – univoco | |||
Prezzo netto in euro per un'ora di reperibilità | |||
indennità giornaliera in euro per ogni giorno di assistenza | |||
Indennità per i pasti in euro per giorno di assistenza | |||
Indennità per ferie pagate. Si tratta di una percentuale applicata allo stipendio base. | |||
Il contenuto potrebbe essere il seguente:

Si noti che le indennità possono variare da un operatore all’altro. Esse sono collegate a un operatore specifico tramite la sua categoria retributiva. Pertanto, la sig.ra Marie Jouveinal, che ha una categoria retributiva 2 (tabella EMPLOYEES), ha una retribuzione oraria di 2,1 euro (tabella INDEMNITES).
5.2. Metodo di calcolo dello stipendio di un assistente all'infanzia
Presenteremo ora il metodo di calcolo dello stipendio mensile di un assistente all'infanzia. Questo non intende essere il metodo effettivamente utilizzato nella pratica. Come esempio, useremo lo stipendio della signora Marie Jouveinal, che ha lavorato 150 ore in 20 giorni durante il periodo di paga.
Si tiene conto dei seguenti fattori: | | |
Lo stipendio base dell'assistente all'infanzia è dato dalla seguente formula: | ||
Da questo stipendio base deve essere detratto da questo stipendio base : | | |
Totale contributi previdenziali: | ||
Inoltre, l’assistente all’infanzia ha diritto a un’indennità di soggiorno e a un’indennità di vitto per ogni giorno lavorato. Pertanto, riceve le seguenti indennità: | ||
Alla fine, lo stipendio netto da versare all'assistente all'infanzia è il seguente: |
5.3. Come funziona l'applicazione da console
Ecco un esempio di esecuzione dell'applicazione console in una finestra DOS:
Scriveremo un programma che riceverà le seguenti informazioni:
- il numero di previdenza sociale dell'assistente all'infanzia (254104940426058 nell'esempio - riga 1)
- Numero totale di ore lavorate (150 nell'esempio - riga 1)
- Numero totale di giorni lavorati (20 nell'esempio - riga 1)
Si osserva che:
- righe 9–14: mostrano le informazioni sul dipendente il cui numero di previdenza sociale è stato fornito
- righe 17–20: visualizzano le aliquote dei vari contributi
- righe 23–26: visualizzano le indennità associate al livello retributivo del dipendente (in questo caso, livello 2)
- righe 29–33: visualizzano le componenti dello stipendio da corrispondere
L'applicazione segnala eventuali errori:
Chiamata senza parametri:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés
Chiamata con dati errati:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné
Chiamata con un numero di previdenza sociale errato:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable
5.4. Come funziona l'applicazione grafica
L'applicazione grafica calcola gli stipendi degli operatori dell'assistenza all'infanzia utilizzando un modulo Swing:
![]() |
- Le informazioni passate come parametri al programma da console vengono ora inserite utilizzando i campi di immissione [1, 2, 3].
- Il pulsante [4] avvia il calcolo dello stipendio
- Il modulo visualizza le varie componenti salariali fino allo stipendio netto da corrispondere [5]
L'elenco a discesa [1, 6] non mostra i numeri di previdenza sociale dei dipendenti, ma i loro nomi e cognomi. Si presume qui che non vi siano due dipendenti con lo stesso nome e cognome.
5.5. Creazione del database
Avviamo WampServer e utilizziamo lo strumento PhpMyAdmin [1]:
![]() |
- in [2], selezioniamo l'opzione [Database],
![]() |
- in [3], creare un database [dbpam_hibernate],
- in [4], il database è stato creato. Selezionarlo,
![]() |
- in [5], vogliamo importare uno script SQL,
- in [6], utilizzare il pulsante [Sfoglia] per selezionare il file,
![]() |
- In [7,8], selezionare lo script SQL,
- in [9], lo eseguiamo,
![]() |
- In [10] sono state create le tabelle. Il loro contenuto è il seguente:
Tabella EMPLOYEES

tabella INDENNITÀ

tabella CONTRIBUTI
![]()
5.6. Implementazione JPA
5.6.1. Livello JPA / Hibernate
Configureremo il livello JPA nel seguente ambiente:
![]() |
Un programma da console interagirà con il database. Per farlo, è necessario:
- disporre di un database,
- disporre del driver JDBC per il DBMS, in questo caso MySQL,
- implementare il livello JPA utilizzando Hibernate,
- scrivere il programma da console.
Creiamo il progetto Maven [mv-pam-jpa-hibernate] [1]:
![]() |
Nella nostra architettura applicativa, abbiamo bisogno dei seguenti elementi:
- il database,
- il driver JDBC per il DBMS MySQL,
- il livello JPA/Hibernate (entità e configurazione),
- il programma della console di test.
5.6.1.1. Il database
Per prima cosa, creiamo un database vuoto. Avviamo WampServer e utilizziamo lo strumento PhpMyAdmin [1]:
![]() |
- in [2], selezioniamo l'opzione [Database],
![]() |
- in [3], creare un database denominato [dbpam_hibernate],
- in [4], il database è stato creato.
5.6.1.2. Configurazione del livello JPA
La connessione tra il livello JDBC e il database viene configurata nel file [persistence.xml], che configura il livello JPA. Questo file può essere creato utilizzando NetBeans:
![]() |
- nella scheda [Services] [1], connettersi al database utilizzando il driver JDBC di MySQL [2],
- in [3], il nome del database a cui si desidera connettersi.
- in [4], l'URL JDBC del database,
- in [5], accedi come root senza password,
- in [6], è possibile testare la connessione,
- in [7], la connessione è andata a buon fine.
![]() |
- Il collegamento compare in [8] e [9],
- in [10], aggiungi un nuovo elemento al progetto,
![]() |
- in [11] selezionare la categoria [Persistence] e in [12] l'elemento [Persistence Unit],
- in [13], assegnare un nome a questa unità di persistenza,
- in [14], selezioniamo un'implementazione di Hibernate,
- in [15], specificare la connessione appena creata al database MySQL,
- In [16], specificare che quando il livello JPA viene istanziato, deve creare le tabelle corrispondenti alle entità JPA del progetto.
Al termine della procedura guidata viene generato il file [persistence.xml]:
![]() |
- il file appare in un nuovo ramo del progetto, nella cartella [META-INF] [1],
- che corrisponde alla cartella [src/main/resources] del progetto [2,3].
Il suo contenuto è il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
- Riga 3: il nome dell'unità di persistenza e il tipo di transazione. RESOURCE_LOCAL indica che il progetto gestisce autonomamente le transazioni. In questo caso, sarà il programma console a occuparsene,
- riga 4: l'implementazione JPA utilizzata è Hibernate,
- righe 6–9: le proprietà JDBC per la connessione al database,
- riga 11: richiede la creazione delle tabelle corrispondenti alle entità JPA. In realtà, NetBeans genera qui una configurazione errata. La configurazione dovrebbe essere la seguente:
<property name="hibernate.hbm2ddl.auto" value="create"/>
Con l'opzione create, Hibernate, al momento dell'istanziazione del livello JPA, elimina e poi crea le tabelle corrispondenti alle entità JPA. L'opzione create-drop fa la stessa cosa ma, alla fine del ciclo di vita del livello JPA, elimina tutte le tabelle. Esiste un'altra opzione:
<property name="hibernate.hbm2ddl.auto" value="update"/>
Questa opzione crea le tabelle se non esistono, ma non le elimina se sono già presenti.
Aggiungeremo altre tre proprietà alla configurazione di Hibernate:
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
Queste impostazioni indicano a Hibernate di visualizzare le istruzioni SQL che invia al database. Il file completo è quindi il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
</properties>
</persistence-unit>
</persistence>
5.6.1.3. Dipendenze
Torniamo all'architettura del progetto:
![]() |
Abbiamo configurato il livello JPA tramite il file [persistence.xml]. L'implementazione scelta è stata Hibernate. Ciò ha introdotto delle dipendenze nel progetto:
![]() |
Queste dipendenze sono dovute all'inclusione di Hibernate nel progetto. Dobbiamo aggiungere un'altra dipendenza: il driver JDBC di MySQL, che implementa il livello JDBC dell'architettura. Aggiorniamo il file [pom.xml] come segue:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
...
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
Le righe 8–12 aggiungono la dipendenza per il driver JDBC di MySQL.
5.6.1.4. Entità JPA
![]() |
Domanda: Seguendo l'approccio illustrato nell'esempio della Sezione 4.4, generare le entità [Cotisation, Indemnite, Employe].
Note:
- Le entità faranno parte di un pacchetto denominato [jpa],
- ogni entità avrà un numero di versione,
- se due entità sono collegate da una relazione, verrà creata solo la relazione primaria @ManyToOne. La relazione inversa @OneToMany non verrà creata.
5.6.1.5. Il codice per la classe principale
Includiamo nel progetto le entità JPA sviluppate in precedenza [1]:
![]() |
quindi aggiungiamo [2], la seguente classe [main.Main]:
package main;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
EntityManager em=emf.createEntityManager();
// resource release
em.close();
emf.close();
}
}
- Riga 10: creiamo l'EntityManagerFactory per l'unità di persistenza denominata [mv-pam-jpa-hibernatePU]. Questo nome deriva dal file [persistence.xml]:
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- Riga 12: Viene creato l'EntityManager. Questo crea il livello JPA. Verrà utilizzato il file [persistence.xml] e quindi verranno create le tabelle del database.
- Righe 14–15: le risorse vengono rilasciate.
5.6.1.6. Test
Torniamo all'architettura del nostro progetto:
![]() |
Tutti i livelli sono stati implementati. Eseguiamo il progetto [2].
![]() |
L'output della console è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
La console contiene solo i log di Hibernate, poiché il programma eseguito non fa altro che istanziare il livello JPA. Si notino i seguenti punti:
- riga 43: Hibernate tenta di eliminare la chiave esterna dalla tabella [EMPLOYEES],
- righe 51–55: eliminazione delle tre tabelle,
- riga 57: creazione della tabella [COTISATIONS],
- riga 67: creazione della tabella [EMPLOYEES],
- riga 80: creazione della tabella [INDEMNITIES],
- riga 91: creazione della chiave esterna per la tabella [EMPLOYEES].
In NetBeans, è possibile visualizzare le tabelle nella connessione creata in precedenza:
![]() |
Le tabelle create dipendono sia dall'implementazione del livello JPA utilizzata sia dal DBMS utilizzato. Pertanto, un'implementazione JPA/EclipseLink con lo stesso database può generare tabelle diverse. È proprio questo che vedremo ora.
5.6.2. Livello JPA / EclipseLink
Creeremo un nuovo progetto Maven nel seguente ambiente:
![]() |
Seguiremo i passaggi descritti nella sezione precedente:
- creare un database MySQL [dbpam_eclipselink]. Useremo lo script [dbpam_eclipselink.sql] per generarlo,
- creare il file [persistence.xml] del progetto. Utilizzare l'implementazione EclipseLink JPA 2.0,
- aggiungere la dipendenza del driver JDBC di MySQL alle dipendenze generate,
- aggiungere le entità JPA e il programma console,
- eseguire i test.
Il file [persistence.xml] sarà il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
<persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="eclipselink.target-database" value="MySQL"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- Le proprietà da 9 a 13 sono state generate dalla procedura guidata di NetBeans,
- Riga 14: questa proprietà ci permette di impostare il livello di log di EclipseLink. Il livello FINE ci permette di vedere le istruzioni SQL che EclipseLink eseguirà sul database,
- Riga 15: Quando il livello JPA/EclipseLink viene istanziato, le tabelle delle entità JPA verranno eliminate e poi create.
L'output della console è il seguente:
- righe 26-30: connessione al database MySQL,
- righe 31-34: conferma che la connessione è andata a buon fine,
- riga 36: eliminazione della chiave esterna dalla tabella [EMPLOYEES],
- riga 37: eliminazione della tabella [COTISATIONS],
- riga 38: creazione della tabella [CONTRIBUTIONS]. Vale la pena notare che l'ID della chiave primaria non ha l'attributo auto_increment di MySQL. Ciò significa che MySQL non genera i valori della chiave primaria,
- riga 39: eliminazione della tabella [EMPLOYEES],
- riga 40: creazione della tabella [EMPLOYEES]. La sua chiave primaria ID non ha l'attributo auto_increment di MySQL,
- riga 41: eliminazione della tabella [INDEMNITIES],
- riga 42: creazione della tabella [INDEMNITIES]. La sua chiave primaria ID non ha l'attributo auto_increment di MySQL,
- riga 43: creazione di una chiave esterna dalla tabella [EMPLOYEES] alla tabella [BENEFITS],
- riga 44: creazione di una tabella [SEQUENCE]. Sarà utilizzata per generare le chiavi primarie per le tre tabelle precedenti,
- riga 47: si verifica un'eccezione perché questa tabella esisteva già,
- righe 51–53: inizializzazione della tabella [SEQUENCE].
L'esistenza delle tabelle generate può essere verificata in NetBeans [1]:
![]() |
Pertanto, basandosi sulle stesse entità JPA, le implementazioni JPA di Hibernate ed EclipseLink non generano le stesse tabelle. Nel resto di questo documento, quando l'implementazione JPA utilizzata è:
- Hibernate, useremo il database [dbpam_hibernate],
- EclipseLink, useremo il database [dbpam_eclipselink].
5.6.3. Lavoro da svolgere
Seguendo la stessa procedura di prima,
- crea e testa un progetto [mv-pam-jpa-hibernate-oracle] utilizzando un'implementazione JPA di Hibernate e un DBMS Oracle,
- crea e testa un progetto [mv-pam-jpa-hibernate-mssql] utilizzando un'implementazione JPA di Hibernate e un DBMS SQL Server,
- crea e testa un progetto [mv-pam-jpa-eclipselink-oracle] utilizzando un'implementazione JPA di EclipseLink e un DBMS Oracle,
- creare e testare un progetto [mv-pam-jpa-eclipselink-mssql] utilizzando un'implementazione JPA di EclipseLink e un DBMS SQL Server,
5.6.4. Lazy o Eager?
Torniamo a una possibile definizione dell'entità [Employee]:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
...
}
Le righe 27–29 definiscono la chiave esterna dalla tabella [EMPLOYEES] alla tabella [INDEMNITIES]. L'attributo fetch alla riga 27 definisce la strategia di recupero per il campo indemnity alla riga 29. Esistono due modalità:
- FetchType.LAZY: quando viene interrogato un dipendente, l'indennità corrispondente non viene recuperata. Verrà recuperata quando il campo [Employee].indemnity viene referenziato per la prima volta.
- FetchType.EAGER: quando si cerca un dipendente, viene recuperata l'indennità corrispondente. Questa è la modalità predefinita quando non viene specificata alcuna modalità.
Per comprendere il vantaggio dell'opzione FetchType.LAZY, si consideri il seguente esempio. In una pagina web viene visualizzato un elenco di dipendenti senza la relativa retribuzione, con un link [Dettagli]. Facendo clic su questo link viene visualizzata la retribuzione del dipendente selezionato. Si osserva che:
- Per visualizzare la prima pagina, non è necessario includere i dipendenti e i relativi benefici. È quindi opportuno utilizzare la modalità FetchType.LAZY;
- per visualizzare la seconda pagina con i dettagli, è necessario effettuare una query aggiuntiva al database per recuperare i benefici del dipendente selezionato.
La modalità FetchType.LAZY impedisce il recupero di una quantità eccessiva di dati di cui l'applicazione non ha bisogno immediatamente. Vediamo un esempio.
Il progetto [mv-pam-jpa-hibernate] viene duplicato:
![]() |
- in [1] il progetto viene copiato,
- in [2] specifichiamo la cartella per la copia e in [3] il suo nome,
- in [4], il nuovo progetto ha lo stesso nome di quello vecchio. Modifichiamo questo:
![]() |
- in [1], rinominiamo il progetto,
- in [2], rinominiamo il progetto e il suo artifactId,
- in [3], il nuovo progetto.
Modifichiamo il programma [Main.java] come segue:
package main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
public class Main {
// the JPQL query below brings back an employee
// the foreign key [Employe].indemnite is in FetchType.LAZY
public static void main(String[] args) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
// first attempt
EntityManager em = emf.createEntityManager();
Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// second test
em = emf.createEntityManager();
employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
// free up resources
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// resource release
emf.close();
}
}
- riga 15: creiamo l'EntityManagerFactory per il livello JPA,
- riga 17: otteniamo l'EntityManager, che ci permette di interagire con il livello JPA,
- riga 18: recuperiamo il dipendente di nome Jouveinal,
- riga 19: chiudiamo l'EntityManager. Questo chiude il contesto di persistenza.
- riga 22: visualizziamo il dipendente recuperato.
La classe [Employee] è la seguente:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
/**
* Returns a string representation of the object. This implementation constructs
* that representation based on the id fields.
* @return a string representation of the object.
*/
@Override
public String toString() {
return "jpa.Employe[id=" + getId()
+ ",version="+getVersion()
+",SS="+getSS()
+ ",nom="+getNom()
+ ",prenom="+getPrenom()
+ ",adresse="+getAdresse()
+",ville="+getVille()
+",code postal="+getCodePostal()
+",indice="+getIndemnite().getIndice()
+"]";
}
...
}
- riga 27: il campo indemnite viene recuperato in modalità LAZY,
- riga 47: utilizza il campo indemnite. Se il metodo toString viene chiamato mentre il campo indemnite non è ancora stato recuperato, verrà recuperato in quel momento. A meno che il contesto di persistenza non sia stato chiuso, come nell'esempio.
Torniamo al codice [Main]:
- righe 21–25: dovremmo ottenere un'eccezione. Questo perché verrà chiamato il metodo toString. Esso utilizzerà il campo indemnite. Questo campo verrà cercato. Poiché il contesto di persistenza è stato chiuso, l'entità [Employee] recuperata non esiste più, da qui l'eccezione.
- riga 27: creiamo un nuovo EntityManager,
- riga 28: recuperiamo il dipendente Jouveinal richiedendo esplicitamente l'indennità associata nella query JPQL. Questa richiesta esplicita è necessaria perché la modalità di recupero per questa indennità è LAZY,
- Riga 30: chiudiamo l'EntityManager,
- Righe 32–36: visualizziamo nuovamente il dipendente. Non dovrebbe verificarsi alcuna eccezione.
Per eseguire il progetto, è necessario disporre di un database popolato. È possibile crearlo seguendo i passaggi descritti nella sezione 5.5. Inoltre, è necessario modificare il file [persistence.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
- Abbiamo rimosso l'opzione che creava le tabelle. Il database qui esiste già ed è popolato,
- abbiamo rimosso le opzioni che facevano sì che Hibernate registrasse le istruzioni SQL inviate al database.
L'esecuzione del progetto genera i seguenti due messaggi nella console:
- Riga 1: L'eccezione verificatasi durante il tentativo di recuperare l'indennità mancante mentre la sessione era chiusa. Si può notare che l'indennità non è stata recuperata a causa della modalità LAZY,
- riga 2: il dipendente con la sua indennità recuperata tramite una query che ha bypassato la modalità LAZY.
5.6.5. Lavoro da svolgere
Seguendo una procedura simile a quella appena descritta, creare un progetto [mv-pam-pa-eclipselink-lazy] che dimostri il comportamento di EclipseLink con la modalità LAZY.
Si ottengono i seguenti risultati:
In modalità LAZY, entrambe le query hanno restituito la retribuzione insieme al dipendente. Ricercando questa anomalia online, scopriamo che l'annotazione [FetchType.LAZY] (riga 1):
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
non è un requisito ma un suggerimento. L'implementatore JPA non è obbligato a seguirlo. Possiamo quindi notare che il codice a volte diventa dipendente dall'implementazione JPA utilizzata. È possibile configurare EclipseLink affinché si comporti come previsto in modalità LAZY.
5.6.6. Andando avanti
L'architettura dell'applicazione da realizzare è la seguente:
![]() |
Nel resto di questo documento, duplicheremo il progetto Maven [mv-pam-jpa-hibernate] nel progetto [mv-pam-spring-hibernate] [1, 2, 3]:
![]() |
- quindi rinomineremo il nuovo progetto [4, 5, 6].
Modificheremo le dipendenze del nuovo progetto. Il file [pom.xml] assumerà il seguente aspetto:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-hibernate</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- righe 25–31: la dipendenza per i test JUnit,
- righe 32–41: dipendenze per il pool di connessioni Apache DBCP,
- righe 42–65: dipendenze per il framework Spring,
- righe 67–71: dipendenze per l'implementazione JPA/Hibernate,
- righe 72–76: la dipendenza per il driver JDBC di MySQL,
- righe 77–81: la dipendenza per l'interfaccia Swing. Questa viene aggiunta automaticamente da NetBeans quando si aggiunge un'interfaccia Swing al progetto.
Inoltre, genereremo i due database MySQL:
- [dbpam_hibernate] dallo script [dbpam_hibernate.sql],
- [dbpam_eclipselink] dallo script [dbpam_eclipselink.sql],
5.7. L'interfaccia im i per i livelli [business] e [DAO]
Torniamo all'architettura dell'applicazione:
![]() |
Nell'architettura sopra riportata, quale interfaccia dovrebbe fornire il livello [DAO] al livello [business] e quale interfaccia dovrebbe fornire il livello [business] al livello [UI]? Un primo approccio alla definizione delle interfacce dei diversi livelli consiste nell'esaminare i vari casi d'uso dell'applicazione. Qui ne abbiamo due, a seconda dell'interfaccia utente scelta: console o modulo grafico.
Esaminiamo come viene utilizzata l'applicazione da console:
L'app riceve tre informazioni dall'utente (vedi riga 1 sopra)
- il numero di previdenza sociale dell’assistente all’infanzia
- il numero di ore lavorate nel mese
- il numero di giorni lavorati nel mese
Sulla base di queste informazioni e di altri dati memorizzati nei file di configurazione, l'applicazione visualizza le seguenti informazioni:
- righe 4–6: i valori inseriti
- righe 8–10: informazioni relative al dipendente di cui è stato fornito il numero di previdenza sociale
- righe 12–14: le aliquote dei vari contributi previdenziali
- righe 16–17: le varie indennità corrisposte all’assistente all’infanzia
- righe 19–24: voci presenti sulla busta paga del fornitore di servizi di assistenza all’infanzia
Il livello [aziendale] deve fornire una serie di informazioni al livello [UI]:
- informazioni relative a un fornitore di servizi di assistenza all'infanzia identificato tramite il proprio numero di previdenza sociale. Queste informazioni si trovano nella tabella [EMPLOYEES]. Ciò consente la visualizzazione delle righe 6–8.
- gli importi delle varie aliquote contributive previdenziali da detrarre dallo stipendio lordo. Queste informazioni si trovano nella tabella [COTISATIONS]. Ciò consente la visualizzazione delle righe 10–12.
- gli importi delle varie indennità relative al ruolo di assistente all'infanzia. Queste informazioni si trovano nella tabella [INDEMNITES]. Ciò consente di visualizzare le righe 14-15.
- le componenti dello stipendio visualizzate nelle righe 18-22.
Da ciò, potremmo decidere un'implementazione iniziale dell'interfaccia [IMetier] presentata dal livello [metier] al livello [ui]:
- riga 1: gli elementi del livello [business] sono collocati nel pacchetto [business]
- riga 5: il metodo [calculatePaystub] accetta come parametri le tre informazioni ottenute dal livello [ui] e restituisce un oggetto di tipo [Paystub] contenente le informazioni che il livello [ui] visualizzerà sulla console. La classe [ Paystub] potrebbe essere la seguente:
- riga 9: il dipendente a cui si riferisce la busta paga - informazione n. 1 visualizzata dal livello [ui]
- riga 10: le varie aliquote contributive - informazione n. 2 visualizzata dal livello [ui]
- riga 11: le varie indennità collegate all'indice del dipendente - informazione n. 3 visualizzata dal livello [ui]
- riga 12: le componenti del loro stipendio - informazione n. 4 visualizzata dal livello [ui]
Un secondo caso d'uso per il livello [business] si presenta con l'interfaccia grafica:
![]() |
Come mostrato sopra, l'elenco a discesa [1, 2] visualizza tutti i dipendenti. Questo elenco deve essere richiesto al livello [business]. L' ace di interfaccia per questo livello si evolve quindi come segue:
- riga [10]: il metodo che consentirà al livello [UI] di richiedere l'elenco di tutti i dipendenti al livello [Business].
Il livello [business] può inizializzare i campi [Dipendente, Contributo, Indennità] dell'oggetto [Libro paga] sopra indicato solo interrogando il livello [DAO], poiché tali informazioni sono memorizzate nelle tabelle del database. Lo stesso vale per il recupero dell'elenco di tutti i dipendenti. Potremmo creare un'unica interfaccia [DAO] per gestire l'accesso alle tre entità [Employee, Contribution, Allowance]. Tuttavia, in questo caso abbiamo deciso di creare un'interfaccia [DAO] per ogni entità.
L'interfaccia [DAO] per l'accesso alle entità [Contribution] nella tabella [CONTRIBUTIONS] sarà la seguente:
- Riga 6: L'interfaccia [ICotisationDao] gestisce l'accesso all'entità [Cotisation] e quindi alla tabella [COTISATIONS] nel database. La nostra applicazione necessita solo del metodo [findAll] alla riga 16, che recupera tutti i contenuti della tabella [COTISATIONS]. In questo caso, abbiamo voluto affrontare un caso più generale in cui tutte le operazioni CRUD (Create, Read, Update, Delete) vengono eseguite sull'entità.
- Riga 8: Il metodo [create] crea una nuova entità [Cotisation]
- Riga 10: Il metodo [edit] modifica un'entità [Cotisation] esistente
- Riga 12: il metodo [destroy] elimina un'entità [Cotisation] esistente
- Riga 14: il metodo [find] recupera un'entità [Cotisation] esistente utilizzando il suo id
- Riga 16: Il metodo [findAll] restituisce un elenco di tutte le entità [Membership] esistenti
Diamo un'occhiata più da vicino alla firma del metodo [create]:
Il metodo create ha un parametro cotisation di tipo Cotisation. Il parametro cotisation deve essere persistito, ovvero memorizzato nella tabella [COTISATIONS]. Prima di questa persistenza, il parametro cotisation ha un identificatore id senza valore. Dopo la persistenza, il campo id ha un valore che è la chiave primaria del record aggiunto alla tabella [COTISATIONS]. Il parametro cotisation è quindi un parametro di input/output del metodo create. Non sembra necessario che il metodo create restituisca inoltre il parametro cotisation come risultato. Poiché il metodo chiamante detiene un riferimento all'oggetto [Cotisation cotisation], se questo viene modificato, il metodo chiamante ha accesso all'oggetto modificato poiché ne detiene un riferimento. Può quindi conoscere il valore che il metodo create ha assegnato al campo id dell'oggetto [Cotisation cotisation]. La firma del metodo potrebbe quindi essere semplificata in:
Quando si scrive un'interfaccia, è importante ricordare che può essere utilizzata in due contesti diversi: locale e remoto . Nel contesto locale, il metodo chiamante e il metodo chiamato vengono eseguiti nella stessa JVM:
![]() |
Se il livello [business] chiama il metodo create del livello [DAO], dispone effettivamente di un riferimento al parametro [Membership membership] che passa al metodo.
Nel contesto remoto, il metodo chiamante e il metodo chiamato vengono eseguiti in JVM diverse:
![]() |
Nell'esempio sopra riportato, il livello [business] viene eseguito nella JVM 1 e il livello [DAO] nella JVM 2 su due macchine diverse. I due livelli non comunicano direttamente. Tra di essi si trova un livello che chiameremo livello di comunicazione [1]. Questo è costituito da un livello di trasmissione [2] e da un livello di ricezione [3]. Lo sviluppatore generalmente non deve scrivere questi livelli di comunicazione. Essi vengono generati automaticamente da strumenti software. Il livello [business] viene scritto come se fosse in esecuzione nella stessa JVM del livello [DAO]. Pertanto, non sono necessarie modifiche al codice.
Il meccanismo di comunicazione tra il livello [business] e il livello [DAO] è il seguente:
- il livello [business] chiama il metodo create del livello [DAO], passando il parametro [Contribution contribution1]
- questo parametro viene effettivamente passato al livello di trasmissione [2]. Questo livello trasmetterà il valore del parametro cotisation1 sulla rete, non il suo riferimento. La forma esatta di questo valore dipende dal protocollo di comunicazione utilizzato.
- Il livello di ricezione [3] recupera questo valore e lo utilizza per ricostruire un oggetto [Cotisation cotisation2] che rispecchia il parametro iniziale inviato dal livello [business]. Ora abbiamo due oggetti identici (in termini di contenuto) in due JVM diverse: cotisation1 e cotisation2.
- Il livello di presentazione passerà l'oggetto `contribution2` al metodo `create` del livello [DAO], che lo renderà persistente nel database. Dopo questa operazione, il campo `id` dell'oggetto `contribution2` è stato inizializzato con la chiave primaria del record aggiunto alla tabella [COTISATIONS]. Questo non è il caso dell'oggetto `contribution1`, a cui il livello [business] fa riferimento. Se vogliamo che il livello [business] abbia un riferimento all'oggetto cotisation2, dobbiamo passarlo al livello. Pertanto, dobbiamo modificare la firma del metodo create nel livello [DAO]:
- Con questa nuova firma, il metodo create restituirà l'oggetto persistito contribution2. Questo risultato viene restituito al livello ricevente [3] che ha chiamato il livello [DAO]. Il livello [DAO] restituirà il valore (non il riferimento) di contribution2 al livello mittente [2].
- Il livello mittente [2] recupererà questo valore e lo utilizzerà per ricostruire un oggetto [Membership membership3] che rispecchia il risultato restituito dal metodo create del livello [DAO].
- L'oggetto [Contribution contribution3] viene restituito al metodo nel livello [business] la cui chiamata al metodo create del livello [DAO] aveva avviato l'intero meccanismo. Il livello [business] può quindi determinare il valore della chiave primaria assegnato all'oggetto [Contribution contribution1] per il quale aveva richiesto la persistenza: questo è il valore del campo id in contribution3.
L'architettura precedente non è la più comune. Più frequentemente, i livelli [business] e [DAO] si trovano nella stessa JVM:
![]() |
In questa architettura, sono i metodi del livello [business] che devono restituire i risultati, non quelli del livello [DAO]. Tuttavia, la seguente firma del metodo create del livello [DAO]:
ci permette di evitare di fare ipotesi sull'effettiva architettura in uso. Usare firme che funzionano indipendentemente dall'architettura scelta — sia essa locale o remota — significa che se un metodo chiamato modifica alcuni dei suoi parametri:
- anche questi devono far parte del risultato del metodo chiamato
- il metodo chiamante deve utilizzare il risultato del metodo chiamato e non i riferimenti ai parametri modificati che ha passato al metodo chiamato.
Questo ci permette di passare da un'architettura locale a un'architettura remota senza modificare il codice. Rivediamo l'interfaccia [ICotisationDao] in questa ottica:
- riga 8: il caso del metodo create è stato gestito
- Riga 10: il metodo edit utilizza il suo parametro [Cotisation cotisation1] per aggiornare il record nella tabella [COTISATIONS] che ha la stessa chiave primaria dell'oggetto cotisation. Restituisce l'oggetto cotisation2, che è una rappresentazione del record modificato. Il parametro contribution1 non viene modificato. Il metodo deve restituire contribution2 come risultato, sia in un'architettura remota che locale.
- Riga 12: Il metodo destroy elimina il record dalla tabella [COTISATIONS] che ha la stessa chiave primaria dell'oggetto contribution passato come parametro. L'oggetto contribution non viene modificato. Pertanto, non è necessario restituirlo.
- Riga 14: Il parametro id del metodo find non viene modificato dal metodo. Non è necessario includerlo nel risultato.
- Riga 16: Il metodo findAll non ha parametri. Non è quindi necessario esaminarlo.
In definitiva, solo la firma del metodo create deve essere adattata per essere utilizzabile all'interno di un'architettura remota. Il ragionamento di cui sopra si applica alle altre interfacce [DAO]. Non lo ripeteremo qui e useremo invece firme utilizzabili sia in architetture remote che locali.
L'interfaccia [DAO] per l'accesso alle entità [Indemnite] nella tabella [INDEMNITES] sarà la seguente:
- Riga 6: L'interfaccia [IIndemniteDao] gestisce l'accesso all'entità [Indemnite] e quindi alla tabella [INDEMNITES] nel database. La nostra applicazione necessita solo del metodo [findAll] alla riga 16, che recupera l'intero contenuto della tabella [INDEMNITES]. In questo caso, abbiamo voluto affrontare un caso più generale in cui tutte le operazioni CRUD (Create, Read, Update, Delete) vengono eseguite sull'entità.
- Riga 8: Il metodo [create] crea una nuova entità [Indemnite]
- Riga 10: Il metodo [edit] modifica un'entità [Indemnite] esistente
- Riga 12: il metodo [destroy] elimina un'entità [Indemnite] esistente
- Riga 14: il metodo [find] recupera un'entità [Indemnite] esistente utilizzando il suo id
- Riga 16: Il metodo [findAll] restituisce un elenco di tutte le entità [Indemnite] esistenti
L'interfaccia [DAO] per accedere alle entità [Employe] nella tabella [EMPLOYES] sarà la seguente:
- Riga 6: L'interfaccia [IEmployeDao] gestisce l'accesso all'entità [Employee] e quindi alla tabella [EMPLOYEES] nel database. La nostra applicazione necessita solo del metodo [findAll] alla riga 16, che recupera tutti i contenuti della tabella [EMPLOYEES]. In questo caso, volevamo affrontare un caso più generale in cui tutte le operazioni CRUD (Create, Read, Update, Delete) vengono eseguite sull'entità.
- Riga 8: Il metodo [create] crea una nuova entità [Employee]
- Riga 10: Il metodo [edit] modifica un'entità [Employee] esistente
- Riga 12: il metodo [destroy] elimina un'entità [Employee] esistente
- Riga 14: il metodo [find] recupera un'entità [Employee] esistente tramite il suo ID
- Riga 16: Il metodo [find(String SS)] recupera un'entità [Employee] esistente utilizzando il suo numero SS. Abbiamo visto che questo metodo era necessario per l'applicazione console.
- Riga 18: il metodo [findAll] restituisce un elenco di tutte le entità [Employee] esistenti. Abbiamo visto che questo metodo era necessario per l'applicazione grafica.
5.8. La classe [PamException]
Il livello [DAO] funzionerà con l'API JDBC di Java. Questa API genera eccezioni [SQLException] controllate, che presentano due svantaggi:
- appesantiscono il codice, che deve gestire queste eccezioni utilizzando blocchi try/catch.
- devono essere dichiarate nelle firme dei metodi dell'interfaccia [IDao] utilizzando "throws SQLException". Ciò impedisce l'implementazione di questa interfaccia da parte di classi che genererebbero un'eccezione controllata di tipo diverso da [SQLException].
Per risolvere questo problema, il livello [DAO] "propagherà" solo eccezioni non controllate di tipo [PamException].
![]() |
- Il livello [JDBC] genera eccezioni di tipo [SQLException]
- Il livello [JPA] genera eccezioni specifiche dell'implementazione JPA in uso
- il livello [DAO] genera eccezioni non intercettate di tipo [PamException]
Ciò comporta due conseguenze:
- Il livello [business] non sarà tenuto a gestire le eccezioni provenienti dal livello [DAO] utilizzando blocchi try/catch. Può semplicemente lasciarle propagare fino al livello [UI].
- I metodi dell'interfaccia [IDao] non devono specificare la natura della [PamException] nelle loro firme, il che lascia aperta la possibilità di implementare questa interfaccia con classi che genererebbero un altro tipo di eccezione non intercettata.
La classe [PamException] verrà inserita nel pacchetto [exception] del progetto NetBeans:
![]() |
Il suo codice è il seguente:
- Riga 4: [PamException] deriva da [RuntimeException]. Si tratta quindi di un tipo di eccezione che il compilatore non richiede di gestire con un blocco try/catch né di includere nelle firme dei metodi. Per questo motivo, [PamException] non è inclusa nelle firme dei metodi dell'interfaccia [IDao]. Ciò consente all'interfaccia di essere implementata da una classe che genera un diverso tipo di eccezione, a condizione che derivi anch'essa da [RuntimeException].
- Per distinguere tra gli errori che possono verificarsi, utilizziamo il codice di errore nella riga 7. I tre costruttori nelle righe 14, 19 e 24 sono quelli della classe padre [RuntimeException], a cui abbiamo aggiunto un parametro: il codice di errore che vogliamo assegnare all'eccezione.
Il comportamento dell'applicazione, dal punto di vista delle eccezioni, sarà il seguente:
- Il livello [DAO] incapsulerà qualsiasi eccezione incontrata all'interno di una [PamException] e la rilancerà al livello [business].
- Il livello [business] consentirà alle eccezioni lanciate dal livello [DAO] di propagarsi verso l'alto. Incapsulerà qualsiasi eccezione che si verifichi nel livello [business] in una [PamException] e la rilancerà al livello [UI].
- Il livello [UI] intercetta tutte le eccezioni propagate dai livelli [business] e [DAO]. Si limiterà a visualizzare l'eccezione sulla console o sull'interfaccia utente grafica.
Esaminiamo ora l'implementazione dei livelli [DAO] e [business] uno per uno.
5.9. Il livello [DAO] dell'applicazione [PAM]
Stiamo lavorando all'interno della seguente architettura:
![]() |
5.9.1. Implementazione
Lettura consigliata: Sezione 3.1.3 di [rif1]
Domanda: Utilizzando l'integrazione Spring/JPA, scrivi le classi [CotisationDao, IndemniteDao, EmployeDao] per implementare le interfacce [ICotisationDao, IIndemniteDao, IEmployeDao]. Ogni metodo di classe intercetterà qualsiasi eccezione e la racchiuderà in un oggetto [PamException] con un codice di errore specifico per l'eccezione intercettata.
Le classi di implementazione faranno parte del pacchetto [dao]:
![]() |
5.9.2. Configurazione
Lettura consigliata: Sezione 3.1.5 di [rif. 1]
L'integrazione DAO/JPA è configurata dal file Spring [spring-config-dao.xml] e dal file JPA [persistence.xml]:
![]() |
Domanda: Scrivi il contenuto di questi due file. Si suppone che il database utilizzato sia il database MySQL5 [dbpam_hibernate] generato dallo script SQL [dbpam_hibernate.sql]. Il file Spring definirà i seguenti tre bean: employeDao di tipo EmployeDao, indemniteDao di tipo IndemniteDao e cotisationDao di tipo CotisationDao. Inoltre, l'implementazione JPA utilizzata sarà Hibernate.
5.9.3. Test
Lettura consigliata: sezioni 3.1.6 e 3.1.7 di [rif1]
Ora che il livello [DAO] è stato scritto e configurato, possiamo testarlo. L'architettura di test sarà la seguente:
![]() |
5.9.4. InitDB
Creeremo due programmi di test per il livello [DAO]. Questi saranno inseriti nel pacchetto [dao] [2] del ramo [Test Packages] [1] del progetto NetBeans. Questo ramo non è incluso nel progetto generato dall'opzione [Build project], il che garantisce che i programmi di test che inseriamo lì non saranno inclusi nel file .jar finale del progetto.
![]() |
Le classi collocate nel ramo [Test Packages] hanno accesso alle classi nel ramo [Source Packages] e alle librerie di classi del progetto. Se i test richiedono librerie diverse da quelle presenti nel progetto, queste devono essere dichiarate nel ramo [Test Libraries] [2].
Le classi di test utilizzano lo strumento di test unitari JUnit:
- [JUnitInitDB] non esegue alcun test. Popola il database con alcuni record e poi li visualizza sulla console.
- [JUnitDao] esegue una serie di test e ne verifica i risultati.
Lo scheletro della classe [JUnitInitDB] è il seguente:
- Il metodo [init] viene eseguito prima dell'avvio della suite di test (annotazione @BeforeClass). Esso istanzia il livello [DAO].
- Il metodo [clean] viene eseguito prima di ogni test (annotazione @Before). Cancella il database.
- Il metodo [initDB] è un test (annotazione @Test). È l'unico. Un test deve contenere istruzioni di asserzione Assert.assertCondition. Qui non ce ne saranno. Il metodo è quindi un test fittizio. Il suo scopo è quello di popolare il database con alcune righe e quindi visualizzare il contenuto del database sulla console. Qui vengono utilizzati i metodi create e findAll dei livelli [DAO].
Domanda: Completa il codice per la classe [JUnitInitDB]. Usa l'esempio della Sezione 3.1.6 di [ref1] come guida. Il codice genererà l'output mostrato nella Sezione 5.1.
5.9.5. Implementazione del test
Ora siamo pronti per eseguire [InitDB]. Descriviamo la procedura utilizzando il DBMS MySQL5:
![]() |
- le classi [1], i file di configurazione [2] e le classi di test del livello [DAO] [3] sono stati configurati,
![]() |
- il progetto viene compilato [4]
- viene eseguita la classe [JUnitInitDB] [5]. Il DBMS MySQL5 viene avviato con un database [dbpam_hibernate] esistente,
- la finestra [Test Results] [6] indica che i test hanno avuto esito positivo. Questo messaggio non è rilevante in questo contesto, poiché il programma [JUnitInitDB] non contiene istruzioni di asserzione Assert.assertCondition che potrebbero causare il fallimento del test. Tuttavia, dimostra che non si sono verificate eccezioni durante l'esecuzione del test.
La finestra [Output] contiene i log di esecuzione, inclusi quelli di Spring e del test stesso. L'output generato dalla classe [JUnitInitDB] è il seguente:
Le tabelle [EMPLOYEES, ALLOWANCES, CONTRIBUTIONS] sono state popolate. È possibile verificarlo collegando NetBeans al database [dbpam_hibernate].
![]() |
- In [1], nella scheda [services], è possibile visualizzare i dati della tabella [employees] della connessione [dbpam_hibernate] [2],
- in [3] il risultato.
5.9.6. JUnitD ao
Ora esamineremo una seconda classe di test [JUnitDao]:
![]() |
Lo scheletro della classe sarà il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
Nella classe di test precedente, il database viene svuotato prima di ogni test.
Domanda: Scrivere i seguenti metodi:
1 - test02: basato su test01
2 - test03: un dipendente ha un campo di tipo Indennità. Pertanto, creare un'entità Indennità e un'entità Dipendente
3 - test04.
Procedendo allo stesso modo della classe di test [JUnitInitDB], otteniamo i seguenti risultati:
![]() |
- In [1], eseguiamo la classe di test
- in [2], i risultati del test nella finestra [Test Results]
Proviamo a generare un errore per vedere come viene segnalato nella pagina dei risultati:
Riga 13: L'asserzione genererà un errore, poiché il valore di Csgrds è 3,49 (riga 8). L'esecuzione della classe di test produce i seguenti risultati:
![]() |
- La pagina dei risultati [1] ora mostra che alcuni test hanno dato esito negativo.
- In [2], un riepilogo dell'eccezione che ha causato il fallimento del test. Include il numero di riga nel codice Java in cui si è verificata l'eccezione.
5.10. Il livello [business] dell'applicazione [PAM]
Ora che il livello [DAO] è stato scritto, passiamo allo studio del livello business [2]:
![]() |
5.10.1. L'interfaccia Java [IMetier]
È stata descritta nella sezione 5.7. La riportiamo di seguito:
L'implementazione del livello [business] verrà effettuata in un pacchetto [business]:
![]() |
Il pacchetto [Business] includerà, oltre all'interfaccia [IMetier] e alla sua implementazione [Metier], altre due classi: [Payroll] e [PayrollItems]. La classe [Payroll] è stata brevemente introdotta nella Sezione 5.7. Ora la rivedremo.
5.10.2. La classe [Payroll]
Il metodo [calculatePayStub] dell'interfaccia [IMetier] restituisce un oggetto di tipo [PayStub] che rappresenta i vari elementi di una busta paga. La sua definizione è la seguente:
- riga 7: la classe implementa l'interfaccia Serializable poiché le sue istanze possono essere scambiate in rete.
- riga 9: il dipendente a cui si riferisce la busta paga
- riga 10: le varie aliquote contributive
- riga 11: le varie indennità legate all'indice del dipendente
- riga 12: le componenti del loro stipendio
- righe 14–22: i due costruttori della classe
- righe 25–27: metodo [toString] che identifica uno specifico oggetto [PayStub]
- Righe 29 e successive: accessori pubblici ai campi privati della classe
La classe [ElementsSalaire] a cui si fa riferimento alla riga 11 della classe [FeuilleSalaire] sopra riportata contiene gli elementi che compongono una busta paga. La sua definizione è la seguente:
- riga 3: la classe implementa l'interfaccia Serializable poiché è un componente della PayrollClass, che deve essere serializzabile.
- riga 6: lo stipendio base
- riga 7: contributi previdenziali versati su questo stipendio base
- riga 8: assegni giornalieri per il mantenimento dei figli
- riga 9: le indennità giornaliere per i pasti dei bambini
- riga 10: lo stipendio netto da versare all'asilo nido
- righe 12–24: costruttori di classe
- righe 27–31: metodo [toString] che identifica uno specifico oggetto [ElementsSalaire]
- Righe 34 e successive: accessori pubblici ai campi privati della classe
5.10.3. La classe di implementazione [Metier] del livello [business]
La classe di implementazione [Metier] del livello [business] potrebbe essere la seguente:
- riga 5: L'annotazione @Transactional di Spring garantisce che ogni metodo della classe venga eseguito all'interno di una transazione.
- righe 9-10: riferimenti ai livelli [DAO] delle entità [Cotisation, Employe, Indemnite]
- righe 14–17: il metodo [calculatePayroll]
- righe 20–22: il metodo [findAllEmployees]
- Riga 24 e seguenti: gli accessori pubblici per i campi privati della classe
Domanda: Scrivi il codice per il metodo [findAllEmployees].
Domanda: Scrivi il codice per il metodo [calculatePayroll].
Tieni presente quanto segue:
- Il metodo per il calcolo dello stipendio è stato spiegato nella sezione 5.2.
- Se il parametro [SS] non corrisponde a nessun dipendente (il livello [DAO] ha restituito un puntatore nullo), il metodo genererà un'eccezione [PamException] con un codice di errore appropriato.
5.10.4. Test del livello [business]
Creiamo due programmi di test:
![]() |
Le classi di test [3] vengono create nel pacchetto [metier] [2] all'interno del ramo [Test Packages] [1] del progetto.
La classe [JUnitMetier_1] potrebbe avere questo aspetto:
Nella classe non sono presenti asserzioni Assert.assertCondition. Stiamo semplicemente cercando di calcolare alcuni stipendi in modo da poterli poi verificare manualmente. L'output sullo schermo ottenuto eseguendo la classe precedente è il seguente:
- Riga 4: busta paga di Justine Laverti
- riga 5: busta paga di Marie Jouveinal
- riga 6: l'eccezione dovuta al fatto che il dipendente con numero di previdenza sociale 'xx' non esiste.
Domanda: La riga 17 di [JUnitMetier_1] utilizza il bean Spring denominato metier. Fornisci la definizione di questo bean nel file [spring-config-metier-dao.xml].
La classe [JUnitMetier_2] potrebbe essere la seguente:
La classe [JUnitMetier_2] è una copia della classe [JUnitMetier_1], con la differenza che in questo caso le asserzioni sono state inserite nel metodo test01.
Domanda: Scrivi il metodo test01.
Quando si esegue la classe [JUnitMetier_2], se tutto va bene si ottengono i seguenti risultati:

5.11. Il livello [ui] dell'applicazione [PAM] – versione della console
Ora che il livello [business] è stato scritto, dobbiamo ancora scrivere il livello [ui] [1]:
![]() |
Creeremo due diverse implementazioni del livello [ui]: una versione console e una versione GUI Swing:
![]() |
5.11.1. La classe [ ui.console.Main]
Ci concentreremo innanzitutto sull'applicazione console implementata dalla classe [ui.console.Main] sopra riportata. Il suo funzionamento è stato descritto nella Sezione 5.3. Lo scheletro della classe [Main] potrebbe essere il seguente:
Domanda: Completa il codice sopra riportato.
5.11.2. Esecuzione
Per eseguire la classe [ui.console.Main], procedere come segue:
![]() |
- In [1], selezionare le proprietà del progetto,
- in [2], selezionare la proprietà [Esegui] del progetto,
- utilizzare il pulsante [3] per specificare la classe (nota come classe principale) da eseguire,
- selezionare la classe [4],
- la classe è riportata in [5]. Per essere eseguita, questa classe richiede tre argomenti (numero di previdenza sociale, numero di ore lavorate, numero di giorni lavorati). Questi argomenti vengono inseriti in [6],
- una volta fatto ciò, il progetto può essere eseguito [7]. La configurazione precedente implica che verrà eseguita la classe [ui.console.Main].
I risultati dell'esecuzione vengono visualizzati nella finestra [output]:
![]() | ![]() |
5.12. Il livello [ui] dell'applicazione [PAM] – versione grafica
Ora implementeremo il livello [ui] con un'interfaccia utente grafica:
![]() |
![]() |
- in [1], la classe [PamJFrame] dell'interfaccia grafica
- in [2]: l'interfaccia utente grafica
5.12.1. Un breve tutorial
Per creare l'interfaccia grafica utente, procedere come segue:
![]() |
- [1]: Creare un nuovo file utilizzando il pulsante [1] [Nuovo file...]
- [2]: Seleziona la categoria di file [Moduli GUI Swing], ovvero i moduli grafici
- [3]: Seleziona il tipo [Modulo JFrame], un tipo di modulo vuoto
![]() |
- [5]: Assegnare un nome al modulo; questo sarà anche il nome della classe
- [6]: Inserisci il modulo in un pacchetto
- [8]: Il modulo viene aggiunto alla struttura del progetto
- [9]: È possibile accedere al modulo tramite due viste: [Design] [9], che consente di progettare i vari componenti del modulo, e [Source] [10 sotto], che fornisce l'accesso al codice Java del modulo. In definitiva, un modulo è una classe Java come qualsiasi altra. La vista [Design] è uno strumento per la progettazione del modulo. Ogni volta che viene aggiunto un componente in modalità [Design], il codice Java viene aggiunto nella vista [Source] per tenerne conto.
![]() |
- [11]: L'elenco dei componenti Swing disponibili per un modulo si trova nella finestra [Tavolozza].
- [12]: La finestra [Inspector] mostra la struttura ad albero dei componenti del modulo. I componenti con una rappresentazione visiva si trovano nel ramo [JFrame]; gli altri nel ramo [Other Components].
![]() |
- In [13], selezioniamo un componente [JLabel] con un singolo clic
- In [14], lo trasciniamo sul modulo in modalità [Design]
- In [15], definiamo le proprietà di JLabel (testo, font).
![]() |
- In [16], il risultato.
- In [17], richiediamo un'anteprima del modulo
- In [18], il risultato
- In [19], l'etichetta [JLabel1] è stata aggiunta all'albero dei componenti nella finestra [Inspector]
![]() |
- In [20] e [21]: nella vista [Source] del modulo è stato aggiunto del codice Java per gestire il JLabel aggiunto.
Un tutorial sulla creazione di moduli con NetBeans è disponibile all'URL [http://www.netbeans.org/kb/trails/matisse.html].
5.12.2. L'interfaccia grafica [PamJFrame]
Realizzeremo la seguente interfaccia grafica utente:
![]() |
- in [1], l'interfaccia grafica
- in [2], la struttura ad albero dei suoi componenti: un JLabel e sei contenitori JPanel
JLabel1
![]() |
JPanel1
![]() | ![]() |
JPanel2
![]() | ![]() |
JPanel3
![]() | ![]() |
JPanel4
![]() | ![]() |
JPanel5
![]() | ![]() |
Esercizio pratico: Realizza l'interfaccia grafica vista in precedenza seguendo il tutorial [http://www.netbeans.org/kb/trails/matisse.html].
5.12.3. Eventi dell'interfaccia grafica utente
Lettura consigliata: il capitolo [Interfacce grafiche utente] in [rif2].
Gestiremo il clic sul pulsante [jButtonSalaire]. Per creare il metodo che gestisce questo evento, possiamo procedere come segue:
![]() |
Viene generato il gestore per il clic sul pulsante [JButtonSalaire]:
Viene generato anche il codice Java che associa il metodo precedente al clic sul pulsante [JButtonSalaire]:
Le righe 2–5 specificano che il clic (evt di tipo ActionPerformed) sul pulsante [jButtonSalaire] (riga 2) deve essere gestito dal metodo [jButtonSalaireActionPerformed] (riga 4).
Gestiremo anche l'evento [caretUpdate] (spostamento del cursore) sul campo di immissione [jTextFieldHT]. Per creare il gestore di questo evento, procediamo come in precedenza:
![]() |
Viene generato il gestore per l'evento [caretUpdate] sul campo di immissione [jTextFieldHT]:
Viene generato anche il codice Java che associa il metodo precedente all'evento [caretUpdate] sul campo di testo [jTextFieldHT]:
Le righe 1–4 indicano che l'evento [caretUpdate] (riga 2) sul pulsante [jTextFieldHT] (riga 1) deve essere gestito dal metodo [jTextFieldHTCaretUpdate] (riga 3).
5.12.4. Inizializzazione della GUI
Torniamo all'architettura della nostra applicazione:
![]() |
Il livello [ui] necessita di un riferimento al livello [business]. Ricordiamo come veniva ottenuto questo riferimento nell'applicazione console:
Il metodo è lo stesso nell'applicazione GUI. All'avvio dell'applicazione GUI, deve essere inizializzato anche il riferimento [IMetier metier] della riga 3 sopra riportata. Il codice generato per la GUI è attualmente il seguente:
- Righe 29–35: il metodo statico [main] che avvia l'applicazione
- riga 32: viene creata e resa visibile un'istanza della GUI [PamJFrame].
- righe 7-9: il costruttore della GUI.
- riga 8: chiamata al metodo [initComponents] definito alla riga 17. Questo metodo viene generato automaticamente in base al lavoro svolto in modalità [Design]. Non modificarlo.
- riga 21: il metodo che gestirà lo spostamento del cursore di input nel campo [jTextFieldHT]
- Riga 25: il metodo che gestirà il clic sul pulsante [jButtonSalaire]
Per aggiungere le nostre inizializzazioni al codice precedente, possiamo procedere come segue:
- Riga 4: chiamiamo un metodo personalizzato per eseguire le nostre inizializzazioni. Queste sono definite dal codice nelle righe 10–42
Domanda: Utilizzando i commenti come guida, completa il codice per la procedura [doMyInit].
5.12.5. Gestori di eventi
Domanda: Scrivi il metodo [jTextFieldHTCaretUpdate]. Questo metodo deve garantire che, se i dati nel campo [jTextFieldHT] non sono un numero reale >=0, il pulsante [jButtonSalaire] venga disabilitato.
Domanda: Scrivi il metodo [jButtonSalaireActionPerformed], che deve visualizzare la busta paga del dipendente selezionato in [jComboBoxEmployes].
5.12.6. Esecuzione della GUI
Per eseguire l'interfaccia grafica, modificare la configurazione [Run] del progetto:
![]() |
- In [1], inserire la classe dell'interfaccia grafica
Il progetto deve essere completo dei file di configurazione (persistence.xml, spring-config-metier-dao.xml) e della classe GUI. Avviare il DBMS di destinazione prima di eseguire il progetto.
5.13. Implementazione del livello JPA con EclipseLink
Ci interessa la seguente architettura in cui il livello JPA è ora implementato da EclipseLink:
![]() |
5.13.1. Il progetto NetBeans
Il nuovo progetto NetBeans viene creato copiando il progetto precedente:
![]() |
- in [1]: dopo aver cliccato con il tasto destro del mouse sul progetto Hibernate, selezionare Copia
- utilizzando il pulsante [2], selezionare la cartella principale per il nuovo progetto. Il nome della cartella appare in [3].
- in [4], assegnare un nome al nuovo progetto
- in [5], il nome della cartella del progetto
![]() |
- in [1], il nuovo progetto è stato creato. Ha lo stesso nome dell'originale,
- in [2] e [3], rinominarlo in [mv-pam-spring-eclipselink].
Il progetto deve essere modificato in due punti per adattarlo al nuovo livello JPA / EclipseLink:
- in [4], i file di configurazione di Spring devono essere modificati. È qui che si trova la configurazione del livello JPA.
- In [5], le librerie del progetto devono essere modificate: le librerie Hibernate devono essere sostituite con quelle di EclipseLink.
Cominciamo da quest'ultimo punto. Il file [pom.xml] per il nuovo progetto sarà il seguente:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-eclipselink</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- righe 73–82: dipendenze per l'implementazione JPA di EclipseLink,
- righe 19–24: il repository Maven per EclipseLink.
I file di configurazione Spring devono essere modificati per indicare che l'implementazione JPA è cambiata. In entrambi i file, cambia solo la sezione che configura il livello JPA. Ad esempio, in [spring-config-metier-dao.xml] abbiamo:
Le righe 19–36 configurano il livello JPA. L'implementazione JPA utilizzata è Hibernate (riga 22). Inoltre, il database di destinazione è [dbpam_hibernate] (riga 41).
Per passare a un'implementazione JPA/EclipseLink, le righe 19–35 sopra riportate vengono sostituite dalle righe seguenti:
- Riga 5: L'implementazione JPA utilizzata è EclipseLink
- riga 9: la proprietà databasePlatform imposta il DBMS di destinazione, in questo caso MySQL
- riga 11: per generare le tabelle del database quando viene istanziato il livello JPA. Qui, la proprietà è commentata.
- riga 7: per visualizzare sulla console le istruzioni SQL emesse dal livello JPA. Qui la proprietà è commentata.
Inoltre, il database di destinazione diventa [dbpam_eclipselink] (riga 4 sotto):
5.13.2. Esecuzione dei test
Prima di testare l'intera applicazione, è consigliabile verificare che i test JUnit vengano superati con la nuova implementazione JPA. Prima di eseguirli, inizieremo eliminando le tabelle dal database. Per farlo, nella scheda [Runtime] di NetBeans, se necessario, crea una connessione al database dbpam_eclipselink / MySQL5. Una volta connesso al database dbpam_eclipselink / MySQL5, puoi procedere all'eliminazione delle tabelle come mostrato di seguito:
- [1]: prima dell'eliminazione
- [2]: dopo l'eliminazione
![]() |
Una volta fatto ciò, è possibile eseguire il primo test sul livello [DAO]: InitDB, che popola il database. Per garantire che le tabelle precedentemente eliminate vengano ricreate dall'applicazione, assicurarsi che nella configurazione Spring JPA / EclipseLink sia presente la riga:
sia presente e non sia commentata.
Compiliamo il progetto e poi eseguiamo il test [JUnit InitDB]:
![]() |
- In [1], il test InitDB viene eseguito.
- In [2], fallisce. L'eccezione viene generata da Spring e non da un test che ha fallito.
Causa: org.springframework.beans.factory.BeanCreationException: Errore durante la creazione del bean con nome 'entityManagerFactory' definito nella risorsa del percorso di classe [spring-config-DAO.xml]: Fallita l'invocazione del metodo init; l'eccezione annidata è java.lang.IllegalStateException: È necessario avviare l'agente Java per utilizzare InstrumentationLoadTimeWeaver. Vedere la documentazione di Spring.
Spring indica che c'è un problema di configurazione. Il messaggio non è chiaro. Il motivo dell'eccezione è stato spiegato nella sezione 3.1.9 di [rif1]. Affinché la configurazione Spring/EclipseLink funzioni, la JVM che esegue l'applicazione deve essere avviata con un parametro specifico, un agente Java. Il formato di questo parametro è il seguente:
[spring-agent.jar] è l'agente Java richiesto dalla JVM per gestire la configurazione di Spring/EclipseLink.
Quando si esegue un progetto, è possibile passare argomenti alla JVM:
![]() |
- In [1], è possibile accedere alle proprietà del progetto
- In [2], le proprietà di esecuzione
- In [3], passare il parametro -javaagent alla JVM
5.13.3. InitDB
Ora siamo pronti a testare nuovamente [InitDB]. Questa volta, i risultati sono i seguenti:
![]() |
- In [1], il test ha avuto esito positivo
- In [2], nella scheda [Services], aggiorniamo la connessione di NetBeans al database [dbpam_eclipselink]
- In [3], sono state create quattro tabelle
![]() |
- in [5], visualizziamo il contenuto della tabella [employees]
- in [6], il risultato.
5.13.4. JUnitDao
L'esecuzione della classe di test [JUnitDao] potrebbe fallire, anche se ha avuto esito positivo con l'implementazione JPA/Hibernate. Per capire il motivo, analizziamo un esempio.
Il metodo sottoposto a test è il seguente metodo IndemniteDao.create:
- righe 15–22: il metodo in fase di test
Il metodo di prova è il seguente:
package dao;
...
public class JUnitDao {
// layers DAO
static private IEmployeDao employeDao;
static private IIndemniteDao indemniteDao;
static private ICotisationDao cotisationDao;
@BeforeClass
public static void init() {
// log
log("init");
// application configuration
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
// layers DAO
employeDao = (IEmployeDao) ctx.getBean("employeDao");
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
}
@Before()
public void clean() {
// empty the base
for (Employe employe : employeDao.findAll()) {
employeDao.destroy(employe);
}
for (Cotisation cotisation : cotisationDao.findAll()) {
cotisationDao.destroy(cotisation);
}
for (Indemnite indemnite : indemniteDao.findAll()) {
indemniteDao.destroy(indemnite);
}
}
// logs
private static void log(String message) {
System.out.println("----------- " + message);
}
// tests
….
@Test
public void test05() {
log("test05");
// we create two allowances with the same index
// violates index uniqueness constraint
boolean erreur = true;
Indemnite indemnite1 = null;
Indemnite indemnite2 = null;
Throwable th = null;
try {
indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
erreur = false;
} catch (PamException ex) {
th = ex;
// checks
Assert.assertEquals(31, ex.getCode());
} catch (Throwable th1) {
th = th1;
}
// checks
Assert.assertTrue(erreur);
// exception chain
System.out.println("Chaîne des exceptions --------------------------------------");
System.out.println(th.getClass().getName());
while (th.getCause() != null) {
th = th.getCause();
System.out.println(th.getClass().getName());
}
// the 1st allowance had to be continued
Indemnite indemnite = indemniteDao.find(indemnite1.getId());
// check
Assert.assertNotNull(indemnite);
Assert.assertEquals(1, indemnite.getIndice());
Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
// the second indemnity should not have persisted
List<Indemnite> indemnites = indemniteDao.findAll();
int nbIndemnites = indemnites.size();
Assert.assertEquals(nbIndemnites, 1);
}
...
}
Domanda: Spiega cosa fa il test test05 e indica i risultati attesi.
I risultati ottenuti utilizzando un livello JPA/Hibernate sono i seguenti:
Il test ha esito positivo, il che significa che le asserzioni sono state verificate e che il metodo di test non ha generato alcuna eccezione.
Domanda: Spiega cosa è successo.
I risultati ottenuti con un livello JPA/EclipseLink sono i seguenti:
Come in precedenza con Hibernate, il test viene superato, il che significa che le asserzioni sono state verificate e che dal metodo di test non viene generata alcuna eccezione.
Domanda: Spiega cosa è successo.
Domanda: Da questi due esempi, cosa possiamo concludere sull'intercambiabilità delle implementazioni JPA? È completa in questo caso?
5.13.5. Gli altri test
Una volta che il livello [DAO] è stato testato e ritenuto corretto, possiamo passare a testare il livello [business] e il progetto stesso nella sua versione da console o grafica. La modifica dell'implementazione JPA non ha alcun effetto sui livelli [business] e [UI]; pertanto, se questi livelli funzionavano con Hibernate, funzioneranno con EclipseLink con poche eccezioni: l'esempio precedente mostra che le eccezioni generate dai livelli [DAO] possono differire. Pertanto, nel caso del test, Spring / JPA / Hibernate genera una [PamException], un'eccezione specifica dell'applicazione [pam], mentre Spring / JPA / EclipseLink genera una [TransactionSystemException], un'eccezione del framework Spring. Se, nel caso di test, il livello [ui] si aspetta una [PamException] perché è stato costruito con Hibernate, non funzionerà più quando si passa a EclipseLink.
5.13.6. Lavoro da svolgere
Compito pratico: Riprovare le applicazioni console e Swing con diversi DBMS: MySQL5, Oracle XE, SQL Server.





















































































