Setena part del curs Curs BBDD i Java centrada la programació d’aplicacions que utilitzin una base de dades orientada a objectes com db4o per a poder emmagatzemar objectes directament en un magatzem especialitzat d’objectes.

1. Introducció

Un altre element que veurem en aquest curs és el de les bases de dades orientades a objectes. Aquestes bases de dades treballen directament sobre objectes que són portats directament entre l’aplicació i la base de dades.

La diferència amb el model tradicional amb mapatge objecte-relacional (o el cas de les bases de dades objecte-relacionals), és que ara l’objecte es desa tal qual a la base de dades.

En realitat, això no és exactament així, cada producte de base de dades orientada a objectes cobrirà aquesta funcionalitat amb algun format binari propietari o treballarà sobre la serialització dels objectes a emmagatzemar.

En el nostre cas treballarem amb db4o, un producte senzill, petit i portable que demostra una bona eficiència i rendiment i que emmagatzema els objectes en un arxiu de disc que serà la base de dades.

L’avantatge d’aquest producte és que és lliure, consisteix en uns arxius JAR que podem afegir als nostres projectes, i que presenta una col·lecció de funcions reduïda i útil per a realitzar les operacions CRUD bàsiques sobre els objectes que ha de manegar, a més d’oferir-nos tres mecanismes de consulta avançats:

  • QBE (Query By Example): Consultes descrites en termes d’allò que s’espera obtenir.
  • Native Query: Consultes utilitzant comparacions en els mateixos objectes.
  • SODA: Consultes en llenguatge natiu de db4o.

Més endavant s’estudiaran aquests casos. Ara vegem els elements bàsics en la programació d’una aplicació basada en db4o.

Per a la realització d’aquests apunts s’ha treballat sobre db4o en la seva versió 8.0.

1.1. Configuració, connexió i desconnexió

En primer lloc, l’aplicació haurà de disposar de les llibreries necessàries per a la seva completa operativa. Això implica els següents arxius:

ant.jar
bloat-1.0.jar
db4o-8.0.249.16098-all-java5.jar
db4o-8.0.249.16098-bench.jar
db4o-8.0.249.16098-core-java5.jar
db4o-8.0.249.16098-cs-java5.jar
db4o-8.0.249.16098-cs.optional-java5.jar
db4o-8.0.249.16098-db4ounit-java5.jar
db4o-8.0.249.16098-instrumentation-java5.jar
db4o-8.0.249.16098-nqopt-java5.jar
db4o-8.0.249.16098-optional-java5.jar
db4o-8.0.249.16098-osgi-java5.jar
db4o-8.0.249.16098-osgi-test-java5.jar
db4o-8.0.249.16098-taj-java5.jar
db4o-8.0.249.16098-tools-java5.jar

Ara, una vegada disposem d’aquestes llibreries en el lloc adient, el nostre programa podrà utilitzar les funcionalitats de db4o per afegir persistència d’objectes.

Per connectar amb una base de dades existent (o crear-ne una de nova si no existia abans), només caldrà fer una crida com la següent:

ObjectContainer db = Db4oEmbedded.openFile(
                        Db4oEmbedded.newConfiguration(), 
                        dbFile);

on dbFile representa un String amb el nom de l’arxiu on estigui (o vulgui crear-se) la base de dades d’objectes.

Quan hem acabat de fer les accions que siguin oportunes amb la base de dades, hem de tancar la base de dades mitjançant una simple crida com la següent:

db.close();

D’aquesta manera s’alliberaran tots els recursos que db4o hagi ocupat per mantenir la base de dades activa i connectada, a més de buidar al disc les últimes modificacions pendents de ser fixades.

1.2. Execució de consultes CRUD

Quant a l’execució de consultes, les de selecció d’objectes es faran en algun dels tres formats que s’han comentat anteriorment en aquest tema: QBE, Native Query o SODA, i que seran analitzats més endavant.

Com a exemple bàsic, consideri que podria tractar-se d’una sentència tant senzilla com:

List<Employee> emps = db.queryByExample(Employee.class);

Aquest cas simplement és tota la llista de treballadors.

Quan a la resta d’operacions (CRUD), tingui present les següents crides:

Acció Crida
Crear un objecte nou a la base de dades db.store(objecte);
Actualitzar un objecte existent db.store(objecte);
Eliminar un objecte existent db.delete(objecte);

Observi que el mètode store() serveix tant per inserir un objecte nou com per actualitzar un d’existent.

Això sí, vigili quan programi amb db4o, perquè s’han de complir els següents requeriments:

  • Si l’objecte és nou i volem afegir-lo, utilitzem store().

  • Si l’objecte ja existia i l’havíem portat anteriorment amb alguna consulta, amb store() actualitzarem l’objecte de la base de dades.

  • Si l’objecte ja existia però no l’havíem inclós en cap consulta anterior, amb store() aquell objecte tornarà a inserir-se a la base de dades.

  • Si volem eliminar un objecte existent, abans l’haurem de portar amb alguna consulta i aleshores podrem executar delete().

Com veu, excepte en el cas d’inserir un nou objecte, les manipulacions d’objectes existents passen per que abans hagi estat inclòs en una consulta de selecció.

A més, db4o ens ofereix control de transaccions mitjançant les crides:

db.commit();
db.rollback();

Quan es connecta amb una base de dades s’inicia automàticament una transacció, que tancarem amb una de les dues funcions esmentades. Quan es criden, aquestes funcions inicien automàticament una nova transacció.

2. LLenguatge de consultes en db4o

Com s’ha dit, db4o ens ofereix 3 formes d’escriure consultes de selecció d’objectes.

2.1. QBE (Query By Example)

Aquesta modalitat de consultes es fonamenta en explicar-li a db4o com volem que sigui el resultat obtingut. Dit d’una altra manera, passem a la consulta un objecte amb els camps assignats de manera que donin a entendre quins valors estem condicionant.

El cas més senzill és una consulta no filtrada, que pren la següent forma:

List<Department> depts = db.queryByExample(Department.class);

Com veu, l’exemple és passar la classe del resultat que busquem, és a dir, tots els objectes Department (de la seva classe).

Si el que volem és filtrar per algun criteri, haurem de subministrar un objecte parcialment assignat que demostri la nostra intenció:

Department d = new Department();
d.setDepartmentName("Sales");
List<Department> depts = db.queryByExample(d);

En aquest cas s’ha configurat un Department que només té assignat un camp, que és el nom del departament. Això farà que, en cercar els departaments, realitzi un filtrat d’igualtat amb el valor indicat només en aquest camp.

Com veu, les consultes QBE són útils en casos molt bàsics, i no permetran filtrar rangs de valors o expressions avaluades més complexes.

2.2. Native Query

El software db4o ens ofereix un mecanisme força interessant per filtrar objectes amb aquest sistema anomenat Native Query.

La idea és senzilla: Estem treballant amb objectes, en un llenguatge de programació orientat a objectes, on la comprovació d’un filtratge “passa/no passa” pot ser tant senzilla com una condició if ben escrita.

Així doncs, no seria més pràctic deixar al programador escriure la funció de filtrat “passa/no passa” en el seu llenguatge d’alt nivell orientat a objectes? Native Query fa exactament això.

Un exemple: Suposem que volem tots els treballadors que cobrin un sou superior a 10000 o que treballin per al departament anomenat “Sales”. Amb Native Query quedarà així:

List<Employee> emps = db.query(new Predicate<Employee>() {
  public boolean match(Employee emp) {
    return ( 
      (emp.getSalary() > 10000) || 
      (emp.getDepartment().getDepartmentName().equals("Sales")) 
    );
  }
});

És a dir, la consulta es basa en un Predicate sobre la classe Employee que passarà cadascun dels Employee trobats a la base de dades per la funció interna que hi és definida. Si l’objecte concret aplicat a match() passa la condició i retorna un true, aquell objecte sortirà al resultat de la consulta. Si per a l’objecte, la funció match() hagués retornat un false, aleshores serà eliminat del resultat.

Aquest mecanisme ens permet, amb molta facilitat, utilitzar tota la infraestructura de funcions i classes del llenguatge de programació i podrem realitzar qualsevol tasca que ens puguem imaginar o plantejar.

2.3. SODA

SODA és el mecanisme intern de db4o per executar consultes sobre les seves bases de dades. És, per tant, el llenguatge més eficient i que permet afinar més la consulta que s’executarà sobre la base de dades.

En el cas SODA, les consultes s’inicien afegint una restricció que indica quina és la classe buscada en la base de dades, i després s’afegeixen filtres addicionals que condicionen els objectes obtinguts com a resultat.

Observi el següent exemple:

Query query=db.query();
query.constrain(Employee.class);
query.descend("lastname").constrain("King");
List<Employee> result = query.execute();

Veurà que s’ha utilitzat el mètode descend() per baixar a aquell atribut de l’objecte i aplicar un criteri restrictiu. En aquest cas, obtindrem els treballadors amb cognom “King”.

Podem aplicar restriccions d’aquest tipus sobre qualsevol tipus de dada:

Query query=db.query();
query.constrain(Department.class);
query.descend("locationId").constrain(1700);
List<Department> result = query.execute();

I aplicar modificadors que alteren el sentit de la restricció:

Query query=db.query();
query.constrain(Department.class);
query.descend("locationId").constrain(1700).not();
List<Department> result = query.execute();

A més, podrem combinar diversos descend() i construir consultes de selecció realment complexes:

Query query=db.query();
query.constrain(Employee.class);
query.descend("lastname").constrain("King")
    .or(query.descend("salary").constrain(13000).greater());
query.descend("salary").orderDescending();
List<Employee> result = query.execute();

Observi com s’ha afegit ordenació del resultat de la consulta en aquest últim exemple.