Database ad oggetti: il caso DB4O
Transcript
Database ad oggetti: il caso DB4O
JUG – Ancona Italyy Database ad oggetti: il caso DB4O Ivan Di Pietro IT engineer - PhD JUG Marche - www.jugancona.it Indice ● ● ● Il modello three-tier – Approccio basato su driver – Approccio ORM – Approccio OO DB4O Tools per DB4O Il modello three-tier DBMS Applicazione client Webapp / Business Logic Approccio basato su driver ● ● ● Driver JDBC per inviare istruzioni SQL al DBMS Scrittura di codice SQL ad hoc Gestione personalizzata delle transazioni Approccio ORM ● ● ● ● Componente configurabile Dipendenza da un driver JDBC Strumenti automatici Gestione ottimizzata di query e transazioni Modello dei dati ● ● In questi due primi approcci, il modello dei dati è espresso in termini di tabelle e relazioni Il modello dei dati è portato nell'applicazione tramite una traduzione dello schema del DB in classi Approccio OO ● ● Il modello dei dati in un'applicazione Object Oriented è già rappresentato tramite il diagramma delle classi Un DB a oggetti contiene istanze di classi (beans) ed esegue su di esse le operazioni CRUD: – CReate – Update – Delete Approccio OO schumacher:Pilot raikkonen:Pilot massa:Pilot Caso di studio: DB4O ● ● ● ● ● ● Engine scritto in Java Nessuna installazione di software aggiuntivo Licenza GPL/commerciale Prestazioni eccellenti Accesso ai dati in locale su filesystem Accesso ai dati in remoto via socket DB4O: aprire un DB ● Esempio di apertura di DB su filesystem ObjectContainer db=Db4o.openFile(Util.DB4OFILENAME); try { // do something with db4o } finally { db.close(); } DB4O: creare oggetti ● ● Classe Pilot con: – Nome (String) – Punti (int) Inserimento di un nuovo oggetto Pilot Pilot pilot1=new Pilot("Michael Schumacher", 100); db.set(pilot1); DB4O: cercare oggetti Tre metodi: ● ● ● Query By Example (QBE): ricerca tramite oggetto prototipo Native Query (NQ): ricerca tramite metodo di mapping personalizzato Query API (SODA): ricerca a “basso livello” tramite API DB4O: cercare oggetti (QBE) ● ● Oggetto prototipo con campi “vuoti” (impostati sul default del tipo) Esempio: ricerca di tutti i piloti nel DB // retrieveAllPilotQBE Pilot proto=new Pilot(null,0); ObjectSet result=db.get(proto); ● Esempio: ricerca per nome Pilot proto=new Pilot("Michael Schumacher",0); ObjectSet result=db.get(proto); DB4O: modifica/cancellazione ● Prevedono sempre una ricerca preliminare // updatePilot QBE ObjectSet result=db.get(new Pilot("Michael Schumacher",0)); Pilot found=(Pilot)result.next(); found.addPoints(11); db.set(found); // deleteFirstPilotByName QBE ObjectSet result=db.get(new Pilot("Michael Schumacher",0)); Pilot found=(Pilot)result.next(); db.delete(found); QBE: svantaggi QBE è un metodo di ricerca estremamente semplice, ma presenta diversi svantaggi: ● Inefficienza: reflection su tutti i membri del prototipo ● Complessità limitata: non si effettuano query complesse (AND, OR, ecc) ● Default: non è possibile cercare oggetti con campi impostati sul default (ex piloti con 0 punti) ● Costruttori: tutti i campi di un oggetto devono essere inizializzati dal costruttore DB4O: Native Query Le query vengono effettuate per mezzo di un oggetto Predicate. ● Bisogna fornire un'implementazione del metodo match Esempio: ricerca di piloti con 100 punti ● List <Pilot> pilots = db.query(new Predicate<Pilot>() { public boolean match(Pilot pilot) { return pilot.getPoints() == 100; } }); Nei prossimi esempi faremo preferibilmente riferimento a questo tipo di query DB4O: Query SODA Complessità maggiore ● Potenza espressiva paragonabile a NQ, ma possibilità di creare query dinamicamente Esempio: ricerca piloti per nome ● // retrievePilotByName Query query=db.query(); Restrizione su classe Restrizione su attributo query.constrain(Pilot.class); query.descend("name").constrain("Michael Schumacher"); ObjectSet result=query.execute(); DB4O: Oggetti strutturati ● Scenario: introduciamo la classe Car DB4O: Oggetti strutturati ● Creazione di un oggetto strutturato Car car1=new Car("Ferrari"); Pilot pilot1=new Pilot("Michael Schumacher",100); car1.setPilot(pilot1); db.set(car1); ● Ricerca di un oggetto strutturato (NQ) final String pilotName = "Michael Shumacher"; ObjectSet results = db.query(new Predicate() { public boolean match(Car car){ return car.getPilot().getName().equals(pilotName); }}); DB4O: Update Depth ● Aggiorniamo una macchina impostando anche nuovi valori per il pilota associato // updatePilotSeparateSessionsPart1 ObjectSet result=db.query(new Predicate() { public boolean match(Car car){ return car.getModel().equals("Ferrari"); } }); Aggiornamento del pilota associato Car found=(Car)result.next(); found.getPilot().addPoints(1); db.set(found); Le modifiche non vengono propagate DB4O: Update Depth ● ● La propagazione degli update è onerosa per oggetti complessi Si imposta tramite una apposita configurazione prima di aprire il DB Db4o.configure().objectClass("com.db4o.f1.chapter2.Car") .cascadeOnUpdate(true); Equivale a ON UPDATE=CASCADE nei RDBMS DB4O: cancellazione ricorsiva ● ● Cancellando un oggetto Car il pilota associato NON viene cancellato Per ottenere una cancellazione ricorsiva degli oggetti associati si usa una impostazione apposita Db4o.configure().objectClass("com.db4o.f1.chapter2.Car") .cascadeOnDelete(true); Equivale a ON DELETE=CASCADE nei RDBMS DB4O: cancellazione ricorsiva ● ● Problema: cosa succede se un oggetto è associato a più oggetti e viene cancellato? Immaginiamo la seguente situazione (anche se impossibile nel mondo reale!!) X X X Cancellando la BMW, cancelliamo anche Schumacher e la Ferrari resterebbe con un pilota nullo Controllo affidato al programmatore DB4O: collections ● ● Per oggetti che includono collections non ci sono osservazioni particolari Essendo oggetti strutturati, valgono le considerazioni fatte per le due impostazioni sulla propagazione delle operazioni Db4o.configure().objectClass("com.db4o.f1.chapter2.Car") .cascadeOnUpdate(true); Db4o.configure().objectClass("com.db4o.f1.chapter2.Car") .cascadeOnDelete(true); DB4O: collections ● ● Introduciamo un oggetto SensorReadout che ha un array di rilevamenti Ogni auto ha una lista di SensorReadout DB4O: collections ● ● Il salvataggio e la ricerca di oggetti collections non ha differenze rispetto ai casi precedenti Un esempio di ricerca tramite NQ è il seguente (recupera i rilevamenti che contengono 0.3 0.1): ObjectSet results = db.query(new Predicate() { public boolean match(SensorReadout candidate){ return Arrays.binarySearch(candidate.getValues(), 0.3) >= 0 && Arrays.binarySearch(candidate.getValues(), 1.0) >= 0; } }); DB4O: collections ● Un accorgimento per le QBE – È possibile recuperare tutte le history delle auto (oggetti di tipo ArrayList) ObjectSet result=db.get(new ArrayList()); – Lo stesso non vale per gli oggetti di tipo double[] in SensorReadout. In questo caso si ricorre alle NQ ObjectSet result=db.get(new double[]{0.6,0.4}); DB4O: ereditarietà ● I meccanismi di ricerca sono consapevoli delle relazioni di ereditarietà delle classi Cercando con un prototipo (QBE) di tipo Sensore vengono ritornati anche oggetti delle sue sottoclassi !! In QBE non posso usare come prototipo un'interfaccia o una classe astratta DB4O: Activation Depth ● ● ● Immaginiamo uno scenario reale in cui esiste una lunga catena di dipendenze tra oggetti pilota:Pilot auto:Car m:Measure u:Unit c:Component s:Sensor Una ricerca sul primo oggetto caricherebbe in memoria anche gli oggetti collegati L'activation depth permette di indicare il numero di oggetti collegati da memorizzare (il default è 5) DB4O: Activation Depth (2) ● È possibile attivare gli oggetti nella portata dell' activation depth tramite una apposita configurazione statica: Db4o.configure().objectClass(TemperatureSensorReadout.class) .cascadeOnActivate(true); ● Altrimenti si possono attivare gli oggetti collegati dinamicamente SensorReadout readout=car.getHistory(); while(readout!=null) { db.activate(readout,1); System.out.println(readout); readout=readout.getNext(); } DB4O: transazioni ● DB4O dispone di meccanismi di accesso concorrente ai dati – db.commit() – db.rollback() – db.ext().refresh(java.lang.Object obj, int depth) L'ultimo metodo, meno familiare, serve ad allineare lo stato degli oggetti presenti nell'applicazione con quelli memorizzati nel DB DB4O: Transparent Activation ● Il problema dell'attivazione in profondità degli oggetti può essere gestito automaticamente Db4o.configure().add(new TransparentActivationSupport()); ● Le classi degli oggetti immessi nel db devono: – Implementare l'interfaccia Activatable – Chiamare il metodo activate nei metodi getters public class Car implements Activatable { private Pilot pilot; Attivazione automatica della public Pilot getPilot() { profondità necessaria activate(); return pilot; } } DB4O: Enhancement ● ● Il codice aggiuntivo per ottenere la Transparent Activation può essere generato automaticamente tramite l'inclusione di librerie opzionali di DB4O (enhancement) ENHANCEMENT – Compile time: script ANT o applicazione enhancer (--> possibili problemi di debugging) – Load time: si usa un class loader alternativo (Db4oEnhancedLauncher) – Ottimizzazione NQ a querying time: basta includere le opportune librerie DB4O: client/server ● ● Per garantire un accesso concorrente al DB, è necessario avviare l'engine come server (e non appoggiarsi ad un semplice file) Esistono due possibilità: – Esecuzione del server in locale (client e server usano la stessa JVM) – Esecuzione remota (networking) DB4O: client/server (locale) ● Avvio del server in locale (porta 0) // accessLocalServer ObjectServer server=Db4o.openServer(Util.DB4OFILENAME,0); try { ObjectContainer client=server.openClient(); // Do something with this client, or open more clients client.close(); } finally { server.close(); Client per l'esecuzione } delle query DB4O: client/server (remoto) ● ● Si mette il server in ascolto su una porta Il client accede tramite credenziali // accessRemoteServer ObjectServer server=Db4o.openServer(Util.DB4OFILENAME,PORT); server.grantAccess(USER,PASSWORD); try { ObjectContainer client=Db4o.openClient("localhost",PORT,USER,PASSWORD); // Do something with this client, or open more clients client.close(); } finally { server.close(); } DB4O: client/server caching DB4O: client/server inner classes ● ● ATTENZIONE: quando si usa una NQ con una classe Predicate interna anonima, DB4O tenta di serializzare anche la classe esterna -> possibili fallimenti (ObjectNotStorableException) Soluzioni: – Usare sottoclassi di Predicate di primo livello – Dichiarare static le classi Predicate interne – Assicurarsi che la classe esterna sia serializzabile DB4O: segnalazioni fuori banda ● Al server possono essere inviati messaggi di servizio fuori banda (spegnimento, defrag, ecc) db4oServer.ext().configure().clientServer() .setMessageRecipient(this); ● I messaggi fuori banda saranno gestiti dal metodo processMessage dell'oggetto db4oServer DB4O: segnalazioni fuori banda ● I client inviano al server delle istanze di messaggi ad una sottoclasse di MessageRecipient MessageSender messageSender = objectContainer.ext().configure() .clientServer().getMessageSender(); // send an instance of a StopServer object messageSender.send(new StopServer()); Gestito dal metodo processMessage del MessageRecipient Tools: ObjectManager ● Apertura di un DB Tools: ObjectManager ● ● Interfaccia di gestione Possibilità di usare query SQL (progetto SQL4Objects) Tools: NetBeans Plugin ● Consente di eseguire query SQL, Native o SODA JUG – Ancona Italyy Grazie ! Riferimenti: www.db4o.com Ivan Di Pietro Domande...?