3.3 - Consultes
DB4O disposa de tres formes de realitzar consultes. Totes elles són de tipus NoSQL.
Nota
Per a poder tenir un poc més de joc, torneu a inserir les 2 empleades de la modificació de la classe Prova1, i així en tindrem un total de 3. Si havíeu fet tots els exemples anteriors, potser siga millor esborrar Empleats.db4o i tornar a executar Prova1 i Prova1_1 per a crear-les de nou.
Mètode Query By Example
La primera forma ja s’ha comentat, és la que s’anomena consulta basada en un exemple o “query by example”. Consisteix, com ja hem vist, en trobar totes les instàncies guardades que coincidesquen amb els valors no nuls i diferents de zero (en cas que siguen numèrics) d’un patró o exemple passat per paràmetre.
Si, per exemple, volem traure els empleats del departament 10 que són de Castelló, n’hi hauria prou amb crear el patró següent:
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import classesEmpleat.Adreca;
import classesEmpleat.Empleat;
public class Prova11 {
public static void main(String[] args) {
ObjectContainer bd = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), "Empleats.db4o");
Empleat f = new Empleat();
f.setDepartament(10);
f.setAdreca(new Adreca(null, null, "Castelló"));
ObjectSet<Empleat> llista = bd.queryByExample(f);
for (Empleat e : llista) {
System.out.println("Nif: " + e.getNif() + ". Nom: " + e.getNom() + ". Departament: " + e.getDepartament()
+ ". Població: " + e.getAdreca().getPoblacio());
}
bd.close();
}
}
cosa que donarà com a resultat el següent, que es pot comprovar que són del departament 10 i de Castelló:
Nif: 11111111a. Nom: Albert. Departament: 10. Població: Castelló
Nif: 22222222b. Nom: Berta. Departament: 10. Població: Castelló
Seguint aquest raonament, per obtenir tots els empleats de l’aplicació caldrà passar un patró empleat sense valors ( bd.queryByExample(new Empleat() ) ), i si el que desitgem és obtenir tots els objectes emmagatzemats a la base de dades, el que haurem de passar com a paràmetre és un valor null ( bd.queryByExample(null) ).
Com podeu veure, resulta un sistema molt simple. Ara bé, també té moltes limitacions en consultes més complexes, i fins i tot poden resultar impossibles. Posem alguns exemples en els quals no funciona aquest tipus de consulta:
- És impossible trobar tots els empleats que no tinguen algun camp assignat encara (és a dir, null) a causa del mecanisme utilitzat: només s’avaluen els camps no nulls.
- Tampoc podríem trobar aquells empleats que cobren més de 1300€ . En aquest tipus de consulta només podem buscar igualtats.
- Com es basa en la coincidència, no podrem fer consultes que puguen agafar un de dos o més valors determinats. Per exemple, agafar els empleats que són de Castelló o Borriana.
Mètode Native Queries
DB4O disposa d’un sistema molt més potent anomenat Native Queries. És fàcil deduir que es tracta d’un sistema vinculat directament al mateix llenguatge de programació. De fet, es tracta de construir un procediment en el qual s'avaluen els objectes i es decideix quins objectes acompleixen la condició i quins no.
Per a fer la consulta haurem de crear una classe que implemente una interfície anomenada Predicate . Aquesta interfície consta d'un únic mètode declarat anomenat match. La classe nostra que implementarà Predicate haurà de sobreescriure el mètode match(), i en aquest mètode podrem posar una sèrie de sentències Java i dir si cada objecte de la Base de Dades acompleix o no la condició tornant respectivament true o false.
En el següent exemple creem una classe anomenada EmpleatsPerPoblacio (que implementa Predicate), a la qual se li pot passar en el constructor un vector de cadenes de caràcters amb els noms de les poblacions de les quals volem els empleats. En la implementació del mètode match tornarem cert si l'empleat és d'alguna de les poblacions, i fals en cas contrari. Com que utilitzem el mateix llenguatge de programació, la potència és molt elevada i la corba d’aprenentatge d’aquesta tècnica esdevé pràcticament nul·la.
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;
import classesEmpleat.Empleat;
public class Prova12 {
public static void main(String[] args) {
ObjectContainer bd = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), "Empleats.db4o");
String[] pobl = { "Castelló", "Borriana" };
ObjectSet<Empleat> llista = bd.query(new EmpleatsPerPoblacio(pobl));
for (Empleat e : llista) {
System.out.println(e.getNom() + " (" + e.getAdreca().getPoblacio() + ")");
}
bd.close();
}
public static class EmpleatsPerPoblacio extends Predicate<Empleat> {
String[] poblacions;
public EmpleatsPerPoblacio(String[] poblacions) {
this.poblacions = poblacions;
}
@Override
public boolean match(Empleat emp) {
boolean ret = false;
for (int i = 0; !ret && i < poblacions.length; i++) {
if (emp.getAdreca().getPoblacio().equalsIgnoreCase(poblacions[i]))
ret = true;
}
return ret;
}
}
}
Observeu que una vegada definida la classe, podem fer-la servir en una Query per realitzar una consulta específica. En l'exemple, s'obtenen tots els empleats que són de Castelló o de Borriana. En variar la llista de poblacions obtindrem uns objectes empleat o uns altres. En el mètode match, que és qui diu si un element Empleat compleix la condició, es comprova si la població de l'empleat (que està dins d'adreça, i per tant s'accedeix amb emp.getAdreca().getPoblacio() ) és igual a alguna de les de l'array de poblacions. Per a fer-lo més general, es fa amb el mètode equalsIgnoreCase(), que no distingeix entre majúscules i minúscules. En quant es troba una població coincident, s'eixirà del bucle, ja que s'ha posat també com a condició del bucle !ret (que no s'ha trobat), a banda del comptador del bucle.
Com que es tracta d’una interfície amb un únic mètode a implementar, no caldrà que implementem sempre noves classes per a cada consulta diferent, sinó que podem fer servir classes anidades anònimes (anonimous nested class), per a fer-lo molt més curt. Recordeu que les classes anidades poden treballar directament amb tots els atributs (tinguen l’àmbit que tinguen) de la classe que les continga i que les classes anònimes es defineixen a l’interior d’un mètode qualsevol.
Mirem-ho en un altre exemple, en el qual es buscaran els empleats que tinguen el sou entre dos valors determinats. Construïm la classe Predicate en el mateix lloc on s'utilitza, en el query(), i no abulta molt perquè només té el mètode match(). En el mètode match() és on es comprova la condició:
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Predicate;
import classesEmpleat.Empleat;
public class Prova13 {
public static void main(String[] args) {
ObjectContainer bd = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), "Empleats.db4o");
final int max = 1500;
final int min = 1000;
ObjectSet<Empleat> llista = bd.query(new Predicate<Empleat>() {
@Override
public boolean match(Empleat emp) {
if (emp.getSou() <= max && emp.getSou() >= min)
return true;
else
return false;
}
});
for (Empleat e : llista) {
System.out.println(e.getNom() + " (" + e.getSou() + ")");
}
bd.close();
}
}
Mètode SODA
Existeix encara una altra forma de definir consultes. DB4O l’anomena SODA (Simple Object Database Access), i es pot considerar com la forma d’accedir a l’estructura interna de la base de dades a baix nivell per tal de seleccionar els nodes de dades que complesquen uns determinats requisits i que acabaran determinant el resultat de la consulta. De fet, segons indiquen els autors, és la forma de consulta més ràpida de les tres.
La idea fonamental de SODA és construir les consultes com un recorregut d’una xarxa de nodes enllaçats. Els nodes de la consulta s’estructuren de forma semblant a les classes emmagatzemades a la base de dades, de manera que el camí seguit en avaluar la consulta, node a node, es repeteix en les instàncies emmagatzemades, la qual cosa permet accedir als valors per avaluar de forma ràpida.
El camí s’especifica utilitzant el mètode descend() per mitjà del qual seleccionem la branca de l’estructura de classes que vulguem fer referència. Per exemple, si ens trobem en el node de la classe Empleat i volguérem fer referència al nom de la població que en l’estructura de classes es troba a empleat.getAdreca().getPoblacio(), hauríem de fer
node.descend("adreca").descend("poblacio")
El resultat de la sentència anterior és un node focalitzat a l’atribut població continguda a l’adreça de l'empleat.
Cada node pot estar afectat per una restricció, per una ordenació i/o per una operació amb una altre node. Les restriccions permeten seleccionar o desestimar les instàncies que es vagen comprovant. Les ordenacions, com és natural, forcen l’ordre de les instàncies seleccionades d’acord amb els valors de l’atribut representat pel node afectat. Finalment, les operacions marquen quin serà el següent node a avaluar, el qual actuarà també com a filtre dels objectes de la selecció.
Les restriccions es veuran afectades per una o més relacions que permetran modificar la comparació i sentenciar en favor o en contra de la selecció d’una instància. Per defecte, la relació avaluada és la d’igualtat. Per exemple, si partim d’un node que representa el NIF d’un empleat, podem definir la relació d’igualtat següent:
node.constrain("11111111a")
Mirem com quedaria el programa que selecciona únicament l'empleat amb el nif anterior:
Però si la relació ha de ser una comparació de tipus major que , menor o igual que, ... , caldrà especificar-les expressament. La manera serà especificant un mètode de la restricció. Les possibilitats seran:
- Major: greater()
Si suposem que partim d’un node focalitzat al sou d’un empleat i volem la condició que el sou siga major estrictament que 1300. S’indicaria d'aquesta manera:node.constrain(1300).greater();
- Menor: smaller()
Si volem que el sou siga estrictament menor que 1500:node.constrain(1500).smaller();
- Major o igual, menor o igual: equal() (després del greater o smaller)
Si ara volem que el sou siga menor o igual que 1500:node.constrain(1300).smaller().equal();
- Que comence per: startsWith(boolean)
Si partim d'un node focalitzat al nom de l'empleat i volem els que comencen per A:node.constrain("A").startsWith(true);
Si en el paràmetre booleà posem true, haurà de coincidir exactament el principi. Si posem false, no distingirà entre majúscules i minúscules.
- Per a unir restriccions: or(restriccio) and(restriccio). Per a negar not()
Per exemple, si partim d’un node focalitzat al nom de l'empleat, podem seleccionar tots els que comencen per A o per B, fent:Constraint constr1 = node.constrain("A").startsWith();
Constraint constr2 = node.constrain("B").startsWith();
constr1.or(constr2); -
Si posem més d'una restricció (més d'un constrain), s'hauran de complir totes, i per tant actua com un and
A banda de les restriccions, si volem ordenar de forma ascendent o descendent, ho indicarem amb els mètode orderAscending() o orderDescending() del node pel mig del qual volem ordenar .
Mirem un parell d'exemples per veure com es posa tot en joc. Anem a construir la sentència que permeta seleccionar tots els empleats amb un sou que oscil·le entre un rang de valors definits (estrictament major que 1000, i menor o igual que 1500, per exemple) ordenats de forma descendent per sou:
I ara els empleats del departament 10 que són de Castelló. Podem utilitzar el mateix objecte node per anar afegint restriccions, però haurem de cuidar de localitzar-lo al lloc oportú:
Tot i que cal reconèixer la potència del sistema, de moment encara no és capaç de tenir tota l’expressivitat d’un llenguatge com OQL. No disposa de funcions d’agregació (SUM, AVG, MAX, MIN, ...), ni es poden expressar relacions entre instàncies emmagatzemades. De moment és l’aplicació que haurà de fer-se responsable que això siga possible. Es tracta, però, d’una tècnica molt jove, que de ben segur anirà evolucionant. Caldrà estar atents per veure fins on arriba.
Llicenciat sota la Llicència Creative Commons Reconeixement NoComercial CompartirIgual 2.5