This%work%is%licensed%under%a%Crea2ve%Commons%A6ribu2on8Noncommercial8Share%Alike%3.0%United%States%
See%h6p://crea2vecommons.org/licenses/by8nc8sa/3.0/us/%for%details%
Gestione della persistenza
Le nostre applicazioni sono sviluppate con linguaggi OO
(lo stesso vale anche per i moderni sistemi informativi)
mentre la persistenza dei dati è affidata ai DBMS relazionali
OO: scalabilità e riusabilità
RDBMS: affidabilità, efficienza, efficacia
Esistono differenze significative tra queste due tecnologie:
differenze tecnologiche
differenze culturali
Si parla di conflitto di impedenza
2
Conflitto di impedenza (1/4)
Definito anche come “disaccoppiamento di impedenza”
(o impedance mismatch) tra base di dati e linguaggio
linguaggi: operazioni su singole variabili o oggetti
SQL: operazioni su relazioni (insiemi di ennuple)
Differenze di accesso ai dati e correlazione:
linguaggio: dipende dal paradigma e dai tipi disponibili;
ad esempio scansione di liste o “navigazione” tra oggetti
SQL: join (ottimizzabile)
3
Conflitto di impedenza (2/4)
Differenze sui tipi di dato primitivi:
linguaggi: numeri, stringhe, booleani
SQL: CHAR, VARCHAR, DATE, ...
Differenze sui costruttori di tipo:
linguaggio: dipende dal paradigma
SQL: relazioni e ennuple
4
Conflitto di impedenza (3/4)
In generale, i dati risiedono nel database (DB) mentre la
logica applicativa viene implementata da oggetti
Dal modello concettuale si può derivare (una prima
versione di)
classi che implementano la logica applicativa (diciamo
che queste classi implementano il modello di dominio, o
più semplicemente il modello)
classi "facade" che offrono le operazioni
(metodi) dell’applicazione
schema relazionale per memorizzare i dati
questa attività può essere fatta anche successivamente
in alcuni casi (sistemi legacy) il database potrebbe esistere già e
potrebbe non essere modificabile
• quindi tutte le attività di progetto sono condizionate da questo vincolo
5
Metodologie per la gestione della persistenza
In generale possiamo pensare che le operazioni (metodi)
degli oggetti facade
caricano tuple dalla base di dati e le usano per creare gli
oggetti del modello
effettuano le operazioni della logica applicativa
(es. evadi ordine)
salvano in maniera persistente gli oggetti del modello
nella base di dati
L'interazione con il DB è una operazione critica: esistono
diverse metodologie (di complessità crescente) per
realizzare questa interazione
forza bruta
pattern DAO
framework ORM (..alla magistrale..)
7
Forza bruta
È la tecnica più semplice per gestire la persistenza
forte accoppiamento con la sorgente dati
Consiste nello scrivere dentro le classi del modello un
insieme di metodi che implementano le operazioni CRUD
Operazioni CRUD
Create: inserimento di una tupla (che rappresenta un oggetto) nel
database (INSERT)
Retrieve: ricerca di una tupla secondo un qualche criterio di ricerca
(SELECT)
Update: aggiornamento di una tupla nel database (UPDATE)
Delete: eliminazione di una tupla nel database (DELETE)
Ci possono essere diverse operazioni di Retrieve
diversi criteri: “per chiave” vs. “per attributo” (es. ricerca cliente per
codice, per nome, etc.)
diversi risultati (un oggetto, una collezione di oggetti) a seconda del
criterio
8
Forza bruta in sintesi (1/2)
Per ogni classe MyC che rappresenta una entità del
dominio, si definiscono:
un metodo doRetrieveByKey(X key) che
restituisce un oggetto istanza di MyC i cui dati sono letti
dal database
• tipicamente da una tabella che è stata derivata dalla stessa
classe del modello di dominio che ha dato origine a MyC
recupera i dati per chiave
9
Forza bruta in sintesi (2/2)
uno o più metodi doRetrieveByCond(…) che
restituiscono una collezione di oggetti istanza della
classe MyC che soddisfano una qualche condizione
(basata sui parametri del metodo)
10
Il pattern Data Access Object
La soluzione Forza bruta non è particolarmente
conveniente
L'accoppiamento tra la sorgente dati e le classi del
modello è molto forte
Le classi del modello sono poco coese (responsabilità
eterogenee)
Molto difficile iniziare a sviluppare senza preoccuparsi di
tanti (troppi !) dettagli e senza un ambiente operativo
complesso (java + dbms)
Una soluzione "naturale" è quella di affidare le
responsabilità di interagire con il database ad opportune
classi
Questo è l'approccio suggerito dal pattern
Data Access Object (DAO)
11
Il pattern DAO
Un pattern è un “modo di fare le cose” che si è dimostrato
efficace e che per questo si tende ad applicare di nuovo
Il pattern DAO è uno degli standard “J2EE design patterns”;
rappresenta un possibile modo di separare:
logica di business (es: Servlet, JSP, …)
logica di persistenza (es: r/w su DB, …)
Infatti, i componenti della logica di business non dovrebbero
mai contenere codice che accede direttamente al database!
scarsa manutenibilità
sovrapposizione di responsabilità
Solo gli oggetti previsti dal pattern DAO
hanno il permesso di “vedere” il DB
espongono metodi di accesso per tutti gli altri componenti
12
DAO: Caratteristiche principali (1/2)
I valori scambiati tra DB e il resto dell’applicazione sono
racchiusi in oggetti detti Data Transfer Object (DTO):
campi privati per contenere i dati da leggere/scrivere su
db
metodi getter e setter per accedere dall’esterno a tali
campi
metodi di utilità (confronto, stampa, …)
Le operazioni che coinvolgono tali oggetti sono raggruppati
in interfacce che definiscono i Data Access Object (DAO)
disponibili:
metodi CRUD
altri metodi
13
DAO: Caratteristiche principali (2/2)
Le implementazioni di tali interfacce (spesso indicate come
oggetti DAO, per brevità) permettono l’effettivo accesso al
database
diverse implementazione possono permettere (e rendere
uniforme) l'accesso a DB di diversi vendor
anche se si fa uso di un solo database, tale separazione
migliora comunque la divisione delle responsabilità tra le
parti dell’applicazione
diventa facile migrare l’applicazione su DB diversi
Gli oggetti DAO non sono istanziati direttamente dai
componenti, ma:
possono essere ottenuti attraverso metodi factory
14
Pattern DAO con classi Factory
Una unica factory (anche astratta)
fornisce specifiche per le factory concrete
espone un metodo creazionale parametrico per ottenere
factory concrete
Una factory concreta per ogni tipo di DB supportato
permette di ottenere oggetti DAO appropriati al
corrispondente tipo di DB
può gestire aspetti quali ottenimento della connessione,
autenticazione, ...
Un oggetto DTO per ogni tipo di entità che si vuole
rappresentare
Una interfaccia DAO per ogni oggetto DTO
Una implementazione dell'interfaccia DAO di un DTO per
ciascun DB supportato
15
Pattern DAO con classi Factory
16
DAO in pratica
Per ogni classe Entità del modello di dominio creiamo
(in un opportuno package) una interfaccia DAOEntità
con (le signature dei) metodi che realizzano le operazioni
CRUD
Queste interface sono implementate da classi
(organizzate in opportuni package)
questa scomposizione permette maggiore flessibilità
20
Esempio (in UML)
Prodotto
1 Ordine
CodiceProdotto * RigaOrdine * 1 CodiceOrdine
Nome Quantità
Data
Descrizione NumeroLinea
stato
Costo
1
Cliente
CodiceCliente
Nome
Indirizzo
21
Esempio: ProdottoDAO
public interface ProdottoDAO {
22
Esempio: ProdottoDAOImpl
public class ProdottoDAOImpl implements ProdottoDAO {
…
try {
…//uses JDBC to connect to DB
} catch (SQLException sqle) {
throw new PersistenceException(sqle);
} finally {
…
}
return prodotti;
}
…
}
23
Interazione Facade-DAO: Esempio
ProdottoDAO
Prodotto
24
Caso di studio
package modello;
import java.util.*;
import persistenza.*;
public Facade() {
clienteDao = new ClienteDAOImpl();
}
ORM e Hibernate 27
JDBC - Concetti Fondamentali
• Riferimenti
– http://java.sun.com/javase/6/docs/technotes/guides/jdbc/
• Introduzione
– Architettura
– Tipi di driver
• Un Esempio
– Driver e Driver Manager
– Connection
– PreparedStatement
– ResultSet
– SQLException
Introduzione
• Da Java Tutorial
– The JDBC API is a Java API that can access any kind
of tabular data, especially data stored in a Relational
Database
– JDBC helps you to write java applications that
manage these three programming activities:
• Connect to a data source, like a database
• Send queries and update statements to the database
• Retrieve and process the results received from the database
in answer to your query
• Dalla documentazione JavaDoc
– java.sql: classi fondamentali
– javax.sql: estensioni
Introduzione
• Una applicazione che accede (in lettura e/
o scrittura) ad una sorgente di dati (nel
nostro caso un database relazionale) ha
bisogno di fare le seguenti operazioni:
– 1. aprire una connessione alla sorgente dati
– 2. inviare attraverso la connessione istruzioni
(di interrogazione e aggiornamento) alla
sorgente dati
– 3. processare i risultati ricevuti dalla sorgente
dati in risposta alle istruzioni inviate
Introduzione
• Le nostre sorgenti dati sono database
relazionali, gestiti da un DBMS
• Ogni DBMS espone una API
• Le applicazioni Java interagiscono con le
API del DBMS attraverso un driver
Architettura
Applicazione%Java% utilizza
le interfacce
java.sql
Driver%Manager% e javax.sql
implementano
Driver% Driver% Driver%
java.sql e,
in alcuni casi
IBM javax.sql
Postgres% Oracle
Db2
Data-source
Tipi di Driver
• Type 1 - drivers that implement the JDBC API as a mapping to
another data access API, such as ODBC. Drivers of this type are
generally dependent on a native library, which limits their portability.
The JDBC-ODBC Bridge driver is an example of a Type 1 driver.
• Type 2 - drivers that are written partly in the Java programming
language and partly in native code. These drivers use a native client
library specific to the data source to which they connect. Again,
because of the native code, their portability is limited.
• Type 3 - drivers that use a pure Java client and communicate with a
middleware server using a database-independent protocol. The
middleware server then communicates the client s requests to the
data source.
• Type 4 - drivers that are pure Java and implement the network
protocol for a specific data source. The client connects directly to the
data source.
Confronto tra driver
• Type 1 (Ponte JDBC-ODBC)
– prestazioni scadenti
– non indipendente dalla piattaforma
– fornito a corredo di SDK
• Type 2
– migliori prestazioni
– non indipendente dalla piattaforma
• Type 3
– client indipendente dalla piattaforma
– servizi avanzati (caching)
– architettura complessa
• Type 4
– indipendente dalla piattaforma
– buone prestazioni
– scaricabile dinamicamente
• Per approfondimenti:
– http://java.sun.com/products/jdbc/driverdesc.html
Esempio
• Obiettivo: esemplificare l API JDBC. Quindi:
– scriviamo codice che ha unicamente lo scopo di
evidenziare l'uso della API JDBC
– non ci preoccupiamo della qualità del codice Java
• Operazioni
– inserimento di una tupla nel db
– cancellazione di una tupla
– ricerca di un tupla per chiave primaria
– ricerca di un insieme di tuple (per qualche
proprietà)
Esempio
• Consideriamo la classe Studente:
import java.util.Date;
public class Studente {
private String matricola;
private String nome;
private String cognome;
private Date dataNascita;
public Studente(){}
public String getNome() {
return this.nome;
}
public void setNome(String nome) {
this.nome = nome;
}
// seguono tutti gli altri metodi getter e setter
}
Esempio
e il database university:
CREATE DATABASE university;
• Classe DriverManager
• Interfaccia Driver
• Interfaccia Connection
• Interfaccia Statement
• Interfaccia ResultSet
• Eccezione SQLException
Driver
• Rappresenta%il%punto%di%partenza%per%o6enere%
una%connessione%a%un%DBMS%%
• ︎I%produ6ori%di%driver%JDBC%implementano%
l”interfaccia%Driver%(mediante%opportuna%
classe)%affinché%possa%funzionare%con%un%2po%
par2colare%di%DBMS%%
• ︎ La%classe%che%implementa%Driver&può%essere%
considerata%la%“factory”%per%altri%oggeV%JDBC%
– ad%esempio,%oggeV%di%2po%Connec2on%%
Operazione n.1
Caricare il Driver
• Creare un oggetto della classe Driver
Driver d = new org.postgresql.Driver();
• Esempio 1 (cont.)
PreparedStatement statement;
String insert_stm = "insert into studente(nome, cognome,
datanascita, matricola) values (?, ?,?,?)";
statement = connection.prepareStatement(insert_stm );
statement.setString(1, studente.getNome());
statement.setString(2, studente.getCognome());
statement.setString(4, studente.getMatricola());
Operazione n.3
Istruzione SQL
• Una volta assegnati i valori ai parametri,
l'istruzione può eseguita
• Distinguiamo due tipi di operazioni:
– Modifiche (insert, update, delete)
• modificano lo stato del database
– interrogazioni (select)
• non modificano lo stato del database
• ritornano una sequenza di tuple
Operazione n.3
Istruzione SQL
• Aggiornamenti (insert, delete, update)
– Si invoca il metodo executeUpdate() sull'oggetto
PrepareStatement
• Esempio 1 (cont.)
PreparedStatement statement;
String insert_stm = "insert into studente(nome, cognome,
dataNascita, matricola) values (?,?,?,?)";
statement = connection.prepareStatement(insert_stm );
statement.setString(1, studente.getNome());
statement.setString(2, studente.getCognome());
long secs = studente.getDataNascita().getTime());
statement.setDate(3, new java.sql.Date(secs));
statement.setString(4, studente.getMatricola());
statement.executeUpdate();
Operazione n.3
Istruzione SQL
• Interrogazioni (select)
– vengono eseguite invocando il metodo
executeQuery()
– che ritorna il risultato in un oggetto ResultSet
• Esempio 2
PreparedStatement statement;
String query = "select * from studente
where matricola=?";
statement = connection.prepareStatement(query);
statement.setString(1, matricola);
ResultSet result = statement.executeQuery();
Oggetto ResultSet
• Un oggetto della classe ResultSet rappresenta la
collezione di ennuple restituita da una query SQL
(istruzione SELECT)
• Per gestire il risultato offre vari metodi:
– metodo boolean next() per scorrere le ennuple
(analogo ad un iteratore)
– metodi getXXX(String nome-colonna) per o
getXXX(int posizione-colonna) per acquisire i
valori degli attributi della tupla
Es.: int getInt(…);
Es.: String getString(…);
• I valori NULL sono convertiti in null, 0, oppure
false, dipendentemente dal tipo di metodo getXXX
Operazione n.4
Gestire il risultato di una query
• Il metodo next()
– Sposta il cursore dalla riga corrente alla successiva. La prima
chiamata a next() ottiene la prima riga, la seconda chiamata ottiene la
seconda riga e cosi via.
– Returns: true se la nuova riga corrente e’ valida, falso se non vi sono
piu’ righe
– Se non vi sono piu’ righe, il cursore e’ posizionato DOPO l’ultima riga,
ed ogni invocazione di qualsiasi metodo di ResultSet che richiede
una riga corrente, sollevera’ una SQLException
public List<Studente> findAll() {
Connection connection = this.dataSource.getConnection();
List<Studente> studenti = new LinkedList<>();
try {
Studente studente;
PreparedStatement statement;
String query = "select * from studente";
statement = connection.prepareStatement(query);
ResultSet result = statement.executeQuery();
while (result.next()) {
studente = new Studente();
studente.setMatricola(result.getString("matricola"));
studente.setNome(result.getString("nome"));
studente.setCognome(result.getString("cognome"));
long secs = result.getDate("datanascita").getTime();
studente.setDataNascita(new java.util.Date(secs));
studenti.add(studente);
}
} catch (SQLException e) {
throw new PersistenceException(e.getMessage());
} finally {
try {
connection.close();
} catch (SQLException e) {
throw new PersistenceException(e.getMessage());
}
}
return studenti;
}
Operazione n.4
Gestire il risultato di una query%
Studente studente;
PreparedStatement statement;
String query = "select * from studente";
statement = connection.prepareStatement(query);
ResultSet result = statement.executeQuery();
while (result.next()) {
studente = new Studente();
studente.setMatricola(result.getString("matricola"));
studente.setNome(result.getString("nome"));
studente.setCognome(result.getString("cognome"));
long secs = result.getDate("datanascita").getTime();
studente.setDataNascita(new java.util.Date(secs));
studenti.add(studente);
}%
Controllo sul tipo di Dato
• l%dialogo%tra%una%applicazione%Java%e%i%DB%so6ostan2%
richiede%che%i%2pi%di%dato%SQL%siano%mappa2%in%
corrisponden2%2pi%di%dato%Java%e%viceversa.%%
• La%conversione%(SQL2Java)%riguarda%tre%categorie:%%
– Tipi%SQL%che%hanno%direV%equivalen2%in%Java%%
• (es.%il%2po%SQL%INTEGER&%!%Java%int)%%
– Tipi%SQL%che%possono%essere%conver22%negli%equivalen2%
2pi%Java%%
• (es.%i%2pi%SQL%CHAR%e%!%Java%String)%%
– Tipi%SQL%che%sono%unici%(una%minoranza)%e%che%necessitano%
la%creazione%di%uno%speciale%ogge6o%Java%%
• (es.%il%2po%SQL%DATE%si%converte%nell’ogge6o%Date%definito%
dall’omonima%classe%Java)%%
Nota sul tipo Data
• Le istruzioni
long secs = student.getBirthDate().getTime());
statement.setDate(4, new java.sql.Date(secs));
• Analizziamo%ora%i%principali%problemi%dovu2%al%
confli6o%di%impedenza%e%ne%presen2amo%le%
soluzioni%%
• A%scopo%didaVco%lavoriamo%su%DAO,%ma%le%
considerazioni%che%facciamo%seguendo%questa%
soluzione%hanno%validità%generale!!%%
• Le%problema2che%rela2ve%al%confli6o%di%
impedenza%sono%alla%base%della%metodologia%
Object%Rela2onal%Mapping%(ORM)%che%esula%dagli%
obieVvi%del%corso%
Risolvere il conflitto di impedenza
• Nel%modello%OO,%le%relazioni%tra%oggeV%sono%
realizzate%con%riferimen2,%mentre%nel%modello%
relazionale%sono%realizzate%con%i%valori%%
• Vediamo%problemi%e%soluzioni%rela2vamente%a:%
– Retrieve:%dal%DB%agli%oggeV%(ricostruzione%degli%
oggeV%a%par2re%da%da2%persisten2)%%
• Create:%dagli%oggeV%al%DB%
– (memorizzazione%persistente%degli%oggeV%nel%DB)%%
Esempio%di%riferimento%
Studente&
Gruppo&
*% 8matricola:%String%
8id:%Long% 8nome:%String%
8Nome:%String% 8cognome:%String%
8dataNascita:%Date%
gruppo& studente&
id% bigint% nullable=false% matricola% character(8)% nullable=false%
PK% PK%
nome% varchar(255)% nullable=false% nome% varchar(255)% nullable=false%
cognme% varchar(255)% nullable=false%
data_nascita% date% nullable=true%
gruppo_id% bigint% FK%gruppo.id%
Esempio%di%riferimento%
gruppo& studente&
id% bigint% nullable=false% matricola% character(8)% nullable=false%
PK% PK%
nome% varchar(255)% nullable=false% nome% varchar(255)% nullable=false%
cognme% varchar(255)% nullable=false%
data_nascita% date% nullable=true%
gruppo_id% bigint% FK%gruppo.id%
gruppo% studente%
ID& NOME& matricola& nome& cognome& data_nascita& gruppo_id&
…% …% …% …% …% …%
Esempio%di%riferimento%
public class Gruppo {
private Long id;
private String nome;
private Set<Studente> studenti;
public Gruppo(){
this.studenti = new HashSet<>();
}
// getter e setter
public Studente(){}
// getter e setter
// hashCode e equals
%
Ricostruzione degli oggetti dal db
• Consideriamo%la%ricostruzione%di%un%ogge6o%Gruppo,%
ad%esempio%per%definire%il%metodo%%
Gruppo findByPrimaryKey(Long id)%
– nella%classe%Gruppo:%%
• una%collezione%di%riferimen2%a%oggeV%Studente;%%
– nel%database:%%
• tabella%gruppo
• tabella%studente (foreign%key%con%gruppo)%
• Per%ricostruire%un%ogge6o%Gruppo "completo"%devo%
ricostruire%la%collezione%di%oggeV%Studente (e%
anche%ques2%oggeV%devono%essere%"comple2"?!)
Ricostruzione degli oggetti dal db
• Come ricostruiamo l'oggetto Gruppo "completo"?
• Se voglio ricostruire la collezione di riferimenti agli
studenti
– eseguo un join tra le tabelle gruppo e studente
– itero sul ResultSet e costruisco i vari oggetti Gruppo e
Studente (aggiungendone i riferimenti alla collezione)
• NB: che join usiamo?
– è possibile che nella tabella gruppo ci siano tuple per le
quali non ci sono righe nella tabella studente?
– se la risposta è SI, allora devo usare un outer join
Esempio%
• Scriviamo%la%classe%DAO%per%Gruppo%
• Concentriamoci%sul%metodo%che%implementa%
l'operazione%CRUD%di%RETRIEVE:%%
– Gruppo%findByPrimaryKey(Log%id)%
Esempio%con%Join%
public Gruppo findByPrimaryKeyJoin(Long id) {
Connection connection = this.dataSource.getConnection();
Gruppo gruppo = null;
try {
PreparedStatement statement;
String query = "SELECT g.id AS g_id, g.nome AS g_nome, "
+ "s.matricola AS matricola, s.nome AS nome, "
+ "s.cognome AS cognome, "
+ "s.datanascita AS datanascita "
+ "FROM gruppo g LEFT OUTER JOIN "
+ "studente s ON g.id=s.gruppo_id "
+ "WHERE g.id = ?";
statement = connection.prepareStatement(query);
statement.setLong(1, id);
logger.debug(statement);
ResultSet result = statement.executeQuery();
Esempio%con%Join%(cont.)%
boolean primaRiga = true;
while (result.next()) {
if (primaRiga) {
gruppo = new Gruppo();
gruppo.setId(result.getLong("g_id"));
gruppo.setNome(result.getString("g_nome"));
primaRiga = false;
}
if(result.getString("matricola")!=null){
Studente studente = new Studente();
studente.setMatricola(result.getString("matricola"));
studente.setNome(result.getString("nome"));
studente.setCognome(result.getString("cognome"));
long secs = result.getDate("datanascita").getTime();
studente.setDataNascita(new java.util.Date(secs));
gruppo.addStudente(studente);
}
}
} catch (SQLException e) { ///…
Esempio%con%Join:%note%
• Query%SQL:%
SELECT g.id AS g_id, g.nome AS g_nome,
s.matricola AS matricola, s.nome AS nome,
s.cognome AS cognome,
s.datanascita AS datanascita
FROM gruppo g LEFT OUTER JOIN studente s
ON g.id=s.gruppo_id
WHERE g.id = ?
• Perchè%LEFT%OUTER%JOIN?%
– gruppi%senza%studen2%non%verrebbero%trova2%
• Perchè%gli%alias%nella%target%list?%
– JDBC%fa%riferimento%ai%nomi%delle%colonne%
Problemi%soluzione%Join%
• La%soluzione%con%il%join%ha%un%(potenzialmente%
grosso)%problema%di%prestazioni%
• Non%è%scontato%che%le%operazioni%che%devo%fare%
sui%da2%in%memoria%richiedano%i%da2%degli%oggeV%
collega2%(ad%esempio%mi%servono%solo%I%nomi%dei%
gruppi)%%
• Per%ovviare%a%questo%problema,%facciamo%in%modo%
che%gli%oggeV%della%collezione%siano%carica2%solo%
se%necessario,%cioè%solo%se%viene%invocato%un%%
metodo%che%richiede%l'accesso%alla%collezioni%
– nel%nostro%esempio%il%metodo%getStuden2()%
Ricostruzione degli oggetti dal db
• Ma che cosa è un "oggetto completo"?
– con un modello di dominio complesso questa
strategia può portarci a caricare una rete
molto vasta di oggetti
(esempio: Professore"Corso"Gruppo!Studente!…)
• Fino a quando devo costruire oggetti
"inseguendo i riferimenti"?
– e quindi calcolando costosi join?
Ricostruzione degli oggetti dal db
• Possiamo dare una risposta a queste domande
analizzando i casi d'uso e il modello del dominio
• In particolare l'inseguimento dei riferimenti tra
oggetti è guidato dalle operazioni offerte dal sistema
• Nel progettare le classi, i riferimenti vanno messi
considerando la direzione in cui le corrispondenti
associazioni sono navigate dalle operazioni del
sistema
• questo aspetto non ha rilevanza nella progettazione del
database, perché i riferimenti sono implementati tramite
valori e l'operatore di join non è direzionale
Ricostruzione degli oggetti dal db
• E quando non ha senso costruire un oggetto
completo, come mi comporto?
• (i metodi che richiedono l'inseguimento di un
riferimento, se invocati devono ritornare l'oggetto
corretto!)
• In questo caso ricorriamo ad una tecnica, detta
Caricamento Pigro, implementata tramite
l'applicazione del Pattern Proxy
Lazy Load (caricamento pigro)
• Consideriamo la classe Gruppo
• Se non è ragionevole pensare che per caricare i
dati di un gruppo si debba ricostruire l'intera rete
di oggetti collegati dai riferimenti nelle collezioni,
possiamo pensare che la collezione di studenti
associati al gruppo possa essere caricata solo
quando richiesta:
– quando invochiamo il metodo getStudenti() su un
oggetto Gruppo
• Questa strategia si chiama Lazy Load
(caricamento pigro)
Lazy Load (caricamento pigro)
• In pratica quello che vogliamo fare è caricare i dati solo quando questi
sono effettivamente richiesti
• Vediamo come dovrebbe agire il metodo getStudenti () della
classe Gruppo seguendo questa strategia
– quando invocato il metodo getStudenti() esegue la query per costruire gli
oggetti Studente associati al gruppo, aggiunge i riferimenti a questi oggetti
nella collezione dell'oggetto Gruppo, e restituisce la collezione
• E' importante osservare che se il metodo non viene mai invocato, il
costo di caricare i dati relativi agli ordini non viene mai pagato
• Non possiamo però implementare questa strategia nella classe
Gruppo: metteremmo codice di gestione della persistenza in una
classe del dominio
• Per implementare bene la strategia Lazy Load senza ridurre la coesione
nella classe del modello è utile fare riferimento al pattern Proxy
Il pattern proxy: structure
Subject
Client
request()
Proxy RealSubject
request() request()
Pattern Proxy:
Participants and Responsibilities
request()
request()
Lazy Load e classi proxy
• I DAO non restituiscolo un oggetto
completo ma un proxy
– Il proxy ha il compito di gestire il caricamento
degli oggetti collegati
– Il proxy deve contenere tutte le informazioni
necessarie per effettuare il caricamento lazy
– Gli oggetti collegati vengono caricati solo su
richiesta
– Il proxy nasconde l'accesso al database
Lazy Load: dettagli
• Quando chiediamo ad un DAO di recuperare un
oggetto (o una collezioni di oggetti), questi esegue
una query sul DB, e con i dati recuperati istanzia e
restituisce l'oggetto richiesto
• L'oggetto restituito non è però del tipo richiesto,
ma è un proxy, il cui tipo estende il quello richiesto
e riscrive i metodi che devono implementare le
operazioni onerose
– nell'esempio, l'operazione onerosa consiste nel
metodo getStudenti()
Lazy Load
public%class%GruppoDaoJDBC%{%
//%… %%
public&Gruppo&findByPrimaryKey(Long&id)&{&
% %Connec2on%connec2on%=%this.dataSource.getConnec-on();&
% %Gruppo%gruppo%=%null;&
% %try&{&
% % %PreparedStatement%statement;%
% % %String%query%=%"select%*%from%gruppo%where%id%=%?";%
% % %statement%=%connec2on.prepareStatement(query);%
% % %statement.setLong(1,%id);%
% % %ResultSet%result%=%statement.executeQuery();%
% % %if&(result.next())&{&
% % % %gruppo%=%new&GruppoProxy();&
% % % %gruppo.setId(result.getLong("id")); % % % %%
% % % %gruppo.setNome(result.getString("nome"));%
% % %}%
% %}%catch&(SQLExcep-on&e)&{… % %} %%
% %return&gruppo;&
%}%
}%
Lazy Load: implementazione Proxy
public class GruppoProxy extends Gruppo {
private DataSource dataSource;
public GruppoProxy() {
this.dataSource = new DataSource();
}
gruppo& studente&
id% bigint% nullable=false% matricola% studente&
character(8)% nullable=false%
PK% PK%
matricola% character(8)% nullable=false%
nome% varchar(255)% nullable=false% nome% varchar(255)% PK%
nullable=false%
nome%
cognome% varchar(255)% nullable=false%
Universita& cognme%
data_nascita% varchar(255)%
date% nullable=false%
nullable=true%
public Gruppo(){
this.studenti = new HashSet<>();
}
gruppo& studente&
id% bigint% nullable=false% matricola% studente&
character(8)% nullable=false%
PK% PK%
matricola% character(8)% nullable=false%
nome% varchar(255)% nullable=false% nome% varchar(255)% PK%
nullable=false%
nome%
cognome% varchar(255)% nullable=false%
Universita& cognme%
data_nascita% varchar(255)%
date% nullable=false%
nullable=true%