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