MODELLO AD OGGETTI PER LE BASI DI DATI E ANALISI DI

MODELLO AD OGGETTI PER LE BASI DI DATI E ANALISI DI
PRODOTTI COMMERCIALI
di
Luca Carnini
Tesina presentata per la discussione del
diploma di laurea in
Ingegneria informatica
Politecnico di Milano sede di Como
Capitolo 1 ........................................................................................................4
Introduzione ..............................................................................................4
Lavoro svolto.............................................................................................7
Capitolo 2 ........................................................................................................9
Database object oriented............................................................................9
Evoluzione delle basi di dati ....................................................................................9
Basi di dati Relazionali.......................................................................................9
Modello ad oggetti per le basi di dati.....................................................................11
Basi di dati relazionali e a oggetti ....................................................................12
Basi di dati a oggetti ...............................................................................................13
Caratteristiche di un ODBMS...............................................................................14
Modello dei dati ...............................................................................................14
Schema del database.........................................................................................16
Architettura client-server.................................................................................17
Accessi concorrenti..........................................................................................17
Distribuzione dei dati ......................................................................................18
Capitolo3 .......................................................................................................20
Prodotti Object Oriented........................................................................20
OODBMS Commerciali .......................................................................................20
Classificazione........................................................................................................20
ODBMS “puri” ................................................................................................20
Odbms integrati in piattaforme di sviluppo....................................................21
Progetti Universitari o di privati.....................................................................21
ObjectDB...............................................................................................................22
Objectivity - DB ....................................................................................................28
Jyd ODB ................................................................................................................33
Eye DB...................................................................................................................38
Versant...................................................................................................................41
Orient ....................................................................................................................45
Neo Access.............................................................................................................49
tabella comparativa..................................................................................52
Capitolo4 .......................................................................................................54
Applicazione d’esempio...........................................................................54
Descrzione .............................................................................................................54
Specifiche .........................................................................................................55
Modello dei dati ...............................................................................................57
Interfaccia.........................................................................................................58
Objectivity DB ......................................................................................................60
Connessione al database ..................................................................................60
Inserimento di un oggetto nel database ...........................................................62
Recupero di un oggetto dal database ...............................................................63
2
Modifica di un oggetto nel database ................................................................65
Object DB..............................................................................................................66
Connessione al database ..................................................................................67
Aggiunta di un oggetto al database..................................................................67
Recupero di un oggetto dal database ...............................................................68
Query...............................................................................................................69
Modifica di oggetti nel database.......................................................................69
Versant...................................................................................................................69
Connessione al database ..................................................................................69
Inserimento di oggetti nel database .................................................................70
Modifica di oggetti nel database.......................................................................71
Recupero di oggetti dal database .....................................................................72
Capitolo 5 ......................................................................................................74
3
Capitolo 1
INTRODUZIONE
La raccolta, conservazione e organizzazione dei dati è una delle più importanti
applicazione dei sistemi informatici. I sistemi informatici garantiscano che i dati
raccolti vengano conservati in modo permanente, vengano continuamente
aggiornati seguendo lo sviluppo della realtà che rappresentano, e resi disponibili per
la consultazione degli utenti.
La disponibilità di informazione è necessaria per la riuscita di ogni attività, da quelle
semplici della vita di tutti i giorni a quelle più complesse di grandi organizzazioni
mondiali. Con l’aumentare della complessità delle attività svolte è sempre più
necessario disporre di un grande numero di informazioni di diverso tipo, e di
poterne disporre in modo efficiente e diversificato a seconda delle necessità così da
trarre il maggiore aiuto possibile dai dati raccolti in precedenza.
Per questo dove si svolgono attività complesse e che necessitano di informazioni è
presente un Sistema Informativo che organizza e gestisce le informazioni necessarie. I
sistemi informativi non sono necessariamente da collegare ai calcolatori, anche gli
archivi di documenti cartacei costituiscono un Sistema Informativo utilizzato da
secoli. A seconda dell’ambito in cui ci si trova le informazioni possono essere
rappresentate in modo diverso: disegni, scritti, numeri, oggetti o anche
semplicemente ricordate a memoria. Man mano che l’informatica è entrata a far
parte delle operazioni svolte, si è inevitabilmente modificato il modo di gestire e
rappresentare le informazioni.
Nei sistemi informatici, che rappresentano la parte automatizzata dei sistemi
informativi, le informazioni sono rappresentate e memorizzate sotto forma di dati,
che risultano facili da gestire e immagazzinare, ma che tuttavia necessitano di una
4
interpretazione prima di poter diventare informazione utile all’utente. I dati da soli
possono essere solamente un insieme di numeri e parole senza alcun significato a
meno di saperli interpretare e relazionare correttamente.
Ua base di dati è un insieme di dati, usati per rappresentare delle informazioni più o
meno complesse che possono così diventare informazioni utili per diverse persone
che utilizzano un Sistema Informativo.
La gestione dei dati è sempre stata fondamentale nelle applicazioni informatiche,
tuttavia inizialmente non esistevano prodotti dedicati esclusivamente a questo scopo,
e la gestione dei dati necessari alle applicazione era gestita dagli stessi linguaggi di
programmazione tradizionali come C , Fortran o linguaggi più recenti come C++ o
Java orientati agli oggetti. In questo caso i dati sono memorizzati persistentemente
in file, che tuttavia non consentono metodi di accesso e consultazione complessi.
Inoltre ogni applicazione e linguaggio di programmazione definisce uno o più file in
un formato “privato”, specifico dell’applicazione stessa, che pertanto non può essere
condiviso con utenti che utilizzano diverse modalità di accesso ai dati, rendendo
necessario duplicare i dati per ogni differente tipologia di accesso.
Una base di dati è gestita da un Data Base Management System (DBMS) che
consente di gestire grandi quantità di dati all’interno del database e di renderli
condivisibili da diverse applicazioni e utenti in modo efficace e sicuro garantendo la
correttezza dei dati.
Le principali caratteristiche di un DBMS e delle basi di dati:
-
devono garantire la gestione dei dati indipendentemente dalle loro
dimensioni e quantità (a parte le limitazioni dei supporti fisici) che possono
arrivare ad alcuni TByte.
-
Utenti e apllicazioni diverse devono poter accedere agli stessi dati attraverso
una modalità comune. Il DBMS deve gestire l’accesso ai dati di molti utenti
5
contemporaneamente. Questo meccanismo è detto controllo di accesso
concorrente.
-
I dati sono persistenti, sopravvivono dopo la morte dell’applicazione.
Devono quindi essere salvati in memoria di massa e non in memoria centrale.
-
Devono garantire che i dati rimangano intatti o permetterne il recupero in
caso di malfunzionamenti o errori di hardware o software.
-
Ogni utente deve essere identificato e deve essergli garantita la privatezza dei
suoi dati. Inoltre ogni utente ha il diritto di svolgere soltanto alcune
operazioni sui dati.
-
L’accesso ai dati da parte dell’utente deve avvenire ad un alto livello di
astrazione, in modo indipendente dalla struttura fisica con cui i dati vengono
salvati e gestiti dal DBMS. La modalità fisica di gestione dei dati può essere
cambiata , ma queste modifiche devono rimanere trasparenti all’utente.
-
Tutto il sistema deve permettere la gestione dei dati e la loro trasformazione
in informazioni utili per l’utente con un utilizzo di risorse come tempo e
spazio accettabile dall’utente, a condizione che il sistema sia installato su
supporti informatici adeguati.
I dati memorizzati nel calcolatore rappresentano la struttura di un concetto del
mondo reale che tuttavia non è direttamente comprensibile ad un calcolatore. Per
rendere questo possibile è necessario definire un Modello dei Dati. Questo fornisce i
concetti necessari a strutturare i dati di interesse per renderli poi utilizzabili
all’interno di programmi per i calcolatore.
Esistono diversi tipi di modello di dati che forniscono dei meccanismi per definire
tipi di dato a partire da altri tipi elementari così da permettere di ottenere tipi di dati
complessi adatti a modellare la realtà dei dati. Nell’evoluzione delle basi di dati sono
stati sviluppati differenti modelli dei dati:
6
-
modello gerarchico, prende il nome dall’uso di struttura ad albero per la
modellazione dei dati. È stato i primo modello ad essere definito nalla fase
iniziale di sviluppo dei DBMS (anni Sessanta)
-
modello reticolare, basato sull’uso di grafi per modellare i dati, sviluppato in
seguito a quello gerarchico (anni Settanta)
-
modello relazionale, quello attualmente più diffuso; organizza i dati in
insiemi di record, e rappresenta le relazioni attraverso tabelle (anni Settanta)
-
Modello ad oggetti, sviluppato come evoluzione del modello relazionale
estende alle basi di dati le caratteristiche dei linguaggi di programmazione ad
oggetti (anni Ottanta).
Le operazioni possibili all’interno di un DBMS sono effettuate tramiti appositi
linguaggi. Molto spesso questi linguaggi sono standard riconosciuti a livello
mondiale che permettono, se rispettati di ottenere delle applicazioni molto flessibili
e portabili. Altre volte si incontrano applicazioni che utilizzano propri linguaggi
spesso derivati da quelli standard.. I linguaggi per le basi di dati si dividono in due
categorie in base alla loro funzione: Data Definition Language, usato per creare lo
schema logico secondo cui organizzare i dati e per gestire i permessi dei diversi
utenti; Data Manipulation Language, necessario per inserire, modificare e aggiornare
i dati e per eseguire interrogazioni sulla base di dati stessa.
LAVORO SVOLTO
Il lavoro svolto richiedeva di individuare in quale modo il modello della
programmazione ad oggetti è stato integrato nelle basi di dati, cercando di capire
come questo nuovo approccio risolve alcuni dei problemi presenti nel modello
relazionale, come ad esempio la modellazione di dati e relazioni complesse oppure
l’impedance mismatch , ovvero la difficoltà di integrazione fra il linguaggio dei dati
relazionali, che maneggia delle tuple, e i linguaggi di programmazione usati per
costruire le applicazioni che invece utilizzano con difficoltà questo tipo di dati.
7
Inoltre si doveva verificare quali e come i vantaggi e le caratteristiche del modello
descritto in teoria fossero applicati nella concreta realizzazione di programmi e
applicazioni per la gestione dei dati con i linguaggio di programmazione ad oggetti.
È stata quindi effettuata una ricerca di materiale teorico riguardante il modello ad
oggetti delle basi di dati attraverso la consultazione di testi e di risorse in rete, e
successivamente, sempre attraverso la ricerca nella rete sono stati individuati alcuni
prodotti commerciali che offrissero le caratteristiche presentate dal modello teorico.
In base alla disponibilità, chiarezza e completezza della documentazione reperibile
dei prodotti individuati, è stata formulata una descrizione delle caratteristiche
principali dei prodotti più rilevanti. Fra questi ne sono stati scelti tre con cui è stata
sviluppata una piccola applicazione con cui sono state testate le caratteristiche
principali. La scelta è stata effettuata in base alla completezza e chiarezza della
documentazione disponibile, la possibilità di ottenere una versione di prova del
prodotto, e il linguaggio di programmazione supportato, per il quale si è utilizzato
Java.
Con lo studio del modello ad oggetti sulle basi di dati, la ricerca effettuata sui vari
prodotti commerciali e lo sviluppo dell’applicazione è stato possibile chiarire in
quale modo possano essere sfruttate le caratteristiche dei linguaggi di
programmazione ad oggetti per costruire basi di dati che permettano di gestire un
insieme di dati complesso utilizzando la normale sintassi del linguaggio, poiché gli
oggetti sono salvati nel database con la stessa struttura che hanno nell’applicazione.
8
Capitolo 2
DATABASE OBJECT ORIENTED
Evoluzione delle basi di dati
Basi di dati Relazionali
Il modello relazionale si basa sui concetti di relazione e tabella. I dati vengono
collegati fra loro secondo una certa relazione in un record. La relazione è formata da
un insieme di record che costituiscono una tabella e che sono tutti gli elementi legati
fra loro da quella stessa relazione. Ogni riga della tabella contiene un record, e ad
ogni campo del record si assegna un nome univoco, detto “attributo” che serve per
identificarlo e per descrivere in modo il suo ruolo nella relazione, e viene usato come
intestazione della colonna della tabella che contiene quel campo
Per costruire una base di dati che contenga un modello complesso è necessario
utilizzare più di una relazione. Questo porta a definire diverse tabelle che
rappresentano diverse relazioni e che contengono tuple con valori comuni che
permettono di stabilire la corrispondenza fra l’elemento di una relazione e quello di
un’altra. Le varie tabelle che compongono una relazione sono poi ricombinate (Join)
attraverso un linguaggio di query (SQL è lo standard universalmente diffuso) che
permette di avere una vista unitaria delle informazioni appartenenti alla stessa
relazione. Per far questo però è necessario mantenere l’identità di ogni elemento
(tupla), e nasce quindi la necessità di un identificatore univoco. È
necessario
identificare una colonna della tabella che contenga valori distinti per ogni elemento
ed è necessario assicurare che questo avvenga sempre. Questo campo è chiamato
chiave primaria. La chiave primaria può essere costituita da due campi che combinati
garantiscono l’unicità di quella tupla. Purtroppo non sempre nei dati che vogliamo
modellare sono presenti un campi con tali proprietà, può essere quindi necessario
9
introdurre un campo, solitamente un codice numerico progressivo assegnato
automaticamente dal sistema, che tuttavia contiene un dato che non ha alcun
significato rispetto al modello dei dati originale.
Per mantenere i riferimenti fra tabelle diverse, un record può avere un campo in cui
è memorizzato un valore corrispondente al valore della chiave di un record nella
tabella a cui si fa riferimento .
Per ottenere una rappresentazione dei dati che risponda correttamente alle esigenze
di modellazione della realtà è necessario introdurre il concetto di vincolo di integrità.
Infatti non tutti i valori o combinazioni di valori sono accettabili. Ogni vincolo è
un’espressione booleana che viene valutata per ogni istanza e determina quali sono
ammissibili o meno all’interno della base di dati. I vincoli possono essere di diverso
tipo:
- vincolo intrarelazionale, se può essere valutato rispetto a elementi di una
sola relazione. Può cioè riguardare un singolo valore di ogni tupla, oppure la
combinazione di valori di una singola tupla indipendentemente dalle altre.
Può infine riguardare valori di differenti tuple di una tabella, come è il caso
dei vincoli di chiave, che impongono l’unicità della chiave primaria
all’interno della tabella.
- vincolo interrelazionale, se deve essere valutato considerando più relazioni,
per esempio richiedendo che il valore di un certo campo di una tabella sia
valido solo se compare come valore in un campo di un’altra tabella. Questo è
il caso dei vincoli di integrità referenziale, per cui i valori di un campo sono
significativi solamente se compaiono come valori di chiave primaria di
un’altra relazione.
Il linguaggio SQL consente solo le operazioni sul database, non permette di
sviluppare una applicazione completa. Per far questo l’applicazione viene costruita
con linguaggi di programmazione adatti che ospitano al loro interno delle istruzioni
10
SQL che vengono passate al database come stringa di testo (Il linguaggio viene
chiamato linguaggio ospite). Il database ritorna al linguaggio una tabella contenente i
risultati, tuttavia la maggior parte dei linguaggi di programmazione maneggia
difficilmente le tabelle. Si definiscono così dei cursori che consentono di scandire i
record della tabella e i loro valori.
Le basi di dati relazionali hanno avuto un grande successo e sono tuttora largamente
utilizzate. Funzionano bene quando l’applicazione deve gestire dati con struttura
non troppo complessa e che può essere facilmente modellata con tabelle. I database
relazionali inoltre sono adatti ad applicazioni centralizzate con un numero di accessi
da parte di diversi utente ridotto. Tutto il carico di lavoro per l’inserimento e la
modifica dei dati, la gestione degli accessi e l’esecuzione delle query, è sul server, e
questo costituisce certamente un collo di bottiglia che rende le prestazioni sempre
peggiori con l’aumentare del numero di utenti e non rende l’applicazione scalabile a
meno di aumentare la potenza delle risorse hardware con una spesa molto elevata.
Inoltre nel caso si presenti la necessità di modifiche o evoluzioni allo schema dei dati
a seguito di esigenze dell’utente che variano questo può essere un problema
difficilmente risolvibile a costi limitati.
Modello ad oggetti per le basi di dati
Per questo, man mano che sono nate applicazioni ed esigenze sempre più complicate
e specifiche di ambiti scientifici e tecnologici, con la necessità di modellare dati
complessi e articolati e legati fra loro da relazioni complesse, sono nate le basi di dati
ad oggetti. Integrano alle basi di dati i concetti della programmazione ad oggetti e
permettono così di sorpassare le limitazioni mostrate dal modello relazionale.
Utilizzando le metodologie tipiche dei linguaggi di programmazione ad oggetti è
possibile definire strutture di dati complesse e rappresentare oggetti della realtà in
modo fedele e unitario senza doverne dividere le caratteristiche su diverse tabelle. Si
possono definire relazioni complesse come ereditarietà o caratteristiche quali
incapsulamento, overloading e overriding.
11
Nell’evoluzione dalle basi di dati relazionali verso gli oggetti si sono distinti due
differenti approcci: le basi di dati relazionali e a oggetti (Object Relational Database
Management System ORDBMS), che hanno cercato di estendere il modello
relazionale introducendovi il concetto di oggetto e integrando il linguaggio SQL con
le estensioni necessarie), e le basi di dati orientate agli oggetti (Object Oriented
Management Systems OODBMS) che invece hanno esteso ai DBMS i concetti
propri dei linguaggi di programmazione ad oggetti.
Basi di dati relazionali e a oggetti
Le basi di dati relazionali e a oggetti utilizzano come linguaggio di interrogazione
una estensione di SQL, definita come SQL-3. Il modello dei dati definito da questo
nuovo standard è compatibile con quello del modello relazionale. Gli ORDBMS
consentono di creare dei tipi di tupla a partire dai tipi fondamentali e di rendere
riusabili questi tipi come base per costruirne di più complessi. In particolare si
possono definire strutture dati di tipo tupla, che sono quelle che poi compongono le
tabelle e in cui si possono specificare i vari vincoli. Si possono anche definire delle
gerarchie di tipo fra le tabelle dichiarando dei nuovi tipi che ne estendono altri già
dichiarati e aggiungono nuove caratteristiche. In questo modo le tabelle di livello
inferiore sono un sotto-tipo di quelle che estendono, e ogni tupla presente al livello
inferiore deve essere presente anche a livello più alto. All’interno dei tipi tupla i
campi possono fare riferimento a dei tipi di dato definiti dall’utente detti tipi di dato
astratto (Abstract Data Type) che possono essere utilizzati per costruire dei tipi
tupla complessi o altri tipi di dato astratto. Si può quindi paragonare il concetto di
tupla di un ORDBMS al concetto di classe del modello ad oggetti. Ai tipi di dato
astratto si possono associare anche delle funzioni che implementano le operazione
svolte dai metodi costruttori o per accedere ai vari attributi. Su questi metodi è
anche possibile stabilire dei privilegi di accesso da parte dei diversi utenti. Le
funzioni definite possono essere implementate in SQL-3 oppure in un linguaggio di
programmazione ospite.
12
Basi di dati a oggetti
I dati tuttavia sono comunque immagazzinati in tabelle e il linguaggio di
interrogazione è SQL, pertanto richiede comunque un’integrazione con il linguaggio
con cui sarà poi sviluppata l’applicazione finale. Le basi di dati orientate ad oggetti
offrono una soluzione, adottando per intero il modello della programmazione ad
oggetti e permettendo di salvare le strutture dati definite senza modifiche alla loro
struttura direttamente nel database. La completa integrazione fra il linguaggio del
database e quello di programmazione consente di costruire applicazioni più efficienti
e più facili da adattare ai cambiamenti delle esigenze riguardo al modello dei dati. La
definizione della struttura dati può sfruttare tutte le caratteristiche della
programmazione ad oggetti quali tipi di dati definiti dall’utente, incapsulamento,
ereditarietà, polimorfismo, riferimenti tra gli oggetti.
Semplicemente il modello dei dati viene salvato in memoria di massa dove il DBMS
assegna ad ogni oggetto un identificatore unico che sarà associato all’oggetto fintanto
che questo sarà presente nel database (Oid) . Attraverso l’Oid il sistema può
recuperare l’oggetto e gestire le relazioni. Gli oggetti sono richiamati
dall’applicazione utilizzando le librerie messe a disposizione dal DBMS che
consentono di interagire con il database per inserire e recuperare gli oggetti. Quando
si accede ad un oggetto questo viene caricato dalla memoria di massa in memoria
centrale. A questo punto l’oggetto è disponibile localmente all’applicazione; una
volte effettuate le modifiche all’oggetto in memoria, queste vengono salvate sul
database.
Ogni DBMS utilizza metodi differenti per accedere al database così come diverse
modalità di memorizzazione dei dati. All’utente vengono però fornite librerie per
poter sviluppare applicazioni con linguaggi standard. Molti DBMS forniscono il
supporto per lo standard ODMG. ODMG, Object Database Management Group, è
un consorzio costituito verso la fine degli anni Ottanta dai principali costruttori di
OODBMS per cercare di fornire un comune linguaggio di definizione del modello
dei dati ODL (Object Definition Language) e di interrogazione (OQL Object Query
13
Language) per poter sviluppare soluzioni portabili. Lo standard definisce anche
meccanismi per la definizione dei metodi degli oggetti nei linguaggi C++ e
Smalltalk. La maggior parte dei ODBMS mette a disposizione anche librerie per
consentire la completa interazione con il database attraverso i più comuni linguaggi
di programmazione come Java, C, Smalltalk. In questo modo il linguaggio di
programmazione diventa l’unico linguaggio utilizzato per la creazione, gestione e
interrogazione del database, utilizzando le classi e le librerie di supporto fornite da
ciascun DBMS.
Caratteristiche di un ODBMS
Modello dei dati
Tipi di dato – le proprietà di un oggetto vengono definite attraverso un elenco di dati
a cui è associato un tipo e un nome. I dati possono essere di tipo atomico cioè i tipi
di base messi a disposizione dalla maggior parte dei linguaggi di programmazione
come ad esempio interi, reali, Stringhe, booleani ecc. Inoltre si possono definire
proprietà (attributi) di un oggetto che hanno per valore un altro oggetto. Oggetti
dello stesso tipo appartengono ad una classe all’interno della quale viene data la
definizione delle proprietà del tipo, i metodi con i loro parametri di ingresso ed
uscita, e la loro implementazione. I metodi possono essere usati per non permettere
l’accesso diretto alle proprietà dell’oggetto,che vengono definite private, ma
consentendo solo l’uso di metodi appositi per la lettura e la modifica delle proprietà
stesse esponendo così all’esterno dell’oggetto solo la segnatura dei metodi con i
parametri di ingresso e uscita. L’insieme dei valori assunti dalle proprietà di un
oggetto è il suo stato.
Incapsulamento – è una caratteristica della programmazione ad oggetti per cui si
cerca di nascondere all’esterno dell’oggetto l’implementazione dei suoi metodi e
delle sue proprietà. L’oggetto è costituito da un’interfaccia che definisce i metodi
utilizzabili sull’oggetto e i loro parametri, e dall’implementazione che contiene il
codice vero e proprio che esegue le operazioni. Nelle basi di dati ad oggetti tuttavia
14
la struttura dei dati dell’oggetto può essere resa visibile all’esterno attraverso i
linguaggi di interrogazione.
Ereditarietà – A partire da una classe definita super-classe è possibile definire una
sotto-classe che eredita tutte le proprietà i metodi e la loro l’implementazione dalla
super-classe, e può inoltre definire attributi e metodi aggiuntivi che la specializzano.
In una gerarchia, tutti gli oggetti delle sottoclassi appartengono anche alla
superclasse. Un oggetto si dice istanza di una classe se essa è la classe più specifica che
lo rappresenta, mentre esso si dice membro di tutte le sue super-classi. In un
ODBMS è possibile creare un’istanza di una classe e assegnarle a runtime il tipo di
una superclasse. Le sue proprietà specifiche vengono comunque conservate e
memorizzate permanentemente del database. Se si cercano nel database tutte le
istanze di una super-classe verranno ritornate anche tutte le istanze delle sottoclassi.
Polimorfismo – All’interno di una gerarchia è possibile nelle sotto-classi ridefinire
l’implementazione dei metodi ereditati in modo da avere sui dati un comportamento
più specifico e diversificato per la sotto-classe. Quando viene eseguito il programma,
il sistema determina automaticamente quale metodo usare, scegliendo quello più
adatto secondo il tipo dell’oggetto. Si possono così definire diverse versioni di un
metodo mantenendo la stessa interfaccia per tutti gli oggetti della gerarchia
ottenendo quello che viene chiamato overloading dei metodi. È possibile anche
ridefnire metodi e proprietà modificando l’interfaccia. IN questo modo si
introducono dei sotto-tipi, in cui le proprietà possono avere come tipo un sotto-tipo
di quello che avevano nell’interfaccia della super-clasee. Allo stesso modo i metodi
possono avere come parametri di ingresso e di uscita dei sotto-tipi dei parametri che
avevano nell’interfaccia della super-classe.
Relazioni tra oggetti – Possono essere definite delle relazioni tra gli oggetti
assegnando ad un campo di un oggetto un altro oggetto cioè il suo identificatore, che
nella basi di dati rappresenta univocamente ciascun oggetto creato e salvato nel
database. Se un oggetto fa riferimento ad un altro, terrà il riferimento all’oggetto
15
destinazione ed altre istanze potranno fare riferimenti a quello stesso oggetto.
Utilizzando dei costrutti come le Collezioni gli Array ecc è possibile stabilire
relazioni fra un oggetto e un insieme di altri oggetti mantenendone i riferimenti.
Alcuni DBMS supportano una modalità per cui è possibile inglobare un oggetto
referenziato all’interno sorgente della relazione, così che i due oggetti siano salvati
insieme e l’oggetto destinazione non risulta avere un proprio identificativo ma esiste
soltatnto come proprietà dell’oggetto che lo contiene. Questo permette di salvare
spazio nella memorizzazione dei dati nel database, ma tuttavia non rende possibile
accedere direttamente all’oggetto contenuto con le query, ma solo raggiungendolo
dall’oggetto sorgente.
Schema del database
Poiché tutte le caratteristiche dei linguaggi object oriented sono supportate, la
creazione dello schema del database è immediata e diretta, infatti le classi definite nei
diversi linguaggi di programmazione vengono mappate nello schema del database
senza modifiche alla struttura dati. Con le funzioni messe a disposizione dai DBMS
si costruisce uno schema che risulta indipendente dal linguaggio di programmazione
usato (C++,Java, Smalltalk), ed è possibile accedere agli oggetti di uno stesso
database con linguaggi diversi usando una specifica interfaccia. Le uniche modifiche
apportate riguardano il formato con cui alcuni tipi di parametri sono immagazzinati
in memoria di massa, tuttavia sono modifiche trasparenti all’utente. Nello schema
del database sono descritte solo le proprietà che concorrono a stabile lo stato
dell’oggetto; i metodi non vengono salvati nel database.
Una volta definito lo schema, man mano che si sviluppa l’applicazione è probabile
che siano necessari dei cambiamenti delle classi che comportano dei cambiamenti
dello schema. Non tutti i cambiamenti all’implementazioni delle classi comportano
modifiche allo schema del database. I metodi non sono infatti salvati, ma vengono
pertanto la modifica alla loro implementazione non influenza il db. L’aggiunta di
una classe comporta una modifica allo schema, così come la modifico, l’aggiunta o l
a rimozione di proprietà a classi esistenti. Quando si verifica un cambiamento di
16
questo tipo il DBMS deve essere in grado di accorgersene e di adottare una valida
politica. Vengono adottate soluzioni differenti dai vari DBMS; in alcuni casi, quando
si verifica un cambiamento di schema, viene effettuata la conversione dei vecchi
oggetti in quelli nuovi aggiungendo o togliendo i campi secondo il necessario,
recuperando i valori dei vecchi oggetti. IN altri casi la conversione viene fatto
oggetto per oggetto nel momento in cui questo viene richiamato dal database per
essere caricato nella memoria dell’applicazione. In ogni caso la caratteristica
importante è che il DBMS consenta di modificare lo schema senza necessità di
mandare off-line i database.
Architettura client-server
Una delle principali caratteristiche dei ODBMS è quella di garantire l’accesso a
grandi quantità di dati a un grande numero di utenti. Con le basi di dati relazionali
l’accesso ai dati da parte degli utenti richiedeva tutto il lavoro da parte del server. Gli
utenti inviavano le richieste sotto forma di query che il server eseguiva rimandando
al client solo i risultati. Con questo approccio le capacità di calcolo dei client che con
le prestazioni dei
moderni PC sono molto elevate, vengono completamente
sprecate, mentre si costringe il server a essere equipaggiato con hardware molto
costoso e potente. Le basi di dati ad oggetti trasferiscono parte delle operazioni
svolte a lato client come la gestione della sessione e delle transazioni. In questo modo
si sfrutta tutta la potenza di calcolo a disposizione e si rende possibile un
mantenimento delle prestazioni anche con l’aumentare degli utenti che accedono al
db. Inoltre viene ridotto il traffico generato sulla rete con l’utilizzo di cache in cui
vengono memorizzati gli oggetti a lato client, in modo da non sovraccaricare la rete
ad ogni richiesta.
Accessi concorrenti
La condivisione dei dati da parte di più utenti rende necessario un controllo sul
modo in cui questi vengono letti e modificati. Se infatti due utenti accedono allo
stesso dato per modificarlo, quando le modifiche sono apportate, quelle di una
transazione possono essere soprascritte dall’altra, oppure può avvenire la lettura di
17
un dato che in realtà è in uno stato intermedio di modifica da parte di una
transazione, rendendo così il dato letto di fatto inconsistente. Per questo si
definiscono diverse metodologie per controllare l’accesso in lettura e scrittura dei
dati. Il meccanismo più utilizzato è il locking. L’applicazione che vuole eseguire una
operazione sui dati deve prima eseguire una richiesta di lock al DBMS, e terminata
l’operazione il lock verrà rilasciato. Per ogni operazione di lettura deve essere
richiesto un Read Lock, ; il lock è detto condiviso pichè per un oggetto possono
essere richiesti contemporaneamente diversi lock di lettura da parte di diverse
transazioni. Per ogni operazione di lettura deve essere richiesto un Write Lock, che è
detto esclusivo poiché se su un oggetto è presente un lock di questo tipo non se ne
possono richiedere altri, di lettura o scrittura finché questo non viene rilasciato. Il
sistema deve essere in grado di gestire le situazioni critiche in cui i processi devono
aspettare di ottenere risorse che sono bloccate da altri processi, evitando che
aspettino troppo a lungo o che si creino dei deadlocks.
Distribuzione dei dati
La maggior parte dei sistemi relazionali era costituito da un solo database che era
condiviso da più utenti. Col passare del tempo e con l’aumentare del numero di
utenti che effettuavano accesso al database e la quantità di dati fortemente
ingrandita, si è reso necessario poter distribuire i dati del db su più macchine, senza
però influire sul modo in cui l’utente accede ai dati. Praticamente tutti gli ODBMS
consentono di distribuire i dati. Questo vuol dire che il codice dell’applicazione non
deve risentire del fatto che gli oggetti con cui si interagisce risiedano su dispositivi
differenti, e gli oggetti devono essere referenziati allo stesso modo come se fossero
tutti in un database locale all’applicazione. I riferimenti agli oggetti devono perciò
essere indipendenti dalla loro posizione fisica, e devono funzionare allo stesso modo
fra oggetti tra loro locali o remoti. Gli oggetti devono quindi essere identificati con
un Oid (l’identificatore univoco assegnato all’oggetto dal sistema) che sia
indipendente dal nodo della rete i cui si trova l’oggetto, in caso contrario se un
oggetto dovesse essere spostato da un nodo ad un altro, gli oggetti che fanno
riferimento a quello spostato conterrebbero informazioni inconsistenti a meno di
18
trovarli tutti e modificarne i riferimenti. Anche le transazioni non devono risentire
della distribuzione dei dati e devono poter modificare dati posti in differenti punti
della rete. La fase di commit è gestita diversamente in caso di modifiche a dati locali
o remoti; in caso di modifiche remote viene usato un meccanismo di two-fase
commit. La scelta del metodo appropriato deve essere trasparente al programmatore
ed essere effettuata automaticamente dal sistema.
19
Capitolo3
PRODOTTI OBJECT ORIENTED
OODBMS Commerciali
é stata effettuata una ricerca attraverso la rete per individuare i prodotti presenti sul
mercato che utilizzassero l’approccio ad oggetti per sviluppare delle basi di dati.
Sono stati individuati circa 20 prodotti che si differenziano per numerosi aspetti sia
per quanto riguarda quante e quali delle funzionalità richieste ad un ODBMS sono
implementate, sia per quanto riguarda le modalità con cui questo avviene. Molti
prodotti
adottano
soluzioni
particolari
per
alcuni
aspetti
trascurandone
completamente altri poiché in realtà non risultano essere ODBMS puri ma ad
esempio ambienti di sviluppo per applicazioni web che utilizzano al loro interno un
database ad oggetti che tuttavia viene sfruttato solo per alcune sue caratteristiche e
gli altri aspetti sono pertanto trascurati.
Classificazione
ODBMS “puri”. Supportano generalmente tutte le caratteristiche principali richieste
(ereditarietà, transazioni ecc..), mettono a disposizione interfacce grafiche per
effettuare operazione sul DB più o meno complesse e potenti (creazione e modifica
degli oggetti, navigazione del db..) e librerie per permettere la programmazione con i
principali linguaggi orientati agli oggetti quali C++, Java e Smalltalk. Alcuni sono
dedicati esclusivamente ad uno solo di questi linguaggi, in altri casi invece vengono
utilizzati linguaggi derivati dagli standard ODMG che non sono sempre pienamente
rispettati. Si tratta per la maggior parte di prodotti commerciali distribuiti da società
di grosse dimensioni e presenti sul mercato da tempo. Le principali sono membri
fondatori del gruppo ODMG (Versant, Objectivity, OBjectDB..) e hanno
sviluppato sistemi ad oggetti fin dai primi tempi in cui questi sono entrati nel
mercato.
Di questi prodotti è in genere reperibili una buona e completa
documentazione e spesso versioni di prova del prodotto disponibili per il download.
•
EyeDB – Sysra http://www.sysra.com/eyedb/
•
JYD – Jyd Software http://www.jyd.com/jydinfo.html
•
NeoAccess – NeoLogic http://www.neologic.com/neoaccess.html
•
Objectivity/DB
–
http://www.objectivity.com/Products/prodov.html
•
ObjectStore–Objectstore
http://www.odi.com/ODILIVE/contents/products/objectstore/objectstore
_index.html
•
Orient – orient Technologies http://www.orientechnologies.com/
•
Tenacity – TotallyObjects http://www.totallyobjects.com/toolFrame.htm
•
VDS – Versant http://www.versant.com/us/products/release/index.html
•
Matisse – Matisse http://www.matisse.com/
•
FastObjects – Poet http://www.fastobjects.com/eu/
•
ObjectDB – ObjectDB http://www.objectdb.com/
•
2 – Oxymel http://www.oxymel.com/fr/o2/index.html
•
Gemstone – Gemstone www.gemstone.com
Objectivity
Odbms integrati in piattaforme di sviluppo. Ad esempio si tratta di diversi strumenti
integrati che permettono la costruzione di siti internet o applicazioni per sistemi
informativi che necessitano la gestione di transazioni e usano il db ad oggetti di
supporto all’applicazione. Alcuni utilizzano linguaggi di programmazione propri in
genere derivati in dagli standard ODMG o Javascript per il Web. Non si tratta di
veri e propri DBMS poiché il database è implementatato con le sole caratteristiche
necessarie all’integrazione con l’ambiente di sviluppo e molte funzionalità non sono
supportate.
•
Jasmine
–
Computer
http://www.cai.com/products/jasmine.htm
•
Zope – Privato http://cbbrowne.com/info/framewrk.html
•
Jade – Jade http://www.jade.co.nz/jadehttp.dll?JWS
•
DeployZone
–
Deployzone
http://www.deployzone.com/zone/.eec7400/cmd.14/audience.E
Associates
Progetti Universitari o di privati. Tipicamente sviluppati a fini di ricerca e sviluppo.
Spesso non offrono tutte le caratteristiche standard, la documentazione non è
sempre completa, a volte sono forniti direttamente i sorgenti da compilare.
21
•
Goods – Privato http://www.ispras.ru/~knizhnik/goods.html
•
Minnestore – Privato http://minnestore.sourceforge.net/
•
StoredObjects –Privato http://storedobjects.sourceforge.net/
•
FramerD – MIT http://www.framerd.org/
•
Spider – Interval Software http://www.intervalsoftware.com/odb.html
•
LDB – University of Texas http://lambda.uta.edu/lambda-DB/manual/
•
Ozone – Gruppo di lavoro http://www.ozone-db.org/
Per ciascuno dei prodotti ritenuti più rilevanti prendendo in considerazione le
caratteristiche presentate e la completezza della documentazione disponibile,
verranno analizzate le caratteristiche principali che un ODBMS dovrebbe
supportare. Per quanto riguarda l’approfondimento di alcuni aspetti, o esempi di
funzionalità descritte con parti di codice,
ObjectDB
Object db è scritto interamente in Java ed è conforme allo standard JDO (java Data
Objects) utilizzato anche per mappare programmi java su database relazionali. Tutte
le operazioni sul db sono pertanto effettuate con linguaggio Java, sia per quanto
riguarda la definizione dello schema del database, la creazione e manipolazione degli
oggetti e la creazione di query. In particolare, le funzioni di accesso al database e le
query vengono effettuate attraverso delle particolari librerie che implementano lo
standard JDO e ne estendono alcune caratteristiche.
Complessità strutturale – La complessità strutturale è garantita dall’uso del linguaggio
Java che consente di creare oggetti arbitrariamente complessi a partire da oggetti
semplici, di costruire relazioni fra oggetti e definire i metodi.
Tipi e Classi – Ogni oggetto possiede un tipo che permette di identificarne le
caratteristiche (proprietà e metodi). Tutti gli oggetti di un tipo sono raggruppati
nella stessa Classe che definisce un’interfaccia, dove vengono rappresentate le
segnature dei metodi e le proprietà con il loro tipo, e l’implementazione dei metodi.
22
Gerarchie di tipi e classi – Il modello delle gerarchie è quello Java dove tutti gli oggetti
derivano da Object. Le sottoclassi ereditano i metodi e le proprietà dalle super-classi
e possono aggiungere nuove funzionalità. Un oggetto istanza di una sottoclasse può
avere come tipo ognuna delle sue super-classi
Overriding Overloading Late Binding - è possibili ridefinire i metodi ereditatati con
una nuova implementazione mantenendo la stessa interfaccia, oppure ridefinire il
metodo con parametri di ingresso o uscita differenti. La scelta del metodo corretto
da utilizzare verrà fatta a runtime dalla JVM
Tipi Utente – L’utente può definire nuovi tipi combinando tipi atomici e estendendo
le classi messe a disposizione dalle librerie Java.
Completezza Computazionale del linguaggio – Il linguaggio Java permette di costruire
strutture dati arbitrariamente complesse. Permette inoltre di effettuare tutte le
operazioni necessarie sui dati.
Persistenza – Le classi che vengono salvate nel database sono chiamate persistent
capable, quelle che non rappresentano dati salvati sono transient e non possono
avere istanze persistenti. Una classe è persistente qua
- Dichiararla persistente in un file xml che descrive tutte le classi persistenti ed
eventuali caratteristiche diverse da quelle di default per le classi e le loro proprietà. Il
file xml è un file di questo tipo:
<!DOCTYPE jdo SYSTEM "http://java.sun.com/dtd/jdo_1_0.dtd">
<jdo>
<package name="">
<class name="A" />
</package>
<package name="test">
<class name="B" />
<class name="C" persistence-capable-superclass="B" />
<class name="D" requires-extent="false" />
</package>
23
</jdo>
- deve contenere un costruttore senza argomenti
- deve
implementare l’interfaccia javax.jdo.spi.PersistenceCapable. Poiché
questa interfaccia contiene più di venti metodi, ObjectDB fornisce un tool che
aggiunge automaticamente alle classi dichiarate persistenti nel file xml i metodi
necessari e il costruttore se non è presente. Il tool può essere usato o da linea di
comando prima di eseguire l’applicazione, oppure è disponibile una classe da usare al
posto del main dell’applicazione, che completa le classi persistenti e poi lancia il vero
e proprio main.
Nel database non sono salvati i metodi e i campi definiti static final o transient. Sono
invece salvati tutti i campi di tipo persistent. Sono per default definiti persistent le
classi persistent capable; inoltre i seguenti tipi di sistema:
•
•
Tutti i tipi primitivi- boolean, byte, short, char, int, long, float and
double.
Le seguenti classi del package java.lang:
•
Boolean, Byte, Short, Character, Integer, Long, Float, Double,
Number e String.
Le seguenti classi del package java.util:
Date, Locale, HashSet, TreeSet, ArrayList, LinkedList, Vector,
HashMap, TreeMap e Hashtable, e le interfacce - Collection, Set, List e
Map.
java.math.BigInteger e java.math.BigDecimal
•
Ogni array di un tipo persistente, inclusi array multidimensionali.
•
Quando una istanza di un oggetto viene dichiarata persistente e inserita nel database,
tutte le istanze di altri oggetti a cui fa riferimento con campi di tipo persistent
diventano persistenti e salvati nel database in modo da mantenere l’integrità delle
relazioni.
24
Identità degli oggetti – Gli oggetti possono essere identificati in due modi: con un Oid
o con un nome. L’Oid viene assegnato ad ogni oggetto quando viene salvato per la
prima volta nel database. Si tratta di un numero progressivo che identifica l’oggetto
per tutta la sua “vita” nel database. Una volta che l’oggetto è cancellato il suo id non
viene più utilizzato per un altro oggetto. L’Oid può essere richiamato con una
funzione che restituisce la stringa con il suo valore numerico e può così essere usato
nelle applicazioni per identificare l’oggetto. E’ possibile invece assegnare anche un
nome ad ogni oggetto presente nel database. Il nome deve essere univoco altrimenti
viene lanciata un’eccezione. È possibile rimuovere il nome assegnato ad un oggetto
così come utilizzare il nome assegnato per recuperare l’oggetto dal database.
Gestione accessi concorrenti - ObjectDB usa un meccanismo di lock automatico che
previene che un oggetto venga modificato simultaneamente da due utenti diversi
(intesi come due diverse istanze di PersistenceManager). In una transazione ogni
volta che un oggetto è prelevato dal database è messo un read lock su quell’oggetto
dal PersistenceManager. Ad ogni tentativo di modificare un oggetto in una
transazione è messo un write lock su quell’oggetto dal PersistenceManager. Sono
consentiti più read locks sullo stesso oggetto da PersistenceManager diversi mentre
un write lock è esclusivo. Perciò un tentativo di modificare un oggetto che è già in
uso da un’altra istanza di Persistence Manager (per read o write) o un tentativo di
leggere un oggetto che è in uso per essere
modificato da un altro
PersistenceManager bloccano il thread. Tutti i lock sono rilasciati quando la
transazione finsce.
Per evitare deadlock i thread attendono il lock per 50 millisecondi e poi è
automaticamente fatto un altro tentativo di ottenere il lock. Dopo 40 fallimenti è
lanciata un’eccezione. Entrambe le variabili possono essere modificate. Questo tipo
di transazioni sono chiamate in JDO datastore transactions e sono usate di default in
objectDB. Tuttavia sono supportate anche le optimistic transaction. In questo caso
gli oggetti non sono bloccati e se due transazioni modificano simultaneamente lo
stesso oggetto il conflitto è rilevato al momento del commit. In questo caso la prima
25
transazione che fa commit va a buon fine, l’altra fallisce. Questo metodo è usato se
sono necessarie lunghe transazioni per evitare lock molto lunghi sugli oggetti.
Altrimenti con transazioni corte o alta probabilità di conflitto sono consigliate le
transazioni datastore.
Locking Granularity – Il database esegue locking a livello di oggetto
Linguaggio di interrogazione – Gli oggetti possono essere recuperati dal database
attraverso il loro ID oppure attraverso il nome assegnato all’oggetto nel db. Si può
inoltre utilizzare un Extent per recuperare tutte le istanze di una certa classe e
esaminarle una ad una con un iteratore. Se bisogna effettuare una ricerca più selettiva
il linguaggio usato è JDOQL (Java Data Object Query Language). Una query di
questo tipo è composta da un insieme di istanze in cui ci sono i possibili risultati,
una classe candidata, e un filtro, cioè una espressione booleana che identifica le
condizioni che devono soddisfare le istanze. I risultati sono restituiti in una
Collection che contiene le istanze della classe candidata presenti nell’insieme di
partenza che soddisfano l’espressione. Nell’espressione possono essere usati
parametri, variabili, riferimenti ad oggetti persistenti con l’operatore this e anche
metodi che non modificano i campi di oggetti persistenti come contains(), lenght()
ecc.. I risultati possono essere ordinati secondo un campo a scelta.
Dati distribuiti – Non supportato.
Gestione Versioni - I cambiamenti a classi persistenti che aggiungono o tolgono
metodi o modificano campi non persistenti non modificano lo schema del database.
Lo schema è modificato da cambiamenti ai campi persistenti. I nuovi oggetti sono
salvati con la nuova versione, e le vecchie istanze devono essere salvate con la nuova
versione automaticamente quando una vecchia istanza è caricata in memoria. La
conversione viene effettuata inizializzando con valori di default i nuovi campi non
presenti nel vecchio schema, mentre i vecchi campi non più presenti nel nuovo
schema sono ignorati. Se il tipo di un campo è modificato, se è possibile fare il
26
casting il nuovo campo viene aggiornato con il vecchio valore castato, altrimenti
viene inizializzato come un nuovo campo con valori di default.
Struttura Client-Server Modalità client-server: In questa modalità il server è eseguito
in una differente JVM. L’applicazione che è eseguita su una JVM separata comunica
con il db server con TCP\IP. Poiché il db Server gestisce il meccanismo di locking
automatico, molti client possono accesere allo stesso server simultaneamente con
sicurezza.
Modalità Embedded : L’applicazione usa Objectdb come libreria, e non è necessario
nessun processo server. I file del db sono modificati direttamente dal processo
dell’applicazione usando i file jar di JDO e Objectdb che vengono inclusi nel
classpath dell’applicazione. In questo modo il database è bloccato per un singolo
processo e una attività multiuser può essere ottenuta solo usando diversi thread in
uno stesso processo.
Piattaforme Supportate ObjectDb è scritto interamente in java perciò è eseguibile in
qualsiasi ambiente che supporta java.
Tool - JDO data explorer, tool visuale che permette di:
- Creare un nuovo database o aprire un db esistente
- Navigare nel db e vedere gli oggetti salvati
- Modificare, creare,cancellare gli oggetti contenuti
- Vedere lo schema del db
- Modificare lo schema del db
- Eseguire JDOQL queries e navigare i risultati con tool avanzati per la scrittura
delle query.
- Eseguire Garbage collection
- Nell’edizione server è inoltre possibile amministrare i server dei db remoti, gestire
gli account degli utenti.
27
Objectivity - DB
Tipi e Calssi – Il modello del database è costruito a partire direttamente da quello
dell’applicazione, pertanto tipi e classi sono quelli definiti dall’utente con i linguaggi
supportati che sono C++, Java, Smalltalk.
Persistenza – Possono essere salvati nel database solo gli oggetti persistenti, cioè quelli
che sono istanza di una classe persistent capable. Una classe può essere resa persistent
capable o implementando una specifica interfaccia o estendendo una classe persistent
capable. Le classi sono aggiunte allo schema del database automaticamente quando
vengono rese persistenti. Tuttavia questi oggetti non saranno visibili ad altre
applicazioni fino a quando la transazione all’interno della quale sono stati creati
esegue il commit. Il database ha una struttura gerarchica. In cima è presente un
Database Federato, che può contenere numerosi db locali o remoti, che a loro volta
possono contenere svariati container. Gli oggetti possono essere salvati
selettivamente in ognuna delle parti del Database federato per ottenere una migliore
distribuzione del carico di lavoro e ottimizzare la ricerca. È infatti possibile
recuperare in una sola volta tutte le istanze presenti in un container, o in un
database. All’interno di ciascuno di questi elementi si può creare uno Scope in cui
assegnare un nome univoco agli oggetti salvati così da poterli recuperare facilmente.
Identità degli oggetti – Al livello più basso
objectivity/Db accede agli oggetti
attraverso un Object Identifier a 64bit. Ogni oggetto in un Database Federato ha un
Oid univoco che permette al sistema di gestire e localizzare gli oggetti. L’Oid
contiene informazioni riguardo il database, container, pagina e slot, in cui un
oggetto è memorizzato; fornisce l’indirizzo logico per recuperare l’oggetto ed è
diviso in 4 parti:
I primi 16 bits—Logical Database ID
28
I secondi 16 bits—Logical Container ID (1 bit internal use, 15 bits container
number)
I terzi 16 bits—Logical Page all’interno del container
Gli ultimi 16 bits—Logical Slot all’interno di una pagina
In generale l’Oid non cambia per tutta la vita dell’oggetto. La posizione fisica
dell’oggetto è interamente trasparente alle applicazioni, ed è quindi possible spostare
oggetti da una macchina all’altra senza cambiare il codice delle applicazioni.
Efficienza accessi memoria di massa – Il database utilizza un sistema di lettura e
scrittura di pagine che contengono dei gruppi di oggetti. Gli oggetti vengono letti a
gruppi e memorizzati in una cache del processo del database server, riducendo così il
numero di volte in cui si rende necessario un accesso alla memoria su disco, poiché
in molti casi i dati richiesti dall’applicazione si troveranno nella cache del server.
Gestione accessi concorrenti - Objectivity/DB consente gli accessi concorrenti a un
database da diversi utenti e processi nella rete garantendo la consistenza dei database
contenuti in un Federated Database attraverso diverse funzionalità:
- modello delle transazioni flessibile: supporta transazioni di qualunque durata, incluse
le long transactions attraverso un meccanismo di checkout/checkin.
- Multiple readers, one writer (diversi lettori, uno scrittore) Aumenta il livello di
concorrenza permettendo a più lettori di leggere lo stato più recente di un oggetto
che è bloccato per modifica da un’altro processo.
-Object-level versioning (C++ and Smalltalk applications) Permette ad una
applicazione di operare su una specifica versione di un oggetto.
- Locking gerarchico: il locking a due fasi gerarchico controla l’accesso in lettura e
modifica degli oggetti.
- Lock waiting: permette ad una transazione di aspettare per un tempo definito
dall’utente di ottenere l’accesso ad un oggetto che è bloccato da un’altra transazione.
29
- Individuazione attiva dei Deadlock fra i database: avvisa riguardo il rischio di un una
situazione di deadlock in cui una o più transazioni sono accodate e ciascuna aspetta
un lock che non diventerà mai disponibile..
Locking Granularity – Un processo può ottenere un read lock, che permette ad altri
processi di accedere in lettura a quell’oggetto, oppure un write lock che non
permette ad altri processi di accedere in lettura o scrittura a quello stesso oggetto.
Un processo può ottenere lock condivisi o esclusivi che si propagano agli oggetti
correlati. Object/db supporta tre livelli di locking: a livello di Database Federato, a
livello di database o a livello di container. I lock sono gestiti trasparentemente
all’applicazione da un lock server che è unico per ogni Federated Database.
Robustezza a guasti e errori – è disponibile una modalità di funzionamento con cui è
possibile dividere un database federato in diverse partizioni e distribuirle su diverse
postazioni nella rete. Questo fa si che in caso di guasto di un nodo gli altri
continuino a funzionare correttamente e sono resi inaccessibili solamente i dati che
risiedono sul nodo caduto. Questa opzione è indicata se si prevede che si abbiano
frequenti accessi a dati locali alle applicazioni e sporadici accessi a dati su altri nodi.
In questa modalità i servizi di controllo di concorrenza, e lo schema dei dati del
database sono replicati, in particolare viene usato un lock server per ogni partizione.
L’utente, in caso di funzionamento senza guasti utilizza il database federato come se
fosse in una partizione unica. È inoltre possibile replicare ogni singolo database di
una partizione e spostare l’immagine del database in un’altra partizione. Se un
database non è disponibile il sistema accede automaticamente alla sua immagine. È
presente un sistema di recupero automatico per cui in caso di malfunzionamento di
un server o di fine anomala di una transazione, viene automaticamente recuperata la
consistenza dei dati.
30
Linguaggio di interrogazione – Gli oggetti nel database possono essere recuperati in
differenti modi. Attraverso la definizione di relazioni, o mettendo un oggetto come
valore di un campo, oppure dichiarando delle relazioni fra gli oggetti utilizzando
delle classi messe a disposizione. È possibile definire relazioni uno a uno oppure uno
a molti, e dare un nome alla relazione e al suo iniverso. È in questo modo possibile
recuperare gli oggetti a partire da uno sorgente di una relazione.
È inoltre possibile assegnare agli oggetti dei nomi al momento in cui vengono salvati
nel database, definire uno Scope, all’interno del quale i nomi siano univoci,
permettendo di ritrovare l’oggetto. Si possono inoltre ottenere degli iteratori che
permettono di esaminare tutti gli oggetti di una certa classe. Oppure è possibile
selezionare gli oggetti di una certa classe che soddisfano un certo predicato, che può
essere costruito utilizzando i comuni operatori logici e aritmetici per testare i valori
delle proprietà di un oggetto. I predicati non possono contenere variabili o metodi
degli oggetti.
Dati distribuiti – I dati possono essere suddivisi in diversi container all’interno di una
stesso database. Container e database sono unità logiche che possono risiedere su
diverse macchine in maniera trasparente all’utente. Gli Oid degli oggetti fanno
riferimento agli identificatori dei database e dei container, tuttavia se un database
viene spostato tutti gli oggetti che contiene mantengono il loro Oid.
Transazioni lunghe Objectivity/DB fornisce un meccanismo di checkout/checkin
che permette di ottenere un lock su un container per un lungo periodo di tempo
Gestione Versioni Il modelo dei dati e gli oggetti possono essere modificati quando
cambiano le esigenze dell’utente e dell’applicazione. Con il meccanismo di schema
evolution è possibile aggiungere nuove classi al database, ma anche fare modifiche
alle classi esistenti, ai lor valori e alle loro gerarchie. È possibile aggiungere togliere e
31
modificare campi e i loro tipi, associazioni e riferimenti agli oggetti, e cambiare o
eliminare le super-classe
Objectivity/db rende automatica la conversione per gli oggetti esistenti che vengono
modificati dall’evoluzione dello. È possibile decidere il momento in cui viene
effettuata la conversione delle classi:ritardato,
on-demand, o immediato. La
modalità ritardata, converte gli oggetti la prima volta che sono richiamati dalla
memoria di massa. La modalità on-demand converte gli oggetti secondo istruzioni
richiamate direttamente dall’applicazione. La modalità immediata implica la
conversione di tutti gli oggetti in una sola volta all’interno di un’unica transazione.
Per ciascuno di questi metodi è possibile definire funzioni di conversione per
calcolare i valori dei nuovi dati a partire da quelli vecchi.
Struttura Client-Server - L’architettura distribuita di Objectivity/db permette di
distribuire I servizi del database su diverse machine, mentre solo il controllo di
concorrenza è centralizzato. Invece di un server centralizzato, vengono utilizzati tre
diversi tipi di processi server: lock server, local data server e remote database server.
In questo contesto i termini locale e remoto sono riferiti all’applicazione.
One-tier design
La configurazione più semplice è quella one tier, in cui è presente una singola
applicazione su una singola macchina. Questo design usa un server locale collegato
all’applicazione che gestisce l’accsso ai dati sul disco. Questa modalità di
funzionamento non richiede un lock server ed è disegnata per un singolo utente.
Two-tier design
È costruito su un singolo e centralizzato processo server. In questo caso viene
aggiunto un server remoto, che consente a più applicazioni che possono essere di
diverso tipo, di accedere al database.
Mixed-tier design
In questa modalità di funzionamento, ogni applicazione è collegata ad un server
locale che consente l’accesso ai dati sulla stessa macchina. Su ogni macchina è però
32
presente anche un server remoto, che permette ad altri server locali di accedere alle
informazioni su quella macchina. In questo caso è necessario u processo
centralizzato per la gestione degli accessi concorrenti.
Piattaforme Supportate Objectivity/DB fornisce una completa interoperabilità fra
differenti piattaforme come windows e UNIX. I processi server possono operare su
diversi tipi di macchine e gli oggetti possono essere distribuiti e spostati fra
componenti che risiedono su diverse acchitetture in modo trasparente. Il sistema
effettua le modifiche, quando sono necessarie, per eliminare le differenze fra le
piattaforme quali l’ordine dei byte, o i formati dei numeri.
Tool
-
Sono forniti numerosi tool per lo sviluppo e l’amministrazione. In
particolare un ambiente per sviluppare, compilare ed eseguire le applicazioni,
aggiungere database e container, modificare, aggiungere o eliminare oggetti del
database. Sono inoltre disponibili, un tool grafico per esplorare le definizioni dei
tipi e delle classi all’interno del database ed uno strumento per costruire ed eseguire
query sugli oggetti salvati.Sono presenti funzionalità per monitorare e settare le
prestazioni del sistema, a seconda delle necessità. É disponibile un tool per effettuare
il backup dei dati su un supporto esterno e per recuperare I dati salvati.
Jyd ODB
è scritto interamente in Java ed è inteso per essere usato con applicazioni Java. Il
linguaggio per creare e modificare gli oggetti e interagire con il database è Java. È
supportato lo standard ODMG attraverso una libreria org.odmg che però non
implementa il linguaggio di query OQL.
Complessità strutturale – La complessità strutturale è garantita dai costrutti del
linguaggio Java che quindi consente di costruire oggetti complessi e strutture con
gerarchie ereditarietà, overloading ecc..
33
Persistenza – Un oggetto è persistente solo quando è referenziato da un altro oggetto.
Se un oggetto non è referenziato da nessun altro, è considerato inutile per il database
e viene eliminato. Il sistema è dotato di un Garbage Collector che elimina gli oggetti
“orfani”.
Tutto il database può essere pensato come un albero di oggetti che si referenziano a
vicenda partendo da un oggetto radice designato. Se un l’oggetto radice, direttamente
o indirettamente referenzia un oggetto, questo è persistente. Per rendere un oggetto
persistente è sufficiente farlo referenziare da un altro oggetto persistente o
direttamente dall’oggetto radice. Gli oggetti che possono essere resi persistenti sono
detti Storable. Gli oggetti storable devono estendere una classe: jyd.dbms.Fetchable
se non cambiano il loro stato, jyd.dbms.StorableObject se cambiano il loro stato.
Queste classi permettono agli oggetti di essere salvati nel database.
Aggiunta di un oggetto alla’oggetto radice del database:
Transaction tran = Transaction.current();
Map root = tran.parent().root();
root.put(account.name(), account);
Identità degli oggetti – Ad ogni oggetto è associate un identificatore persistente
univoco all’interno del database, chiamato pid (persistent ID). Questo viene
assegnato all’oggetto o al momento in cui la transazione in cui l’oggetto è stato
creato esegue il commit, e quindi salva l’oggetto stesso sul database, oppure è
possibile richiedere esplicitamente l’assegnazione dell’id all’oggetto prima del
commit della transazione.
Gestione accessi concorrenti – Il meccanismo usato per il controllo degli accessi
concorrenti è quello dell’ordinamento temporale delle versioni degli oggetti (Multi
Version Timestamp Ordering MVTO). Il momento di inizio di una transazione
determina quali cambiamenti può o non può vedere. Una transazione vede solo le
versioni di un oggetto con timestamp minore del suo Start time. Quando una
transazione fa commit o viene abortita ottiene un nuovo Start time che le permette
34
di vedere le modifiche più recenti. Con il metodo intrinsic Versioing, un oggetto
non viene mai cambiato, ma piuttosto viene creata una nuova versione. Il sistema
può permettere, a seconda del timeStamp ad una transazione di leggere una vecchia
versione di un dato mentre un’altra transazione lo modifica. Le operazioni di read
non sono mai rigettate, se l’operazione concorrente di update ha un timeStamp
minore, l’operazione di lettura attende che si liberi la risorsa. Se la transazione
concorrente che fa update ha un timeStamp maggiore, l’operazione di lettura
procede immediatamente. Una operazione di update attende anche il commit di una
transazione di update con TimeStamp più vecchio. Se i processi sono in esecuzione
sul server, il controllo per i conflitti viene effettuato con l’approccio pessimistico,
cioè appena avviene la richiesta per l’operazione. Per i processi eseguiti sul client,
l’approccio adottato è quello ottimistico, i conflitti vengono rilevati al momento del
commit. Questo per diminuire il traffico sulla rete ed evitare che se cade la
connessione gli oggetti rimangano bloccati.
Locking granularity Il locking è eseguito a livello di oggetto
Robustezza a guasti ed errori – Per gestrire eventuali errori che causano il fallimento
delle transazioni Jyd utilizza una tecnica chiamata shadowing. Ogni transazione
tiene una directory in cui sono memorizzati i cambiamenti (versioni) degli oggetti
effettuati. Il sistema assicura che tutti questi cambiamenti siano scritti sul database
prima di dare l’acknowledgment del commit. Se questo non è possibile la directory
con i cambiamenti viene eliminata e nessun cambiamento viene apportato ai dati già
salvati in memoria.
Il database è diviso in due parti (semi- spaces) che vengono usati dal garbage
collector. Il garbage collector attraversa l’albero del database che si trova nel
semispazio attivo e copia gli oggetti raggiungibili nell’altro semispazio ed elimina gli
altri. L’utilità di backup effettua una copia di uno dei due semi spazi, e può essere
richiamato in diversi modi: come processo separato, oppure dal codice
dell’applicazione, effettua una copia del semispazio attivo; oppure può essere
35
eseguito insieme al garbage collector. Appena il garbage collector ha copiato tutti gli
oggetti nel nuovo semispazio che diventa quello attivo, l’utilità di backup comincia a
costruire un’immagine del vecchio semi-spazio.I file di backup possono essere
richiamati da un processo separato, che richiama automaticamente il file di backup
più recente oppure può richiamare uno specifico backup.
Linguaggio di interrogazione – poiché non è supportato il linguaggio di query
standard di ODMG Object Query Language OQL, l’unico linguaggio di
interrogazione è Java. Gli oggetti possono essere salvati nel database assegnando loro
un nome univoco, che permette di recuperarli; possono essere recuperati anche in
base alla classe di appartenenza.
Dati distribuiti – La verione professional supporta diversi database distribuiti che
compongono un unico database logico, e server distribuiti che collaborano per la
gestione delle transazione. Un client si connette ad un singolo server e se richiede
oggetti remoti il server a cui è connesso si preoccupa di recuperare gli oggetti
richiesti dagli altri server. Se gli oggetti sono creati da oggetti presenti sul nodo del
client vengono salvati sullo stesso nodo. Se gli oggetti vengono creati da oggetti
remoti vengono salvati sullo stesso server.
Gestione versioni – poiché le definizioni degli oggetti sono le stesse nell’applicazione
e nel database, ogni modifica alle proprietà di un oggetto Storable, comporta una
variazione dello schema del database. Nel database le classi sono identificate
solamente attraverso il nome, non è presente nessun identificativo di versione
associato alle diverse definizioni. Per questo quando si modifica una classe è
necessario crearne una nuova con un nome diverso in cui salvare i nuovi campi. È
necessario implementare due metodi, uno nella vecchia che ritorna il nome della
nuova classe, e uno nella nuova che prende come argomento la vecchia classe e
converte i vecchi campi in quelli nuovi. Una implementazione standard è fornita
nella classe astratta Fetchable. Implementati questi due metodi la conversione viene
fatta automaticamente la prima volta che si accede all’oggetto. Per effettuare una
36
conversione di tutti gli oggetti è sufficiente richiamare il garbage collector; una volta
finito avrà richiamato una volta ciascuna istanza nel database. Per ritornare ad avere
una classe con il vecchio nome si ripete il processo utilizzando come nova classe una
con il nome di quella originale che avrà gli stessi campi di quella appena creata.
Architettura client –Server – Il database supporta diverse modalità di esecuzione:
-
Standalone: l’applicazione e il server sono eseguiti sulla stessa macchina e
condividono uno stesso database locale. Quando termina l’applicazione viene
terminata anche l’esecuzione del server.
-
Fat client/Thin server: gli oggetti sono inviati al client e le operazioni sono
eseguite sul client. È necessaria una copia del runtime del database
è
necessaria in entrambi i nodi. Gli oggetti modificati sul client sono rispediti
al server ogni volta che viene effettuato il commit.
-
Thin Client / Fat Server: le operazioni sugli oggetti storable sono invocate
dal client ma eseguite sul server. È necessaria una copia del runtime del
database su entrambi i nodi. Nel client sono create delle rappresentazioni
locali (Proxies) degli oggetti. Per questo è necessario che gli oggetti storable
coinvolti implementino una interfaccia che definisce i metodi che possono
essere invocati dal client.
-
Applet: simile alle modalità Client – Server eccetto che il runtime del client è
eseguito come un applet in un web browser.
-
Web Server: le servlet sono eseguite sul server con accesso diretto agli oggetti
persistenti. Il client può essere qualsiasi browser.
Piattaforme supportate – Poiché è scritto interamente in Java è portabile su diverse
piattaforme che abbiano installato JDK 1.3 o superiore.
37
Tools - Sono disponibili delle classi che implementano le funzionalità per criptare i
dati e le comunicazione fra client e server. È presente una applicazione per effettuare
il backup dei dati.
Eye DB
Complessità strutturale – La creazione degli oggetti e della struttura dei dati è
effettuata con il linguaggio ODL definito dallo standard ODMG, che permette di
definire classi, ereditarietà, attributi e segnatura dei metodi. Inoltre Eye db estende
ODL con delle funzionalità per definire trigger e vincoli come not null o vincoli di
cardinalità.
Tipi e Classi – La definizione delle classi comprende un nome, una classe sorgente
(super-classe) un insieme di attributi, un insieme di metodi e un insieme di trigger.
Un attributo è composto da un tipo, un attributo opzionale che lo indica come un
array di elementi e un modificatore che indica se l’attributo contiene un oggetto o
un letterale. Un oggetto è salvato nel database con un suo id, un letterale non ha un
id proprio. Un metodo è un insieme di operazioni legate ad una classe. Un trigger è
un insieme di operazioni legate ad una classe eseguite a seguito di un determinato
evento.
Gerarchia delle classi – La classe di base è Object. Possono essere istanziate solo classi
che ereditano dalla classe Class. Ogni Istanza di una classe Class è anche istanza della
classe Instance che invece non può essere istanziata.???
Overriding Overloading Late Binding – Poichè il modello dei dati è commune a
quello dei linguaggi di programmazione che possono essere usati per implementare i
metodi e I trigger, è possible per le sotto-classi ridefinire proprietà ereditate.
Persistenza – Gli oggetti creati a runtime dall’applicazione sono definiti persistenti, se
sono collegati alla definizione di un oggetto nel database, o transitori se non lo sono.
Un oggetto a runtime persistente non influenza direttamente l’oggetto nel database.
Le modifiche fatte sull’oggetto a runtime vengono propagate al database solo quando
38
si esegue il comando store all’interno della transazione nella quale le operazioni sono
state svolte.
Identita degli oggetti – Un oid identifica un oggetto in modo univoco all’interno di
un set di database, l’oid è creato dallo storage manager al momento della creazione
dell’oggetto.
L’oid è composto di tre campi: lo storage index, l’identificatore di database e un
‘numero magico’ generato casualmente. Il primo campo indica la posizione fisica
dell’oggetto all’interno di un volume di un database, il secondo campo indica un
database rispetto al quale su quell’oggetto sono garantite le proprietà ACID, l’ultimo
campo aumenta la sicurezza nella gestione dell’identificazione dell’oggeto.
Efficienza accessi in memoria di massa – Gli oggetti del database sono mappati
direttamente nella memoria virtuale; le copie dalla memoria sono ridotte utilizzando
una cache lato server.
Gestione accessi concorrenti - Lo storage manager fornisce I servizi standard per le
transazioni, garantendo atomicità, consistenza, isolamento e integrità all’interno del
database. Le transazioni sono gestite con un protocollo di locking a due fasi, il quale
richiede che ogni transazione emetta le richieste di lock e unlock in due fasi: fase di
Growing : una transazione può ottenere dei lock ma non può rilasciarne alcuno fase
di shrinking : una transazione può rilasciare lock ma non può ottenerne.
Inizialmente una transazione è nella fase di growing, e ottiene i lock che richiede.
Una volta che rilascia un lock entra nella fase di shrinking e non può più lanciare
altre richieste di lock. Lo storage manager fornisce altri differenti modalità di
locking nelle transazioni: lettura e scrittura condivise, lettura condivisa e scrittura
esclusiva, lettura e scrittura esclusive o database esclusivo.
Robustezza errori e guasti – Il meccanismo previsto è molto elementare: in caso di
errore del client la transazione corrente viene immediatamente abortita; in caso di
errore del server o del sistema la transazione viene abortita non appena il server
torna disponibile.
39
Linguaggio interrogazione - EyeDb fornisce un linguaggio di query basato sullo
standard ODMG OQL. Il linguaggio di query di eyeDb
non è un Object
Manipulation Language, tuttavia possono essere eseguite tutte le principali
operazioni dei comuni linguaggi: oprazioni logiche e aritmetiche, manipolazione di
stringe, controllo di flusso e definizione di funzioni e infine query. Rispetto al
linguaggio di ODMG sono aggiunte delle funzionalità come il controllo di flusso( if
else foreach), la definizione di funzioni, un operatore di assegnamento, operatori per
espressioni regolari.
Dati distribuiti – La distribuzione dei dati è supportata attraverso CORBA che
definisce una interfaccia e un server che consentono un accesso comune ad un
database.
Gestione versioni – non supportato
Architettura client- server - Il sistema è basato su una architettura client server. il
server è composto di: livello di protocollo server basato su Remote Procedure Call,
implementazione del dell’object model, il motore di query OQL, il sottosistema
dello storage manager.
Il client è composto di: codice dell’applicazione dell’utente, le API C++ o Java che
implementano il binding C++ o Java, il livello di protocollo client basato su RPC.
Piattaforme supportate – Solaris 2.x Sparc e Linux.
Tool – è fornito un tool grafico simile ad un web browser che permette di esplorare i
dati contenuti nel database.
40
Versant
Complessità strutturale – Il sistema fornisce interfacce per l’interazione con il
database nei linguaggi C, C++, Java, che permettono di creare strutture dei dati
anche molto complesse.
Tipi e classi – Quando vengono create le classi il sistema genera uno schema object
che rappresenta la struttura della classe nel database. Per le classi definite in
linguaggio C++, il sistema a runtime crea un oggetto che ne identifica il tipo e che
contiene delle informazioni aggiuntive necessarie alle clasi definite in C++. Ogni
istanza di queste classi possiede un metodo is_a che punta all’oggetto identificatore
che individua la classe dell’oggetto.
Gerarchia delle classi – La gerarchiia è quella del linguaggio di interfaccia usato. Per le
classi C++, l’oggetto che ne identifica il tipo che viene creato dal sistema è una
istanza della classe PClass fornita nelle librerie di interfaccia. Ogni oggetto
persistente deve estendere la classe PObject.
Overloading Overriding Late binding – Supportato dalle interfacce per C++ e Java,
limitazioni sono imposte dal linguaggio C.
Ereditarietà multipla – Per consentire l’ereditarietà multipla, all’interno del database
ogni campo viene memorizzato con un nome univoco composto dalla
concatenazione del nome della classe e quello del campo. Se L’attributo è un
riferimento ad un altro oggetto il nome è composto da quello della classe, più il
nome della classe referenziata, più il nome dell’attributo.
Persistenza – Gli oggetti persistenti nel database hanno il loro corrispondente oggetto
nella memoria dell’applicazione che li rappresenta. Questi oggetti sono detti First
Class Objects. Quando un oggetto del database viene esaminato da una applicazione,
vengono caricate nel FCO solo le parti dell’oggetto richieste. Per poter essere salvate
e gestite dal database le classi devono essere modificate attraverso un tool che
aggiunge le caratteristiche necessarie. Le modifiche apportate dal tool sono diverse a
41
seconda di quali caratteristiche di persistenza si vuole che abbia la classe.
È
necessario definire in un file testuale (oppure con una utilità grafica a disposizione) la
categoria a cui appartengono le classi:
-
Persistent Capable: gli oggetti di questo tipo possono essere ressi persistenti
implicitamente o esplicitamente. Gli oggetti di questo tipo ancora non resi
persistenti sono detti transitori.
-
Persistent Always: gli oggetti di questo tipo vengono resi persistenti non
appena vengono creati.
-
Transparent Dirty Owner: gli oggetti di questo tipo sono incapsulati in
oggetti persistenti.
-
Persistent Aware: queste classi sono modificate dall’Enhancer per interagire
correttamente con gli oggetti persistenti in modo da caricare dalla memoria i
dati quando vengono acceduti o marcare gli oggetti modificati.
-
Not persistent Aware: le classi di questo tipo non sono modificatre e
possono accedere ai campi dei FCo solo attraverso i loro metodi e non
direttamente.
Identità degli oggetti – Ad ogni oggetto viene associato un identificatore univoco
chiamato logical object identifier loid composto da due parti: identificatore di
database e identificatore di oggetto. La prima parte è un identificatore per il database
di creazione dell’oggetto ed è unico all’interno di un sistema di databases. Ogni
database riceve questo oid unico al momento della sua creazione dal sistema che
salva gli id di tutti i database in un unico file centralizzato. La seconda parte di un
loid di un oggetto è univoco fra tutti gli oggetti di un database. L’identificatore di un
oggetto non viene modificato se l’oggetto viene spostato, e se l’oggetto viene
cancellato il suo loid non viene più riutilizzato.
Efficienza accessi in memoria – Quando viene inizializzato un database viene creata
una cache che contiene gli oggetti utilizzati più di recente dalle applicazioni. La
42
cache contiene inoltre una tabella con le informazioni di accesso ai dati. I dati
contenuti nella cache contengono la versione aggiornata dei dati, è si può decidere se
quelli modificati rispetto alla versione salvata nel database vengono scritti subito
oppure al termine della sessione.
Gestione accessi concorrenti – Gli accessi da parte di diversi utenti sono gestiti
attraverso l’uso di transazioni e locking. Sono supportati transazioni e lock lunghi e
corti. I lock brevi, short lock, sono aquisiti e rilasciati nell’arco della durata di una
transazione, che solitamente comprende delle unità di lavoro che comportano una
durata di pochi secondi o minuti. I lock e le transazioni di questo tipo non
sopravvivono in caso di caduta del sistema. I lock e le transazioni lunghe sono usati
per richiedere lock per operazioni che possono richiedere diversi minuti oppure ore
o giorni.
Lock granularity – Il livello a cui i lock sono effettuati è quello di un oggetto. É
possibile richiedere i lock sia sulle istanze presenti nel database sia sui Class Object
che difiniscono la struttura di una classe di oggetti. Porre un lock su un Class object
è equivalente a mettere un lock su ogni singolo oggetto di quella classe, ma in modo
più facile e veloce. Gli oggetti che sono referenziati da un oggetto su cui è posto un
lock non vengono a loro volta bloccati.
Robustezza a guasti ed errori - é presente un modulo chiamato Fault tolerant Server
che permette di replicare un database. Viene effettuata una copia del database che
può spostata in un qualsiasi punto della rete di databases. Le due copie sono
chiamate replica pair. Il sistema, in caso di malfunzionamento di una copia continua
ad usare l’altra. Quando il database torna disponibile il sistema lo sincronizza con la
sua copia attiva che continua ad essere aggiornata e modificata dagli utenti.
Linguaggio di interrogazione – Non è supportato il linguaggio query standard di
ODMG Object Query Language. Viene invece usato un linguaggio derivato. Per
eseguire le query è necessario esprimere il predicato in una stringa che poi viene
passata come argomento ad una funzione che esegue la ricerca. La sintassi del
43
predicato è quella con i consueti operatori select from e operatori aritmetici e di
confronto. L’esecuzione delle query è sul server. È possibile recuperare tutti gli
oggetti di una classe utilizzando il nome della classe stessa.
nell’interfaccia di programmazione Java
In alternativa
le query possono essere effettuate
attraverso dei Predicate Object che sono passati poi ad un metodo di ricerca della
sessione. Per costruire un predicato si usano degli oggetti Attr che rappresentano gli
attributi su cui si vogliono testare delle condizioni. Questi oggetti mettono a
disposizione delle funzioni che consentono di verificare se il valore dell’attributo
soddisfa la condizione. I predicate object possono essere concatenati con gli
operatori AND OR ecc per ottenere predicati più complessi.
Dati distribuiti – I dati possono essere distribuiti su diversi database posizionati in
diversi nodi della rete e su diverse piattaforme. Gli oggetti possono essere spostati da
un database all’altro. L’integrità dei dati è mantenuta utilizzando la modalità di two
phase commit per le transazionii distribuite in modo che i database comunichino tra
loro e le modifiche vengano apportate o annullate simultaneamente. Le applicazioni
possono connettersi a più database distribuiti e maneggiare gli oggetti come se
fossero tutti in un database locale.
Transazioni lunghe – Le transazioni lunghe sono supportate utilizzando anche un
particolare tipo di lock lungo.
Gestione versioni – Lo schema degli oggetti deve essere coerente alla definizione di
classe utilizzata, pertanto quando un oggetto viene caricato il sistema aggiorna
automaticamente lo schema salvato nel database con il nuovo modello utilizzato. Il
sistema aggiorna gli oggetti alla nuova versione man mano che vengono richiamati e
non tutti in una sola operazione (lazy update). I cambiamenti allo schema quali
modifica del nome dei parametri, aggiunta o rimozione di parametri, aggiunta di
classi permettono al sistema di modificare automaticamente lo schema nel database.
Altre modifiche come la rimozione di classi, la modifica della gerarchia, necessitano
44
di operazioni specifiche a seconda del linguaggio utilizzato e una successiva
conversione di tutti gli oggetti con una versione vecchia.
Architettura client server – L’architettura è molto semplice costituita da un client, su
cui viene eseguita l’applicazione. Il client può essere connesso in maniera
concorrente a più server, ognuno dei quali è collegato ad un database. Client e server
possono essere su una stessa macchina o remoti e comunicano attraverso un
protocollo TCP/IP . Uno dei compiti del server è quello di eseguire le query e
ritorna solo gli oggetti desiderati.
Piattaforme supportate – I sistemi operativi supportati sono Windows NT 4.0 e
Windows 2000; Solaris 2.8, IBM AIX 4.3 Linux Red Hat.
Tools – Sono disponibili funzionalità per importare o esportare gli oggetti contenuti
nel database in formato XML
e per creare applicazioni che effettuino queste
operazioni in maniera programmata. Sono inclusi altri tool visuali per gestire il
database. Con i diversi tool è possibile creare, rimuovere, avviare e fermare i
database; aggiungere e rimuovere utenti e permessi; visualizzare i lock, le
transazioni, le connessioni e gli utenti connessi al database. Possono essere
visualizzate graficamente le attività del database e aggiustarne le prestazioni. Infine è
possibile esplorare graficamente i contenuti del database e gli schemi degli oggetti,
inoltre costruire query attraverso un’interfaccia senza dover utilizzare il linguaggio
VQL (versant query language).
Orient
Complessità strutturale – Per poter utilizzare il database è necessario definirne prima
lo sche nel linguaggio ODL di ODMG. Pertanto è possibile utilizzare tutte le
strutture dati messe a disposizione da questo standard.
45
Tipi e Classi – La definizione dello schema del db in ODL deve essere effettuata
descrivendo le classi con i tipi predefiniti messi a disposizione dallo standard di
ODL. I tipi messi a disposizione sono indipendenti dal linguaggio di
programmazione usato: dallo schema del database, attraverso un compilatore
specifico del linguaggio, vengono creati i file sorgenti, in cui i tipi standard di ODL
vengono trasformati in quelli specifici del linguaggio. Le classi che devono essere rese
persistenti devono implementare un specifica interfaccia a seconda del linguaggio di
programmazione usato.
Gerarchia delle classi – Overloading Overriding Late Binding – Ereditarietà Multipla –
Le caratteristiche per la creazione della struttura delle classi sono quelle messe a
disposizione dallo standard ODMG, ODL.
Tipi utente – Le classi definite dall’utente possono essere inserite nello schema del
datbase con tutte le caratteristiche messe a disposizione e possono essere costruite
con il livello di complessità desiderato e gerarchie complesse.
Persistenza – UN oggetto definito come persistent capable, cioè che può essere reso
persistente, può essere creato come oggetto persistente o transitorio. Quando viene
creato come oggetto persistente esiste solo nella meoria dell’applicazione, ma viene
marcato come persistente. La creazione modifica e cancellazione di oggetti
persistenti deve essere effettuata all’interno di una transazione attiva. Quando la
transazione effettua il commit, gli oggetti marcati persistenti sono salvati nel
database e vengono aggiornate le cache del client e del server per rendere disponibile
più velocemente l’oggetto a future richieste. Quando vengo effettuate le operazioni
di modifica, salvataggio e cancellazione degli oggetti il sistema chiama gli eventuali
trigger definiti dall’utente che possono interrompere l’operazione invocata
sull’oggetto.
Efficienza degli accessi in memoria di massa – Il server gestisce le richieste del client
recuperando e fornendo solo gli oggetti necessari, e non intere pagine di memoria.
Inoltre sono utilizzate cache sia a lato client che a lato server che permettono un
46
accesso più efficiente ai dati richiesti di recente. I dati nel database vengono
recuperati e modificati solo nel momento in cui viene modificato il dato presente
nella cache.
Gestione accessi concorrenti – Il sistema è disponibile in due versioni: Just edition e
Enterprise Edition. La versione Just è pensata per un singolo utente pertanto non è
prevista la gestione degli accessi concorrenti. È possibile comunque ottenere accessi
multipli al database utilizzando diversi thread all’interno di una sessione. Nella
versione enterprise invece è previsto l’utilizzo del database da parte di utenti multipli
e la gestione degli accessi è effettuata tramite dei lock sugli oggetti. La politica di
locking è quella di più processi che possono leggere il dato contemporaneamente ma
un solo processo può modificarlo. Questa politica di gestione dei lock sugli oggetti è
detta Multiple readers One Writer (MROW).
Locking granularity – Poiché il server funziona come un object server, cioè
recuperando singolarmente gli oggetti e non intere pagine di memoria, il locking è
effettuato a livello di oggetto.
Robustezza a guasti ed errori – L’architettura distribuita può essere protetta da guasti
ed errori di singole parti posizionando diversi server in nodi differenti che si
occupano di ricevere le richieste dei client e di indirizzarle al database. Un altro
componente permette di replicare i database in diverse posizioni della rete, salvando
i dati in più di un posto. IN questo modo una copia dei dati è sempre di esponibile
in caso di indisponibilità di una copia di un database. Il componente che permette la
replicazione si occupa anche di renderla trasparente all’applicazione. È possibile
effettuare il backup dei dati con un tool apposito.
Linguaggio di interrogazione – Non è supportato il linguaggio standard di
interrogazione OQL )Object Query Language di ODMG). Il linguaggio utilizzato è
un sottoinsieme di SQL- 92 con delle estensione per la gestione degli oggetti e delle
relazioni. Le query sono eseguite valutando condizioni con una sintassi del tipo:
47
<Class>.<Member> <Operator> <Expression> in cui si possono navigare le
relazioni a partire dalla classe specificata per utilizzare un dato membro dell’oggetto
referenziato. Gli operatori son quelli standard definiti in SQL: AND, OR, <=, !=,
ecc.. L’espressione può essere costituita da un singolo valore, numerico o stringa,
oppure da un’espressione. Attraverso le librerie di accesso al database è possibile
recuperare tutti gli oggetti appartenenti ad una classe salvati nel database (Extent) e
contarne il numero di elementi. È possibile anche recuperare un solo elemento di un
certo Extent passando come argomento alla funzione la condizione che l’oggetto
deve soddisfare.
Dati distribuiti – La versione Just non supporta la distribuzione dei dati. La versione
enterprise permette di creare diversi database in postazioni diverse. I database infatti
sono gestiti da un processo chiamato SuperAgent che registra gli indirizzi dei
database presenti nella rete. Il client si connette al super agent che gli nasconde la
distribuzione dei dati. I dati possono anche essere replicati per maggiore sicurezza e
resistenza ai guasti.
Transazioni lunghe - non supportate
Gestione versioni – non supportata
Architettura client server - Nell’edizione Enterprise il carico di lavoro è diviso fra
client e server in modo da migliorare performance e scalabilità. Molte operazioni
sugli oggetti avvengono lato client (creazione, update, cancellazione) finchè una
transazione non fa commit. Questo è possibile poichè il client conosce lo schema
del database e il layout degli oggetti. Questo toglie molto lavoro al server
permettendo così una buona scalbilità con molti client connessi. È possibile
costruire scenari complessi con server replicati per ottenere un sistema fault –
tolerant e garantire load balancing.Al client la posizione del server viene resa
trasparente
con
un
componente
chiamato
SuperAgent.
Questo
registra
dinamicamente tutti i server connessi nella rete e assegna ad ogni client il server
migliore attraverso un algoritmo. Inoltre le richieste dei client possono essere
48
distribuite su diversi server per bilanciare il carico di lavoro. Anche il SuperAgent
può essere replicato.La replicazione di tutti i componenti è garantita da un
componente chiamato Replicator, che propaga tutte le operazioni a tutti i server
nella rete, salvando i dati in diverse posizioni distribuite in diversi punti della rete
stessa.
Piattaforme supportate – windows –Linux
Tools – Sono disponibili alcuni tool per effettuare le operazioni di gestione
elementare del database. Per prima cosa il compilatore ODL che legge da un file
testuale la descrizione dello schema dei dati scritta in linguaggio ODL, e costruisce il
database. È possibile utilizzare il tool anche solo per verificare la correttezza della
definizione dei dati scritta in ODL Successivamente è possibile utilizzare un altro
compilatore, differente a seconda che si usi il linguaggio di programmazione Java o
C++, che, a partire dallo schema presente nel database produce i file sorgenti del
linguaggio desiderato contenenti le classi equivalenti allo schema. È disponibile un
tool grafico che permette di esplorare i contenuti del database, e invece un altro tool
da linea di comando che fornisce dettagliate informazioni sullo stato del database.
Neo Access
Complessità strutturale – Il linguaggio per costruire gli oggetti è C++, e può essere
usato senza limitazioni per definire il modello dei dati. In questo caso le
informazioni sulle classi che devono essere salvate nel database non sono definite in
uno schema ma vengono associate ad ogni classe attraverso una metaclasse che ne
contiene appunto la descrizione delle proprietà, comprese quelle necessarie al
database per recuperare e modificare i dati.
Tipi e classi – Ogni classe viene identificata attraverso un identificatore unico fra
tutte le classi definite dall’applicazione. Questo identificatore è assegnato alla classe
nell’intestazione della classe stessa è può essere scelto dal programmatore. L’id della
classe è inoltre usato per determinare il tipo a cui appartiene un oggetto.
49
Gerarchia delle classi – La gerarchia delle classi può essere costruita liberamente, le
classi che devono essere rese persistenti devono derivare dalla classe CNeoPersist.
Persistenza – Per rendere persistente un oggetto è necessario, dopo averne creato la
descrizione con la metaclasse, utilizzare una funzione messa a disposizione dalle
librerie del sistema:
getDatabase()->addObject(aPerson);
Gli oggetti che devono essere persistenti devono ereditare dalla classe
CNeoPersistent che contiene tutte le funzionalità per consentire all’applicazione di
utilizzare gli oggetti nel database.
Identità degli oggetti – Ogni classe di oggetti è identificata con un numero univoco.
Ogni oggetto è individuato da un identificativo, che può anche non essere univoco
fra gli oggetti di una stessa classe, e che può essere assegnato dall’applicazione al
momento di inserimento nel database. Se l’applicazione passa al database un valore
pari
a
zero,
il
database
assegna
un
identificatore
univoco
all’oggetto
automaticamente.
Efficienza accesso memoria di massa – Viene usata una cache lato server che contiene
gli oggetti usati dalle applicazioni. Quando nessuna applicazione fa riferimento ad
un oggetto questo viene comunque mantenuto in memoria. Se la richiesta di
aggiunta di un oggetto alla cache fa eccedere le dimensioni massime di memoria
disponibili, il sistema richiama un garbage collector, che elimina dalla cache gli
oggetti meno utilizzati.
Gestione accessi concorrenti – La gestione di accessi concorrenti è disponibile con un
componente aggiuntivo Neo Share, che controlla gli accessi grazie ad un
meccanismo di locking che può utilizzare sia l’approccio ottimistico (controllo dei
conflitti al momento del commit) o quello pessimistico (lock dell’oggetto all’inizio
dell’operazione di modifica).
Locking granularity – Il locking è effettuato a livello di oggetto.
50
Robustezza a guasti ed errori – è possibile replicare i dati memorizzati nel database per
ottenere una distribuzione del carico di lavoro e una maggior flessibilità in caso di
guasti o errori ad una copia dei dati.
Linguaggio di interrogazione – Il meccanismo per recuperare gli oggetti è composto
da una particolare classe di oggetti: CNeoSelect. A questa classe viene passata la
classe di oggetti tra cui si deve cercare, le eventuali sottoclassi da includere/escludere
dalla ricerca, i criteri che l’oggetto deve rispettare, e in caso di risultati con più
oggetti i criteri per l’ordinamento.
Transazioni lunghe – non supportate
Gestione versioni – Nella metaclasse, che comprende oltre alla descrizione dei
parametri membri , anche la descrizione delle funzioni per accedere al database,
viene incluso un identificativo di versione e le modalità di accesso a quella versione.
Attraverso la descrizione contenuta nella metaclasse il sistema può accedere ad
oggetti con versioni diverse da quella attuale salvati nel database.
Architettura client Server – Attraverso il componente NeoShare è possibile utilizzare
la condivisione del database da parte di più utenti (client) che utilizzano anche
diverse piattaforme. L’accesso ai dati da parte dei client è effettuato con protocollo di
comunicazione TCP/IP.
Piattaforme supportate – Le piattaforme supportate sono Windows Unix e
Machintosh
51
Orient
NeoAccess
Jyd
EyeDb
y
Objectivit
Versant
ObjectDB
TABELLA COMPARATIVA
Costruzione di modello dei dati
complesso.
SI
SI
SI
SI
SI
SI
SI
Overloading Overriding Late
Binding
SI
SI
SI
SI
SI
SI
SI
Ereditarietà Multipla
SI
SI
SI
SI
SI
SI
SI
ID univoco per ogni oggetto
SI
SI
SI
SI
SI
SI
-
ID logico
SI
SI
SI
NO
SI
SI
-
ID fisico
NO
NO
NO
SI
NO
NO
-
ID cambia
NO
NO
NO
-
NO
-
-
Lock a livello di oggetto
SI
SI
NO
-
SI
SI
SI
Lock a livello di pagina
NO
NO
NO
-
NO
NO
NO
Lock a livello container
NO
NO
SI
-
NO
NO
NO
Individuazione deadlock
SI
SI
SI
-
-
-
-
Locking ottimistico
-
SI
SI
-
-
SI
SI
Modello bilanciato client –
server
NO
NO
NO
NO
SI
NO
NO
Modello server fat
SI
SI
SI
SI
SI
SI
SI
Modello client fat
NO
NO
NO
NO
SI
NO
NO
Query eseguite sul server
SI
SI
-
SI
-
SI
SI
Cache server
SI
SI
SI
SI
-
SI
SI
Cache client
NO
NO
NO
NO
-
NO
SI
Migrazione Oggetti
SI
-
SI
NO
-
-
-
52
Orient
NeoAccess
Jyd
EyeDb
y
Objectivit
ObjectDB
Versant
Aggiunta on-line di database e
volumi
SI
NO
SI
NO
-
-
NO
Dati distribuiti
SI
NO
SI
SI
SI
SI
SI
Distribuzione dei dati
trasparente all’applicazione
SI
NO
SI
SI
SI
SI
SI
Schema evolution on-line
SI
SI
SI
NO
SI
SI
NO
Replicazione dei dati
SI
-
SI
NO
NO
SI
SI
Trigger
NO
NO
-
SI
-
NO
SI
Backup on Line
SI
-
SI
NO
SI
-
SI
Tool importazion-esportazione
XML
SI
NO
NO
NO
NO
NO
NO
Supporta ODMG
SI
NO
NO
SI
SI
NO
SI
Supporta OQL
NO
NO
NO
SI
NO
NO
SI
Supporta JDO
NO
SI
NO
NO
NO
NO
NO
Supporta C
SI
NO
NO
NO
NO
NO
NO
Supporta C++
SI
NO
SI
SI
NO
SI
SI
Supporta Java
SI
SI
SI
SI
SI
NO
SI
Supporta Smalltalk
NO
NO
SI
NO
NO
NO
NO
Condivisione oggetti fra i
linguaggi
SI
-
SI
SI
-
NO
SI
WINDOWS
SI
-
SI
NO
-
SI
SI
LINUX
SI
-
SI
SI
-
SI
SI
Indipendente dalla Piattaforma
(Funziona dove è supportato
NO
SI
NO
NO
SI
NO
NO
53
Capitolo4
APPLICAZIONE D’ESEMPIO
Descrzione
L’applicazione d’esempio sviluppata rappresenta un sistema per registrare le
operazioni elementari di un autonoleggio e dei suoi clienti, ovvero la registrazione di
nuovi clienti che possono effettuare dei noleggi delle auto disponibili, l’inserimento
di veicoli da parte dell’amministratore e la visualizzazione di statistiche riguardo i
noleggi effettuati.
L’applicazione è stata sviluppata per un singolo utente, non considerando la
possibilità di accessi concorrenti, questo perché non tutti i prodotti esaminati, nella
versione disponibile in prova, prevedevano questa caratteristica, e per semplicità di
realizzazione.
Per semplificare la scrittura del codice dell’applicazione con i diversi prodotti, è stata
creata una classe ausiliaria chiamata FunzioniDb che contiene dei metodi e delle
variabili statiche, in cui per ogni prodotto sono state inserite le funzioni e le variabili
per eseguire le interazioni più comuni con il DBMS come ad esempio iniziare e
terminare una transazione. In questo modo, almeno per queste funzioni elementari,
si è potuta mantenere una comune interfaccia di chiamata.
Le principali funzioni definite nella classe FunioniDb sono:
- public static void openConnection()
Stabilisce la connessione con il database che verrà mantenuta aperta per tutta
la vita dell’applicazione.
- public static void startTransaction()
54
Inizia una nuova transazione per consentire di effettuare operazioni sugli
oggetti contenuti nel database.
- public static void closeTransaction()
Chiude una transazione quando questa è andata a buon fine. Esegue cioè il
commit() dei dati verso il database per rendere effettive e persistenti le
modifiche apportate.
- public static void closeConnection()
Chiude la connessione con il database al termine di tutte le operazione
effettuate. Viene chiamata al momento in cui l’applicazione viene chiusa.
Per ogni prodotto sono inoltre state inserite delle variabili che fanno riferimento agli
oggetti messi a disposizione dalle librerie per interagire con il database, come ad
esempio, il database stesso, uno specifico container, la sessione ecc…
Specifiche
L’applicazione deve consentire la gestione dei dati maggiormente significativi per
quanto riguarda un autonoleggio. In particolare deve essere possibile l’utilizzo
dell’applicazione da parte di due differenti tipi di utente: il cliente e
l’amministratore.
Il cliente deve poter accedere ad una finestra in cui effettuare i noleggi. Per far questo
è necessario che il cliente possieda un “account “ memorizzato nella base di dati che
contiene tutti i suoi dati. In particolare devono essere inseriti il cognome e una
password che rappresentano la chiave univoca di identificazione di un cliente
dell’autonoleggio. È quindi consentito che due clienti siano registrati con lo stesso
cognome e password diversa o password uguale e diverso cognome. L’account può
essere creato dal cliente all’avvio dell’applicazione qualora il cliente non sia ancora
registrato.
Una volta effettuato il login il cliente accede ad una finestra in cui può effettuare dei
noleggi. L’operazione di noleggio di un veicolo comporta la registrazione dei dati del
55
noleggio cioè: data di inizio e fine del noleggio, veicolo noleggiato. Il cliente deve
poter scegliere il veicolo da una lista dei veicoli disponibili. Deve inoltre essere
presente una lista dei noleggi precedentemente effettuati di cui devono essere
visualizzati tutti i dati. Una volta inseriti i dati di un noleggio e confermato
l’inserimento, il noleggio viene inserito nella lista dei noleggi effettuati e deve essere
confermato dall’amministratore che al termine del noleggio verifica la correttezza
delle date e inserisce i km percorsi.
L’amministratore non è considerato come un cliente, cioè non possiede un suo
account, ma accede al sistema con un nome e una password prestabilite.
L’amministratore deve poter gestire la flotta dei veicoli. In particolare, visualizzare
l’elenco dei veicoli presenti e tutte le informazioni relative, cioè marca, modello,
targa, tipo (auto o moto), km percorsi e stato (disponibile o no), numero di porte
per le auto e capacità totale di eventuali borse o sottosella per le moto; modificare le
informazione relative ad un veicolo, e inserirne di nuovi o cancellarne dalla lista in
modo che non siano più disponibili per il noleggio.
L’amministratore deve poter visualizzare i noleggi effettuati dai clienti e confermare
i dati inseriti per quanto riguarda le date di inizio e fine noleggio e inserire i km
percorsi. A disposizione dell’amministratore deve essere presente una finestra di
statistiche che mostri il numero di clienti, di noleggi e di veicoli registrati e il
numero di veicoli disponibili per il noleggio. Devono essere inoltre consultabili
statistiche riguardo:
- il numero di noleggi effettuati in ogni giorno della settimana
- la media dei km percorsi in un noleggio e i km totali di tutti i noleggi
- la media dei km percorsi in un noleggio per ciascun veicolo
- il numero massimo di km percorsi da ciascun veicolo in un noleggio
- i clienti che non hanno mai effettuato noleggi di durata inferiore ai 30 gg.
56
Modello dei dati
Il modello dei dati dell’applicazione consiste nelle entità Cliente, Noleggio, Veicolo,
Auto Moto. Ogni entità è rappresentata da una classe Java.
La classe veicolo è una classe astratta che contiene le caratteristiche comuni alle due
sottoclassi Auto e Moto, cioè gli attributi marca, modello, targa, kmPercorsi e i
metodi per accedere e modificarne i valori. È inoltre presente un attributo booleano
che indica se il veicolo è disponibile per il noleggio. La classe auto eredita tutte
queste proprietà e in più definisce la proprietà numPorte. Moto invece introduce il
campo litriBorse che rappresenta la capacità di eventuali borse portaoggetti.
La Classe cliente contiene i dati relativi a nome, cognome, numero della patente e
password e i relativi metodi di accesso e modifica.
La classe Noleggio contiene i riferimenti al cliente che ha effettuato il noleggio e il
veicolo noleggiato. Sono presenti i campi che memorizzano i chilometri che il
veicolo aveva al momento del noleggio e il campo dei km percorsi durante il
noleggio. Sono presenti i campi delle date di inizio e fine noleggio e infine un
attributo booleano che indica se il noleggio è già stato o meno verificato
dall’amministratore.
57
Interfaccia
L’interfaccia dell’applicazione comprende di una finestra di login in cui i clienti
inseriscono cognome e password per accedere alla finestra in cui effettuare i noleggi
mentre
l’amministratore accede alla finestra di gestione. Dalla finestra iniziale di login è
possibile aprire un modulo in cui effettuare la registrazione di un nuovo utente.
La finestra dell’utente consente di visualizzare i propri dati: nome e cognome, e una
lista dei noleggi effettuati precedentemente. Selezionando uno dei noleggi della lista
vengono mostrate le informazioni sulla data di inizio e fine, km percorsi, veicolo
utilizzato e se il noleggio è già stato registrato dall’amministratore. Per effettuare un
58
nuovo noleggio si sceglie un veicolo nel campo a tendina, verificandone le
caratteristiche mostrate a fianco, si inseriscono le date di inizio e fine nel formato
aaaa/mm/gg e si confermano i dati con il pulsante “nuovo noleggio”.
L’amministratore deve accedere inserendo nei campi “cognome” e “password” le
parole “admin” e “admin”. Nella finestra dell’amministratore viene mostrato un
elenco dei veicoli disponibili in una lista. Selezionando uno degli elementi ne
vengono mostrate le caratteristiche. L’amministratore può modificare, cancellare o
creare nuovi veicoli inserendo i dati nei campi; se viene inserito il dato riguardante il
“numero di porte”, verrà creato un nuovo veicolo di tipo Auto e non sarà possibile
modificare il campo “capacità borse”. Viceversa verrà creato un nuovo veicolo di
tipo Moto e il campo “numero porte “ non sarà modificabile.
L’amministratore può inoltre attraverso il menu “Visualizza” aprire il pannello per
la registrazione dei noleggi. Selezionando dalla lista uno dei noleggi non ancora
registrati verranno visualizzate le date di inizio e fine e sarà possibile inserire i km
percorsi e confermare i dati registrando il noleggio.
Infine a disposizione dell’amministratore è una finestra di statistiche in cui vengono
riepilogati il numero di clienti, veicoli e noleggi presenti nel database, e inoltre è
possibile scegliere da un menu di visualizzare delle statistiche riguardanti i noleggi, i
veicoli e i clienti.
59
Objectivity DB
Gli oggetti che devono diventare persistenti devono estendere la classe ooObjy,
direttamente o ereditando da una classe che a sua volta estende ooObjy. In questo
caso la classe Veicolo estende ooObjy, e le classi Auto e Moto ne ereditano le
proprietàè di persistenza.
L’architettura del database di Objectivity consiste in un federated database che
riunisce un insieme di database che a loro volta sono costituiti da più container nei
quali vengono salvati gli oggetti persistenti. All’interno dell’applicazione è possibile
sceglier in quale database o container salvare di volta in volta i dati. In questo caso si
è scelto di utilizzare un solo database ed un solo container data la semplicità
dell’applicazione e del modello dei dati.
Connessione al database
Ognuna delle entità descritte è rappresentata da un oggetto attraverso le librerie
messe a disposizione dal DBMS per Java ed è quindi presente nella classe di appoggio
FunzioniDb:
public class FunzioniDb {
static ooFDObj fedDB;
static ooDBObj dbAutosalone;
static ooDefaultContObj container;
static Connection conn ;
static Session session;
…//Definizione dei metodi
}
La classe FunzioniDb definisce anche i seguenti metodi:
La funzione utilizza una stringa che contiene l’indirizzo per raggiungere il file con le
indicazioni della struttura del Federated database e dello schema dei dati contenuti;
può rappresentare un indirizzo locale o remoto. Con questo file viene aperta una
connessione con il database specificando anche la modalità di interazione che in
questo caso è ReadWrite, e consente di leggere e scrivere dati sul database.
60
Vengono catturate possibili eccezioni dovute alla mancata connessione o al fatto che
sia già aperta una connessione (una applicazione può ottenere una sola connessione
per volta).
public static void openConnection(){
String bootfile="c:/objy80/luca.boot";
conn = null;
try {
conn=Connection.open(bootfile,oo.openReadWrite);
} catch (DatabaseOpenException e) {
e.printStackTrace();
} catch (DatabaseNotFoundException e) {
e.printStackTrace();
}
}
Questo metodo ottiene un’istanza dell’oggetto che rappresenta la sessione da cui si
ricavano gli oggetti che rappresentano il Federated database, il database desiderato
identificato da un nome (si può recuperare un db di default o tutti i db presenti), e il
container, in questo caso quello predefinito. Con la chiamata al metodo
session.begin() viene iniziata la transazione.
public static void startTransaction()
session=new Session();
session.begin();
fedDB=session.getFD();
dbAutosalone=fedDB.lookupDB("objectivity");
container = dbAutosalone.getDefaultContainer();
}
Viene chiusa la transazione effettuando il commit delle modifiche apportate ai dati.
Viene chiusa la sessione.
public static void closeTransaction(){
session.commit();
session.terminate();
}
61
Viene chiusa la connessione al database e intercettata un’eccezione che nasce se si
cerca di chiudere un database già chiuso.
public static void closeConnection(){
try {
conn.close();
} catch (DatabaseClosedException e1) {
e1.printStackTrace();
}
}
Inserimento di un oggetto nel database
Gli oggetti appartenenti a classi Persistent Capable nel momento in cui sono creati
sono transitori, quindi possono essere creati al di fuori di una transizione. L’oggetto
può essere inserito semplicemente nel database con la funzione cluster(Object),
chiamata su un oggetto di tipo database o di tipo container, ad esempio
FunzioniDb.dbAutosalone.cluster(Object),
salva
l’oggetto
nel
container
predefinito del database richiamato nella classe FunzioniDb.
È possibile anche salvare l’oggetto assegnandogli un nome che lo identifica
univocamente, e con il quale è possibile recuperarlo dal database.
try{
if(passwordField1.getText().matches(passwordField2.getText())){
Cliente nuovo=new Cliente();
nuovo.setNome(this.nomeField.getText());
nuovo.setCognome(this.cognomeField.getText());
nuovo.setPassword(this.passwordField1.getText());
nuovo.markModified();
FunzioniDb.startTransaction();
String rootname= nuovo.getCognome() + nuovo.getPassword();
FunzioniDb.dbAutosalone.nameObj(nuovo,rootname);
FunzioniDb.closeTransaction();
}
}catch(NameNotUniqueException e){
…//Si richiede all’utente di modificare la password
}
62
Questo è il codice che implementa la creazione di un nuovo cliente nel database; se
la password è stata inserita correttamente viene inizializzato un nuovo oggetto di
tipo Cliente con i dati inseriti dall’utente. A questo punto viene iniziata una
transazione, viene creata la stringa contenente il nome univoco che identificherà il
cliente all’interno del database, e l’oggetto viene salvato nel container di default con
questo nome. La transazione viene infine chiusa eseguendo il commit().
La funzione nameObj(Object,String) può lanciare una eccezione che va intercettata
se si cerca di salvare un oggetto con un nome già presente nel database. In questo
caso si richiede all’utente di inserire una differente password che risulti in un
identificativo univoco.
Per eliminare un oggetto in maniera definitiva dal database viene chiamato il metodo
dolete() sull’oggetto stesso; l’oggetto verà rimosso al commit della transazione.
Recupero di un oggetto dal database
Gli oggetti sono recuperabili singolarmente dal database attraverso il nome che era
loro stato assegnato al momento dell’inserimento
String rootname = cliente.getCognome() + cliente.getPassword();
Cliente current=(Cliente)FunzioniDb.dbAutosalone.lookupObj(rootname);
current.fetch();
In questo caso attraverso le informazioni fornite dall’applicazione viene costruita la
stringa che contiene il nome assegnato all’oggetto nel db e questo viene caricato nella
memoria dell’applicazione attraverso il metodo lookupObj() richiamato sul database
che cerca in tutti i container (Se fosse stato chiamato su un container avrebbe cercato
solo all’interno di quello specifico container). Quando l’oggetto viene caricato dal
database non vengono inizializzati i valori dei suoi campi. Pertanto prima di
accedere ai campi è necessario chiamare sull’oggetto il metodo fetch() che carica i
valori dei campi presenti nel database nella rappresentazione locale dell’oggetto. Il
metodo fetch() può essere anche inserito come prima istruzione dei metodi che
accedono ai valori dell’oggetto. Ad esempio:
public class Cliente extends ooObjy{
private String nome;
63
//definizione proprietà
//costruttore
public String getNome(){
this.fetch();
return this.nome;
}
//definizione metodi
}
E’ anche possibile recuperare dei gruppi di oggetti in una sola volta, ad esempio tutti
gli oggetti appartenenti ad una certa classe e a tutti i suoi sotto tipi. Utilizzando la
funzione scan(“NomeDellaClasse”) si ottiene un iteratore con cui si possono
scandire tutti gli oggetti della classe trovati nel database o container in cui si è
effettuata la ricerca:
Iterator itr = FunzioniDb.dbAutosalone.scan("Veicolo");
int i = 0;
while (itr.hasNext()) {
Veicolo temp = (Veicolo) itr.next();
temp.fetch();
if (temp.isDisponibile() == true) {
sceltaVeicolo.insert(temp.getMarca()+" "+temp.getModello(),i);
veicoliIndice.put(new Integer(i), temp);
i++;
}
}
FunzioniDb.closeTransaction();
In questo caso vengono cercati tutti gli oggetti di tipo Veicolo, I quali vengono
esaminati per verificare se siano disponibili e in quel caso aggiunti alla lista che
l’utente visualizza. La condizione
poteva essere verificata anche passando alla
funzione scan() una stringa contenente la condizione da verificare sul campo
dell’oggetto:
itr = FunzioniDb.dbAutosalone.scan("Veicolo","disponibile==1");
64
Attraverso la funzione scan() è possibile trovare oggetti che soddisfano una certa
condizione, tuttavia il predicato che esprime la condizione è molto limitato, non
potendo contenere variabili, metodi né comandi per l’ordinamento ecc.. Pertanto
per ottenere interrogazioni complesse è necessario utilizzare costrutti propri del
linguaggio di programmazione.
Modifica di un oggetto nel database
Una volta recuperato un oggetto dal database è possibile effettuare modifiche sui
suoi campi, a patto di essere all’interno di una transazione. Una volta modificati i
campi è necessario segnalare al database che l’oggetto è stato modificato e il suo stato
nel database non è più quello corretto, altrimenti al momento del commit le
modifiche sull’oggetto potrebbero andare perse. Questo è effettuato richiamando il
metodo markModified() sull’oggetto che è stato modificato. Il metodo
markModified() può anche essere inserito nei metodi che modificano i campi
dell’oggetto.
FunzioniDb.startTransaction();
try{
Moto selected=(Moto)FunzioniDb.dbAutosalone.lookupObj(local.getTarga());
selected.fetch();
...//Vengono effettuate le modifiche sull’oggetto
selected.markModified();
FunzioniDb.dbAutosalone.unnameObj(selected);
FunzioniDb.dbAutosalone.nameObj(selected,selected.getTarga());
...
In questo caso poichè il nome dell’oggetto nel database dipende da una delle sue
proprietà, cioè la targa, che potrebbe essere stata modificata, viene prima tolto il
nome all’oggetto e poi gli viene assegnato di nuovo con il valore aggiornato.
Se le operazioni di modifica dovessero generare una eccezione è necessario catturarla
e terminare la transazione con il metodo abort() della sessione corrente, che
65
termina la transazione e ripristina lo stato dei dati senza applicare le modifiche
apportate prima dell’errore.
Object DB
La versione disponibile in prova di questo prodotto è limitata ad un solo utente
collegato al database, client e server risiedono obbligatoriamente sulla stessa
macchina. Il DBMS non necessita quindi di alcun processo server per gestire gli
accessi, e pertanto l’accesso al file del database è effettuato esclusivamente attraverso
le librerie per Java, conformi allo standard JDO, senza utilizzare alcun processo
server.
Le classi che devono essere rese persistenti devono contenere dei metodi che servono
al sistema per permettere agli oggetti di interagire con il database. I metodi necessari
vengono aggiunti da un tool detto Enhancer. Questo tool può essere usato da linea
di comando, oppure lanciato al posto del main dell’applicazione passandogli come
parametri in una funzione i nomi delle classi da modificare. Una volta modificate le
classi viene lanciata l’applicazione.
public class eMain {
public static void main(String[] args) {
com.objectdb.Enhancer.enhance("Veicolo,Auto,Moto,Cliente,Noleggio");
Application1.main();
}
}
Le informazioni riguardanti le classi persistenti devono essere fornite in un file XML
contenenta alcune proprietà delle classi come il nome delle superclassi, eventuali
campi transitori, modalità di caricamento in memoria ecc..
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "http://java.sun.com/dtd/jdo_1_0.dtd">
<jdo>
<package name="">
<class name="Veicolo" />
<class name="Cliente" />
66
<class name="Moto" persistence-capable-superclass="Veicolo" />
<class name="Auto" persistence-capable-superclass="Veicolo" />
<class name="Noleggio">
<field name="dataInizio" persistence-modifier="persistent" />
<field name="dataFine" persistence-modifier="persistent" />
</class>
</package>
</jdo>
Connessione al database
Le funzioni definite dalla classe FunzioniDb per l’apertura e la chiusura di
connessione e transazioni sono così definite:
public class FunzioniDb {
public static PersistenceManager pm;
public FunzioniDb() {
}
public static void openConnection(){
pm = Utilities.getPersistenceManager("autosalone.odb");
}
public static void startTransaction(){
pm.currentTransaction().begin();
}
public static void closeTransaction(){
pm.currentTransaction().commit();
}
public static void closeConnection(){
pm.close();
}
}
La classe Utilities messa a disposizione dal sistema, consente di ottenere un’istanza
della classe PersistenceManager, passando il percorso per il file del database. La classe
PersistenceManager mette quindi a disposizione dell’applicazione i metodi per
iniziare una transazione e terminarla e chiudere la connessione al database.
Aggiunta di un oggetto al database
Anche in questo caso è possibile aggiungere oggetti al database assegnando un nome
univoco ad ogni oggetto in modo da poterlo successivamente usare per recuperare
l’oggetto,
oppure
senza
alcun
nome
chiamando
il
metodo
FunzioniDb.pm.makePersistent(Object); Per assegnare un nome agli oggetti la
67
classe chiamata Utilities contiene diversi metodi utilizzati per l’interazione con il
database tra cui quelli per salvare gli oggetti:
Veicolo temp =
(Veicolo) FunzioniDb.pm.getObjectById(selected. getTarga(), true);
String rootname = cliente.getCognome() + cliente.getPassword();
Cliente current=
(Cliente)FunzioniDb.pm.getObjectById(rootname, true);
Noleggio nolo = new Noleggio();
nolo.setVeicolo(temp);
nolo.setCliente(current);
Utilities.bind(FunzioniDb.pm,
nolo,
temp.getTarga() + nolo.getDataInizio().getTime());
Qui un oggetto viene salvato esplicitamente con la funzione bind(); invece altri due
oggetti, temp e current, vengono salvati in quanto vengono assegnati come valore ad
un campo di un oggetto che è reso persistente. In questo caso viene intercettata una
possibile eccezione che potrebbe venire lanciata qualora il nome inserito per
l’oggetto fosse già presente nel database. In questo caso la transazione è abortita e
viene effettuato rollback per scartare tutte le modifiche al database.
Recupero di un oggetto dal database
Anche qui è possibile recuperare un oggetto fornendo il nome assegnato al
momento del salvataggio attraverso la chiamata del metodo getObjectById():
FunzioniDb.startTransaction();
Moto selected=(Moto)FunzioniDb.pm.getObjectById(local.getTarga(),true);
Il secondo parametro della funzione serve per chiedere al database se verificare se
l’oggetto sia contenuto nella cache locale dell’applicazione e i suoi valori siano
aggiornati.
Per ottenere un iteratore che contenga tutti gli oggetti di una certa classe e delle sue
sottoclassi è sufficiente passare il nome della classe:
FunzioniDb.startTransaction();
Iterator itr=FunzioniDb.pm.getExtent(Veicolo.class,true).iterator();
68
In questo caso, il secondo argomento passato alla funzione indica se le istanze delle
sottoclassi devo essere riportate come risultati o meno. True indica che anche gli
oggetti delle sottoclassi sono ritornati al chiamante.
Quando l’oggetto è caricato dal datbase, di default vengono automaticamente
caricati anche i valori dei suoi campi che sono quindi accessibili immediatamente.
Query
Il linguaggio di query supportato è JDOQL.
Modifica di oggetti nel database
Per modificare un oggetto nel database è sufficiente richiamare dalla memoria di
massa nella cache locale all’applicazione l’oggetto, modificarne i valori come
desiderato ed infine salvarlo nuovamente nel database effettuando il commit della
transazione in cui si sono effettuate le modifche, in questo modo il novo stato
dell’oggetto è reso persistente.
Versant
Anche in questo caso le classi che devono essere persistenti, necessitano di metodi
appropriati per interagire con il DBMS. Per questo è necessario prima di poter
eseguire correttamente l’applicazione, completare le classi con i metodi necessari
attraverso un tool apposito. Per mezzo di un tool grafico, bisogna assegnare ad ogni
classe la categoria di persistenza, ovvero se la classe dovrà essere salvata nel database,
deve poter interagire con oggetti persistenti ecc.. La modifica delle classi può essere
fatta o da linea di comando oppure utilizzando una classe che compila le classi
aggiungendo i metodi per l’interazione con il database e poi lancia il main
dell’applicazione.
Connessione al database
In Versant, l’accesso al database è gestito da un processo server che controlla i lock
per gli accessi concorrenti La connessione al database è effettuata tramite il server,
69
pertanto è sufficiente creare una nuova sessione (che è in relazione uno a uno con
una transazione) per poter operare con gli oggetti persistenti e il database. Ogni
volta che viene creata una sessione viene iniziata una transazione. Se si esegue il
commit o rollback della transazione ne viene automaticamente iniziata un’altra a
meno che si termini esplicitamente la sessione. La sessione è rappresentata dalla
classe TransSession contenuta nelle librerie di interfaccia per il linguaggio Java:
import com.versant.trans.*;
import com.versant.trans.TransSession;
public class FunzioniDb {
public static TransSession session ;
public FunzioniDb() {
}
…//Funzioni di connessione al database
}
La funzione in cui è creata una nuova istanza della classe TransSession prende in
ingresso solamente il nome del database con il quale si intende lavorare. In questo
caso poiché il server è locale all’applicazione è sufficiente solo il nome del database,
altrimenti sarebbe necessario specificare anche l’indirizzo del server remoto a cui ci
si deve connettere.
public static void startTransaction(){
session=new TransSession("autonoleggio");
}
public static void closeTransaction(){
session.commit();
session.endSession();
}
Inserimento di oggetti nel database
Le operazione sugli oggetti persistenti devono essere effettuate all’interno di una
transazione. Per rendere un oggetto persistente è sufficiente chiamare un metodo
associato alla sessione passando l’oggetto come parametro:
session.makePersistent(Object);
70
Per associare all’oggetto un nome che lo identifichi e che permetta di recuperarlo
semplicemente è invece necessario utilizzare la funzione makeRoot() anche questa da
richiamare sulla sessione corrente.
try{
Cliente nuovo=new Cliente();
nuovo.setNome(this.nomeField.getText());
nuovo.setCognome(this.cognomeField.getText());
nuovo.setPassword(this.passwordField1.getText());
FunzioniDb.startTransaction();
String rootname= nuovo.getCognome() + nuovo.getPassword();
FunzioniDb.session.makeRoot(rootname,nuovo);
FunzioniDb.closeTransaction();
}catch(NameNotUniqueException e){
…//Si richiede all’utente di modificare la password
}
Se il nome che si cerca di associare all’oggetto è già presente nel database viene
lanciata una eccezione. È inoltre possibile con lo stesso metodo associare più di un
nome ad un singolo oggetto.
Invece per eliminare in maniera definitiva un oggetto dal database è necessario
richiamare il metodo
deleteObject()
all’interno di una transazione passando al
metodo come parametro l’oggetto da cancellare.
TransSession session = new TransSession (database);
Person person = (Person) session.findRoot (root);
session.deleteObject (person);
session.endSession ();
L’oggetto viene cancellato dal database al commit() della transazione cioè in questo
caso quando viene chiamato il metodo session.endSession(). L’oggetto viene
cancellato solamente dal database, ma rimane nella memoria dell’applicazione
fintantoché non viene eliminato dal GarbageColector (se non è più referenziato).
Modifica di oggetti nel database
La modifica di un oggetto precedentemente salvato nel database non richiede alcuna
operazione particolare se non l’esecuzione delle modifiche desiderate. È infatti
sufficiente recuperare gli oggetti dal database, singolarmente attraverso il
“rootname” oppure un gruppo di oggetti attraverso una query, e apportare le
71
modifiche desiderate. Al momento in cui la transazione viene terminata con successo
le modifiche effettuate sugli oggetti richiamati nella memoria dell’applicazione
vengono riportate sugli oggetti persistenti corrispondenti nel database.
Recupero di oggetti dal database
Gli oggetti salvati nel database possono essere recuperati singolarmente attraverso il
rootname che era stato loro assegnato oppure attraverso le Query.
Per recuperare un oggetto attraverso il suo nome si utilizza il metodo
findRoot(nome) richiamato sulla sessione. Se ricerca un nome che non è presente nel
database il metodo lancia un’eccezione. Dal database viene ritornato un oggetto di
tipo Object, pertanto bisogna effettuare il casting del risultato al tipo desiderato.
Le query possono essere costruite attraverso dei predicate Object o attraverso il
linguaggio di query VQL (Versant Query Language).
I predicate object sono passati al metodo select() della sessione insieme al nome di
una classe di cui cercare le istanze. Se viene fornito un predicato null, vengono
trovate tutte le istanze di quella classe.
Enumeration enum =FunzioniDb.session.select(Veicolo.class,
Null,
"autonoleggio@localhost",
Constants.SELECT_DEEP,
0, 0, 0);
Qui vengono selezionate tutte le istanze della classe veicolo. Poiché Veicolo possiede
delle sottoclassi, viene utilizzato un metodo che consente di indicare delle proprietà
con cui effettuare la ricerca tra cui l’opzione che abilita la restituzione tra i risultati
di tutte le istanze delle sottoclassi di quella passata come parametro, settando il
parametro Constants.SELECT_DEEP.
Per costruire un predicato si utilizzano degli oggetti di tipo Attr.Questi oggetti
possiedono dei metodi come ad esempio eq() (uguaglianza) o and()(concatenazione)
che permettono di costruire il predicato con cui selezionare le istanze della classe.
72
AttrString attrPass=FunzioniDb.session.newAttrString("password");
AttrString attrCognome=FunzioniDb.session.newAttrString("cognome");
Enumeration enum=FunzioniDb.session.select(Cliente.class,
attrCognome.eq(cognomeField.getText()).and(
attrPass.eq(this.passwordField.getText())));
Qui vengono costruiti due oggetti AttrString che rappresentano degli attributi
dell’oggetto di tipo stringa, e nel costruttore viene passato il nome dell’attributo
dell’oggetto. Alla funzione selec viene passato il nome della classe e il predicato
costituito dalla concatenazione dei due attributi, in cui vengono usate le funzioni
eq() e and() per selezionare il cliente con quelle specifiche caratteristiche.
Per utilizzare il linguaggio VQL si costruisce un oggetto VQLQuery e nel
costruttore si passa una stringa che contiene il predicato di selezione in una sintassi
simile a SQL. Ad esempio la stringa selct selfoid from Cliente, seleziona tutti gli
oggetti della classe Cliente. La query va poi eseguita con il comando execute() e
ritorna i risultati in una Enumeration.
VQLQuery q = new VQLQuery (session,
"select selfoid from Person where married = $1");
q.bind ( new Boolean (false) );
VEnumeration e = q.execute ();
Qui si può vedere la sintassi di costruzione del predicato e il carattere speciale $1
che viene poi sostituito con il valore inserito nella funzione bind().
73
Capitolo 5
CONCLUSIONI
74
BIBLIOGRAFIA
Barello, Elio. I pianeti. Londra: 1996.
Doe, Jane. Il Sole. Londra: 1996.
75
INDICE ANALITICO
A
Aristotele,3
3
4