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...?