Db4o - static1.1.sqspcdn.com
Transcript of Db4o - static1.1.sqspcdn.com
Db4o
Muchas veces cuando se presenta un artículo tan técnico, se lo orienta hacia un público especializado o avanzado, y el enfoque es mostrar los últimos detalles del producto de turno, sin embargo si hicieramos esto aquí dejaríamos afuera a los lectores que descubren db4o. Tal vez no entenderían cuándo será viable su uso o bien qué es realmente lo grandioso de db4o. En cambio si el enfoque fuera “arranquemos desde cero”, los desarrolladores avanzados, o incluso intermedios, se aburrirían y dejarían de lado el artículo buscando las últimas novedades. Por ello les recomiendamos a quienes estén avanzados, que en el caso de no responder a sus dudas, consulten hacia el final de este artículo en donde se encuentran las direcciones URL de recursos, entre ellos los de la gran comunidad de usuarios de db4o, donde pueden realizar consultas específicas.
Tanto si usted ha tenido alguna experiencia con bases de objetos (ODBMS), los conocidos como “mapeadores” objetosrelacionales (ORDBMS), es de la vieja guardia de las bases relacionales (RDBMS), como si jamás ha tenido experiencia persistiendo información y quiere averiguar si db4o es para usted, creemos que este artículo puede serle de ayuda.
Qué no encontrará aquí
La elección de un esquema de persistencia, aún cuando debería ser técnicamente una de las cuestiones menos polémicas debido a la enorme cantidad de documentación y comparativas realizadas y disponibles públicamente, ha sido desde hace unos años objeto de múltiples controversias. Sin embargo, el objetivo de este artículo no está centrado en los aspectos netamente comparativos, ni en convencer al lector de abandonar su preferencia por las bases relacionales o herramientas de mapeo, tareas que de por sí requerirían de un artículo aparte y cierta disposición especial al cambio. La idea aquí es presentar un vistazo a las características fundamentales de db4o, dejando a los lectores la posibilidad de generar nuevas entregas, ahondando en cuestiones que consideren de su interés.
Introducción
Vamos a ver entonces algunas características claves de db4o, la base de objetos nativa de alto rendimiento para .Net y Java. A continuación mostraremos cómo instalar y comenzar a utilizar este motor de persistencia. Y seguidamente incluiremos cómo resolver algunas tareas simples en C# (muy fácilmente adaptables a Java), una pequeña conclusión y daremos fuentes para más información.
A grandes rasgos, las características fundamentales de db4o son: Alto rendimiento: Ofreciendo notables ventajas con sistemas que utilizan
objetos anidados o compuestos, o en donde existen referencias cruzadas, herencia o interacciones ricas entre los objetos.
Por su bajo consumo de recursos, (de 600Kb a 800Kb de footprint) es especialmente apta para dispositivos móviles y entornos Clientes/Servidor, aunque no necesariamente limitada sólo a ellos.
Doble licencia: GPL (Open Source) y Comercial (que incluye soporte). Gran comunidad de usuarios. Alto nivel de respuesta y participación. Documentación: Clara, amplia y ordenada. Orientada a ejemplos y de fácil
lectura. Dos modos de trabajo: Embebido y Cliente/Servidor. Transparencia Persistente / Consultas Nativas. Soporte de versionado. Portabilidad entre .Net, Mono y Java. Transacciones ACID: Atomicidad, Consistencia, Aislamiento, Durabilidad. Clientes de peso avalan su uso: BMW, Intel, Boeing, Ricoh, Seagate, Bosch,
Novell, etc.
Instalación
Para empezar a utilizar db4o, es necesario descargar la versión apropiada para su equipo. Actualmente existen versiones para .NET 1.x y 2.0, para Java y para Mono.
.NET
Lo primero que necesita saber es qué versión de .NET está instalada en su computadora. Una comprobación rápida es observar si existe un directorio parecido a "c:\WINDOWS\Microsoft.NET\Framework\", allí dentro debería tener un subdirectorio con el número de versión (ver tabla abajo) por cada versión instalada. La versión más profesional de esta verificación es ejecutar el siguiente script .VBS:
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")Set colItems = objWMIService.ExecQuery("Select * from Win32_Product")
For Each objItem in colItems If InStr(objItem.Name, "Microsoft .NET Framework") > 0 Then Wscript.Echo objItem.Version End IfNext
* Debería aparecer un cuadro de diálogo por cada versión instalada.
Si no ha actualizado el framework .NET separadamente, la siguiente tabla puede darle una idea de la versión instalada en su equipo:
Visual Studio .NET Nombre de Versión Número de versión2002 1.0 Beta 1 1.0.????.02002 1.0 Beta 2 1.0.2914.0
2002 1.0 RTM 1.0.3705.02002 1.0 SP1 1.0.3705.2092002 1.0 SP2 1.0.3705.2882002 1.0 SP3 1.0.3705.60182003 1.1 RTM 1.1.4322.5732003 1.1 SP1 1.1.4322.20322003* 1.1 SP1 1.1.4322.23002005 2.0 Beta 1 2.0.406072005 2.0 RTM 2.0.50727.42 3.0 RTM 3.0.4506.30
* = (Windows Server 2003 Version)
Si tiene varias versiones de .NET instaladas, es posible especificar con qué versión desea utilizar db4o editando un archivo de configuración de la aplicación [1] (que no es más que un XML con extensión .config), sin embargo esto no es necesario ya que por omisión db4o tomará la última versión de .Net instalada.
La distribución de Db4o para .NET consiste en un archivo instalador MSI, por lo que deberá tener habilitado el servicio "Windows Installer" para poder instalarlo. Ingrese en la "Consola de administración de Microsoft" escribiendo services.msc en Inicio > Ejecutar para poder habilitarlo si no está ya habilitado.
Java
Para empezar se debe tener instalada la máquina virtual Java de Sun (JRE). Por el uso de los generics y mejoras en la ejecución del for, se recomienda utilizar Java 5.0 en lo posible. Db4o también corre sobre la máquina virtual Java de Microsoft, para la cual se debe utilizar JDK 1.1 a partir de la versión 6.1 de Db4o.Db4o puede ejecutarse sobre las plataformas Java J2EE, J2SE y dialectos J2ME que soporten reflexión computacional como CDC, PersonalProfile, Symbian, Savaje y Zaurus. Está planeado el soporte sobre dialectos sin reflexión como CLDC, Palm OS y RIM/Blackberry. La distribución para Java viene en formato ZIP que debemos extraer en donde creamos conveniente.
Lenguajes
La siguiente tabla presenta los lenguajes y entornos soportados por Db4o:
Lenguaje/Entorno SoporteC# SíJava (JRE) SíJava (J#) SíVisualBasic.Net SíDelphi.NET SíC++ Administrado (C++/CLI)Ruby (JRuby) Vía reflector
Python (IronPython) SíPython (Jython) Vía reflectorBoo SíASP.NET SíCompact Framework Sí
Configuración
Suficiente hasta aquí con cuestiones de instalación. Db4o posee dos modos de trabajo: Embebido y Cliente/Servidor. A los propósitos de este artículo solo veremos el primer modo, dejando a futuras revisiones el modo cliente/servidor. Para poder distribuir aplicaciones con db4o, sólo necesita copiar una librería (.dll) muy pequeña y configurar algunas cosas en su entorno de desarrollo preferido (además de escribir el código necesario desde luego!).
VisualStudio .NET 2005
Estos son los pasos para comenzar a desarrollar con db4o 6.0 bajo este entorno:
1. Agregar la referencia de la dll en nuestro proyecto haciendo click con el botón derecho sobre References en el Solution Explorer.
2. Elegir Add Reference y luego Browse3. Seleccionar db4o.dll, Open y finalmente Ok
Y listo. No es necesaria la administración de la base de objetos. Los pasos de configuración son similares para el entorno SharpDevelop
Eclipse
Sólo hay que agregar las referencias al .jar correspondiente al entorno de trabajo.
1. Abrir las propiedades del proyecto haciendo click con el botón derecho sobre el proyecto en el Package Explorer y seleccionando Properties.
2. Seleccionar Java Build Path y hacer click sobre la pestaña Libraries.3. Agregar el JAR correspondiente haciendo click en el botón Add External JARs y
navegando hasta encontrar el JAR para la versión de Java que esté utilizando.
Ejemplo: Tienda de renta de DVDs
Vamos a trabajar con un ejemplo típico. Supongamos un cliente que hace un alquiler en nuestra tienda de renta de DVDs. A modo de ejemplo instanciemos una película y su respectiva copia física del DVD en nuestro sistema, y posteriormente un nuevo alquiler del cliente, asociando la información correspondiente. Gráficamente nuestras instancias quedarían así:
Por ahora, supongamos también que para el siguiente código de ejemplo tenemos un objeto “db” sobre el cual se pueden realizar dos operaciones, Set() y Commit(), que persisten los datos. El código que generaría nuestras instancias sería algo como lo siguiente:
Java
(El proyecto que prueba este Script se llama ConsoleApplicatioDb4oTestSimpleScriptJAVA)
C#
(El proyecto que prueba este Script se llama ConsoleApplicatioDb4oTestSimpleScriptCS)
Si nunca trabajó con bases de objetos es importante que tenga en cuenta lo siguiente: En Db4o, las referencias a los objetos especifican la relación entre ellos, por lo tanto en nuestro ejemplo, la película (unaPelicula) y la copia física (copiaDVD) son almacenados implícitamente al hacer db.Set(alquiler). El db.Commit() finalmente cierra la transacción. Es fundamental que recuerde esta especie de principio que determina el funcionamiento de las bases de objetos.
Hasta aquí podemos sacar varias conclusiones. Como observamos, con db4o no es necesario crear un esquema de persistencia, es decir, no hay que hacer el diseño de la base de datos ya que es el modelo de clases de nuestro sistema lo que se persiste. Esto nos libera de varias cosas, entre ellas, el uso de herramientas de administración de base de datos, un administrador de base de datos, y de poseer conocimientos bases de datos relacionales (álgebra relacional, SQL), entre otras tareas.
Para los siguientes ejemplos, puede ser útil ir inspeccionando la base mientras se trabaja con ella. Db4o posee un explorador con el cual podemos navegar nuestras bases y realizar copias de seguridad, defragmentaciones, consultas, etc. Este se llama “Object Manager”, y se puede descargar desde el Centro de Descargas de Db4o (ver sección de
// Creamos una nueva películaPelicula unaPelicula = new Pelicula("The Godfather", “Francis Ford Coppola”);
// Creamos un nueva copia física para la película// y asociamos la película a la copia físicaDvd copiaDVD = new Dvd(5, unaPelicula);
// Alquiler por 2 díasAlquiler alquiler = new Alquiler ("Juan Perez", copiaDVD, 2);
// Finalmente almacenamos el alquilerdb.set(alquiler);db.commit();
// Creamos una nueva películaPelicula unaPelicula = new Pelicula("The Godfather", “Francis Ford Coppola”);
// Creamos un nueva copia física para la película// y asociamos la película a la copia físicaDvd copiaDVD = new Dvd(5, unaPelicula);
// Alquiler por 2 díasAlquiler alquiler = new Alquiler ("Juan Perez", copiaDVD, 2);
// Finalmente almacenamos el alquilerdb.Set(alquiler);db.Commit();
enlaces). En la sección de herramientas de este artículo incluimos un listado de otras utilidades para db4o. Desde el Object Manager entonces, nuestro alquiler recientemente almacenado en la base, se vería de la siguiente manera:
Haciendo click en el ícono del árbol de cada ítem, se muestra una vista jerárquica del item seleccionado.
Para poder ejecutar el Object Manager, deberá tener instalada una máquina virtual Java en su sistema (por ejemplo el llamado Java Runtime Environment o JRE). La forma correcta de ejecución entonces es llamando al archivo por lotes (.bat) que se encuentra
dentro de la carpeta del Object Manager y es el encargado de instanciar el applet de Java (.jar).
Entorno de trabajo
En el CD se encuentran dos proyectos para VisualStudio: uno que incluye acceso vía S.O.D.A. y otro mediante Consultas Nativas, temas que trataremos a continuación. Ambos proyectos están a la XP, es decir, se incluyen tests Nunit para verificar su correcto funcionamiento.
En principio hagamos algunas consultas muy simples sobre Pelicula, a fin de familiarizarnos con el acceso a db4o, y luego nos moveremos a algunas más interesantes.
Trabajaremos con C# y Java que son los lenguajes más populares en la comunidad de db4o. En ambos casos, primero deberá incluir las rutas necesarias para que el enlazador encuentre los métodos:
Javaimport com.db4o.Db4o;import com.db4o.Query;
C#using Db4objects.Db4o;using Db4objects.Db4o.Query;
La API de db4o es similar en todos los lenguajes soportados. Usualmente la forma de trabajo es similar al siguiente molde:
Java// Crear una base si no existe, abrirla si ya existe.File file = new File("testDb4o.yap");String fullPath = file.getAbsolutePath();ObjectContainer db = Db4oFactory.openFile(file);try{
// realizar alguna acción con db4o// Ejemplos :
db.set(obj); // Almacena un objeto en la base db.commit(); // Realizar la transacción (y arrancar otra) db.delete(obj); // Eliminar un objeto en la base}finally{ db.close(); // Cerrar la base y liberar los recursos}
C#// Crear una base si no existe, abrirla si ya existe.IObjectContainer db = Db4oFactory.OpenFile("testDb4o.yap");try{
// realizar alguna acción con db4o
// Ejemplos : db.Set(obj); // Almacena un objeto en la base db.Commit(); // Realizar la transacción (y arrancar otra) db.Delete(obj); // Eliminar un objeto en la base}finally{ db.Close(); // Cerrar la base y liberar los recursos}
La base de objetos consta de un archivo denominado YAP, en donde se almacenarán los objetos serializados. A nivel de aplicación se lo llama ObjectContainer y es el objeto sobre el cual se realizarán las operaciones de persistencia (Set(), Get(), Commit(), etc.).
En los ejemplos a continuación, asumiremos que el código se ejecuta dentro del bloque try { … }, y están incluidos los espacios de nombres citados arriba a fin de que se encuentren las funciones necesarias.
Acceso a Db4o
Antes de ver las formas de acceso a datos en Db4o, vamos a ponernos un poco en contexto: El acceso a los datos en una base de objetos es navigacional. La eficiencia en velocidad (una de las características notables de db4o) viene dada por el hecho de que las referencias son almacenadas directamente en cada instancia, mientras que en las bases de datos tradicionales (que fueron realizadas antes de que el mecanismo de herencia fuera ampliamente utilizado), el acceso es tabular, y lo que generalmente se intenta en estos casos para “salvar” la distancia con el mundo de objetos, es utilizar mapeadores ObjetoRelacionales, que incluyen API’s o lenguajes que dan la “ilusión” de trabajar con objetos.
En db4o existen tres formas de realizar consultas, cada una basada en una tecnología diferente. Estas son:
• S.O.D.A.: Simple Object Database Access / Acceso Simple a Bases de Datos de Objetos.
• Q.B.E.: Query By Example / Consulta por Ejemplo o Plantilla.
• N.Q.: Native Queries / Consultas Nativas
En un principio, db4o permitía acceder mediante Q.B.E. y S.O.D.A. [2], siendo esta última la interface preferida por ser una tecnología desarrollada por el mismo “creador” de db4o, Carl Rosenberg. La experiencia tomada con los usuarios durante el período 2003 – 2005 ha valido para dar cuenta de sus limitaciones y proponer una nueva tecnología de acceso a datos persistidos, que supliera con estas limitaciones. A partir de la versión 6 de db4o, las Consultas Nativas (N.Q.) se convirtieron en la interface principal de acceso, mientras que S.O.D.A. se ha mantenido por cuestiones de compatibilidad y para el uso de consultas generadas dinámicamente. A pesar de su reciente inclusión en db4o, las idea detrás de las Consultas Nativas no son un concepto nuevo; las colecciones de Smalltalk ya incluían esta capacidad desde su especificación en 1983. Veamos cuales son sus ventajas y desventajas principales:
Tecnología Ventajas Desventajas QBE • Simplicidad, recomendada
para principiantes.• Funcionalidades limitadas: Carece de
consultas con juntores (AND, NOT, OR, etc).
• Se necesita agregar un constructor para crear objetos sin campos inicializados.
• No se puede consultar por valores nulos
SODA • Independencia del lenguaje de programación utilizado.
• API simple. Minimiza el uso de cadenas para consultas
• Opera sobre propiedades• Introduce conceptos
relacionados con grafos de nodos (natural a las bases de objetos): Descender y Restringir por nodos que pueden ser una o muchas clases, un atributo de una clase, o consultas mismas.
• No se verifican tipos en tiempo de compilación (query.Descend(“dirctor”) fallaría en tiempo de ejecución)
• Demasiado explícita (verbose).
• Opera sobre campos , (pelicula._director) en vez de propiedades (pelicula.getDirector() o pelicula.Director)
• Requiere actualizar las consultas cuando se refactoriza o modifica el modelo de clases.
• Consultas basadas en cadenas embebidas son blanco para ataques vía inyección de código.
NQs • Interface recomendada para Db4o
• 100% segura en tipos: Verificación de errores semánticos y sintácticos en tiempo de compilación.
• 100% refactorizable• 100% nativas: no hay
necesidad de aprender lenguajes de consultas o API’s
• Las consultas deben ser traducidas al lenguaje o API del motor, esto puede penalizar en rendimiento en el caso de consultas complejas, donde no es posible hacerlo sin instanciar algunos objetos persistidos.
Acceso mediante S.O.D.A. (Simple Object Database Access):
Al trabajar con db4o, debemos imaginar nuestros datos como en un grafo en el que podemos navegar (o consultar), a diferencia de lo que haríamos con una base relacional donde los datos están tabulados y la idea predominante es combinar resultados en tablas.El acceso con S.O.D.A. se realiza mediante la clase Query, que es obtenida a través del ObjectContainer. Consideremos algunas operaciones típicas:
Java// Instanciamos un objeto query para realizar las consultasQuery query = db.query();
// Indicamos el objeto contra el cual se realizará la consultaquery.constrain(Pelicula.class);
////////////////////////////////////////////////////////////////// Configuramos la/s consulta/s en el objeto query////////////////////////////////////////////////////////////////
// Por dato particular : Recuperemos la(s) Pelicula(s) llamada(s) // “El Padrino” si existe(n).query.descend("nombre").constrain("El Padrino");
// Por negación : Recuperar las películas que no se llamen // “El Padrino” si existen.query.descend("nombre").constrain("El Padrino").not();
// Por conjunción : Recuperar las películas llamadas “El Padrino” y// cuyo director sea “Francis Ford Coppola”Constraint constr = query.descend("nombre").constrain("El Padrino");query.descend("director").constrain(“Francis Ford Coppola”).and(constr);
// Por disyunción : Recuperar las películas llamadas “El Padrino” o// cuyo director se llame “Francis Ford Coppola”Constraint constr = query.descend("nombre").constrain("El Padrino");query.descend("director").constrain(“Francis Ford Coppola”).or(constr);
// Por similitud: Recuperar las películas que contengan “God” en su títuloIConstraint constr = query.descend("nombre").constrain("God").like();
// Ordenando resultados de forma ascendentequery.descend("nombre").orderAscending();
// Ordenando resultados de forma descendentequery.descend("nombre").orderDescending();
// Ejecutar la consulta y Mostrar los resultadosObjectSet result = query.execute();listResult(result);
C#// Instanciamos un objeto query para realizar las consultasQuery query = db.Query();
// Indicamos el objeto contra el cual se realizará la consultaquery.Constrain(typeof(Pelicula));
////////////////////////////////////////////////////////////////// Configuramos la/s consulta/s en el objeto query////////////////////////////////////////////////////////////////
// Por dato particular : Recuperemos la(s) Pelicula(s) llamada(s) // “El Padrino” si existe(n).query.Descend("_nombre").Constrain("El Padrino");
// Por negación : Recuperar las películas que no se llamen // “El Padrino” si existen.query.Descend("_nombre").Constrain("El Padrino").Not();
// Por conjunción : Recuperar las películas llamadas “El Padrino” y// cuyo director sea “Francis Ford Coppola”Constraint constr = query.Descend("_nombre").Constrain("El Padrino");query.Descend("_director").Constrain(“Francis Ford Coppola”).And(constr);
// Por disyunción : Recuperar las películas llamadas “El Padrino” o
// cuyo director se llame “Francis Ford Coppola”Constraint constr = query.Descend("_nombre").Constrain("El Padrino");query.Descend("_director").Constrain(“Francis Ford Coppola”).Or(constr);
// Por similitud: Recuperar las películas que contengan “God” en su títuloIConstraint constr = query.Descend("_nombre").Constrain("God").Like();
// Ordenando resultados de forma ascendentequery.Descend("_nombre").OrderAscending();
// Ordenando resultados de forma descendentequery.Descend("_nombre").OrderDescending();
// Ejecutar la consulta y Mostrar los resultadosObjectSet result = query.Execute();listResult(result);
Observamos en los ejemplos previos que mediante una consulta se “desciende” por los nodos especificando un atributo. Estos nodos pueden referir a tanto tipos primitivos como String en los ejemplos de _nombre o _director, o bien referir a otros objetos de nuestra aplicación, como veremos adelante en el ejemplo de acceso a objetos anidados. Finalmente, se “restringe” por el dato que queremos consultar ("El Padrino", “Stanley Kubrick”, Pelicula, DVD, etc). Observemos que las restricciones se dan en dos lugares: Al comienzo, cuando especificamos la clase de lo que queremos obtener (query.Constrain(typeof(Pelicula))) y al final mismo (Constrain(“Francis Ford Coppola”)).
A su vez, estas restricciones (Constraints), se pueden combinar mediante juntores (Or, And, Not) y operadores de comparación (Equal, Identity, Like, Contains, Smaller, Greater, etc).
Por lo tanto, descender y restringir son básicamente las ideas principales con S.O.D.A.. Descender por una consulta da como resultado otra consulta, a la cual podemos descender nuevamente y realizar la restricción que nos interese:
Java// Acceso a objetos anidadosquery.constrain(Alquiler.class);query.descend("copiaFisica").descend("pelicula").constrain(“nombre”);
C#// Acceso a objetos anidadosquery.Constrain(typeof(Alquiler));query.Descend("copiaFisica").Descend("pelicula").Constrain(“_nombre”);
Aquí restringimos por Alquiler, descendimos dos niveles por copiaFisica y pelicula, para luego restringir por nombre. Esta idea de descenso, conlleva de por sí la idea de profundidad. Veamos un caso en donde la profundidad es importante, la actualización de objetos. Pongamos por ejemplo que deseamos actualizar una Película:
Java
// Obtenemos una película utilizando un prototipo1. Query query = db.query();2. ObjectSet result = db.get(new Pelicula("El laberinto del Fauno"));3. Pelicula found = (Pelicula)result.next();// Actualizar el director4. found.director = “Guillermo del Toro”;5. db.set (found);// Mostrar resultados6. Result = db.get (new Pelicula("El laberinto del Fauno"));7. listResult(result);
C#// Obtenemos una película utilizando un prototipo1. Query query = db.Query();2. ObjectSet result = db.Get(new Pelicula("El laberinto del Fauno"));3. Pelicula found = (Pelicula)result.Next();// Actualizar el director4. found.Director = “Guillermo del Toro”;5. db.Set (found);// Mostrar resultados6. Result = db.Get (new Pelicula("El laberinto del Fauno"));7. listResult(result);
Observamos una forma alternativa de recuperar un objeto, especificando un prototipo, el equivalente de las líneas 2 y 3 sin prototipos sería:
C#1. Query query = db.Query();2. query.Constrain(typeof(Pelicula));3. query.Descend("_name").Constrain("El laberinto del Fauno"); ObjectSet result = query.Execute();
Java1. Query query = db.query();2. query.constrain(Pelicula.class);3. query.descend("name").constrain("El laberinto del Fauno"); ObjectSet result = query.execute();
Volviendo a la cuestión de la profundidad: Por omisión, sólo se actualizará el objeto pasado como parámetro al método Set(), es decir, los miembros primitivos de ese objeto, por ello la profundidad de actualización (update depth) será igual a 1. Es importante recordarlo ya que en general un modelo de objetos medianamente complejo seguramente utilizará más de un nivel de contención de objetos que no son primitivos. Si quisiéramos actualizar los objetos miembros, es decir, aquellos que están asociados a un objeto, debemos configurar el objeto raíz en cuestión para que actualice el grafo a la profundidad deseada.
Java// Actualizar el grafo completo de objetos desde PeliculaDb4o.configure().objectClass(Pelicula.class).cascadeOnUpdate(true);
C#
// Actualizar el grafo completo de objetos desde PeliculaDb4o.Configure().ObjectClass(typeof(Pelicula)).CascadeOnUpdate(true);
En general es conveniente realizar un plan de actualización de tal forma que al ejecutar las consultas, sólo se recuperen aquellos objetos que necesiten actualizarse y no más, a fin de no penalizar en velocidad de acceso.
Acceso mediante Consultas Nativas (NQs):
Como vimos, una base de objetos en db4o se crea mediante código escrito en el mismo lenguaje de desarrollo de la aplicación (C#, Java, Visual Basic, etc.), por lo que no es necesario aprender otros lenguajes como SQL, HQL de Hibernate, OQL, JDOQL, EJBQL, o SODA (además del lenguaje de desarrollo elegido) para persistir nuestros datos. Esta característica es llamada Transparencia Persistente, "Native Queries" o simplemente NQs, y está marcando una tendencia entre los usuarios de Db4o. Aún cuando considere que el uso o aprendizaje de otro lenguaje no es realmente relevante, existe otra gran dificultad con los lenguajes de consultas, y es que están basados en cadenas. Estas cadenas deben ser analizadas léxicamente y sintácticamente por el parser del motor de persistencia, esto desde ya añade tiempo y es un proceso propenso a errores. S.O.D.A incluso, que minimiza el uso de cadenas, también tiene la dificultad de que el compilador no puede verificar la validez del atributo de una clase.
Sin embargo, internamente, las Consultas Nativas funcionan sobre S.O.D.A., por lo que existe un conversor y un optimizador de consultas llamado “Native Query Optimizer” para maximizar su rendimiento.
En concreto, la idea tras NQs es posibilitar la forma más simple posible de realizar una consulta. Esta forma más simple trabajaría con una instancia prototípica, es decir, una creada como si fuera un ejemplo, pero que representa a todas las de su tipo. Naturalmente esto sería algo tan simple como:
Java// Obtener las películas que contienen la cadena “Padrino”Pelicula.getNombre().contains(“Padrino”);
C#// Obtener las películas que contienen la cadena “Padrino”Pelicula.Nombre.Contains(“Padrino”);
Siempre tenemos que tener en cuenta que lo principal de la consulta es esta expresión, que realiza la acción que nos interesa. A partir de aquí en adelante lo que veremos es de algún modo, cómo conformar a cada compilador y al motor de Db4o para que acepten esta expresión de la forma más simple posible. Esto no es fundamental, ni tampoco es complejo, pero sí es necesario aprenderlo para poder utilizar Consultas Nativas.
Para que esto funcione entonces, lo primero que necesitaríamos es especificarle el tipo de esta instancia prototípica (película) a la expresión sobre la cual realizamos la consulta, y devolver los resultados en algún tipo de contenedor. Algo como:
Java// Obtener las películas que contienen la cadena “Padrino”(Pelicula pelicula) { return pelicula.getNombre().contains(“Padrino”);}
C#// Obtener las películas que contienen la cadena “Padrino”(Pelicula pelicula) { return pelicula.Nombre.Contains(“Padrino”);}
La próxima característica a agregar a la expresión necesita una introducción. Hace un instante mencionamos que, internamente, db4o realiza un proceso de análisis, conversión y posible optimización sobre las consultas. Esto agrega un nuevo requerimiento a nuestra expresión, que debería ser de tal forma que pueda pasarse como parámetro al motor de la base de objetos, u otro procesador de consultas, y así poder ser modificada.
Siempre priorizando la simplicidad sintáctica, necesitaríamos entonces un objeto que pueda referenciar una especie de método “anónimo” que sea usado como parámetro y sea modificable, ya sea bien optimizándose o simplemente traduciéndose a SODA. Esto es posible a partir de .NET 2.0 utilizando los llamados delegados. Un delegado es como un par objetométodo que funciona como una retrollamada (callback), y sirve para tratar a un método como un objeto de primera clase, y así pasarse como parámetro a otros métodos. Los delegados son muy usados en el mundo de .NET para realizar notificaciones de eventos, ejecuciones encadenadas y algunos otros casos en los que se necesite reflexión computacional.
En Java 1.2 – 1.4 y 5.0, es posible emular este comportamiento utilizando la clase Predicate como una clase anónima, y escribiendo en un método match la expresión dentro de ella, un detalle que le resta simplicidad en comparación con la versión de C#. En el caso particular de Java 5.0, se puede hacer uso de los generics. En código esto sería:
Java// Obtener las películas que contienen la cadena “Padrino”new Predicate() { public boolean match(Pelicula pelicula) { return pelicula.getNombre().contains(“Padrino”);}
C#// Obtener las películas que contienen la cadena “Padrino”delegate (Pelicula pelicula) { return pelicula.Nombre.Contains(“Padrino”);}
Lamentablemente la sintaxis para definir métodos anónimos en C# y Java aún es algo excesiva (esto tal vez cambie en el futuro con versiones posteriores de los lenguajes), en comparación con sus definiciones “equivalentes” en otros lenguajes, por ejemplo las expresiones lambda en Haskell o los CompiledMethods o bloques de Smalltalk. Aún así
pese a esta desventaja, en nuestra opinión, su uso es recomendable considerando sus contrapartes en lenguajes como SQL o lenguajes de consultas basadas en cadenas, propensos a errores de tipeo, dificultades para depuración, y a utilizar cientos de palabras claves. Existen también algunas ventajas adicionales como la posibilidad de realizar consultas dinámicas y parametrizadas, temas que podrían tratarse en futuros artículos.Volviendo a nuestra expresión, finalmente agregamos el contenedor para los resultados, quedando:
Java 5.0List <Pelicula> peliculas = database.query <Pelicula> ( new Predicate <Pelicula> () { public boolean match(Pelicula pelicula) { return pelicula.getNombre().contains(“El Padrino”);
}});
Java 1.2 – 1.4List peliculas = database.query ( new Predicate () { public boolean match(Pelicula pelicula) {
return pelicula.getNombre().contains(“El Padrino”);}
});
C# .NET 2.0IList <Pelicula> peliculas = db.Query <Pelicula> (
delegate (Pelicula pelicula) { return pelicula.Nombre.Contains(“El Padrino”);});
C# .NET 1.1IList <Pelicula> peliculas = db.Query (new PeliculaQuery());Public class PeliculaQuery : Predicate{
Public boolean match(Pelicula pelicula) { return pelicula.Nombre.Contains(“El Padrino”);}
};
Vamos ahora a reescribir nuestras consultas S.O.D.A, a la Consultas Nativas:
Java
// Consulta por dato particular : Recuperar la(s) Pelicula(s) // llamada(s) “El Padrino” si existe(n).
List <Pelicula> peliculas = this.db.query(new Predicate<Pelicula>() { public boolean match (Pelicula pelicula) { return pelicula.get_nombre().equals(“El Padrino”); }});
// Por negación : Recuperar las películas que no se llamen // “El Padrino” si existen.
List <Pelicula> peliculas = this.db.query(new Predicate<Pelicula> (){ public boolean match (Pelicula pelicula) { return !pelicula.get_nombre().equals(“El Padrino”); }});
// Por conjunción : Recuperar las películas llamadas “El Padrino” y// cuyo director sea “Francis Ford Coppola”List <Pelicula> peliculas = this.db.query(new Predicate <Pelicula> (){ public boolean match (Pelicula pelicula) { return pelicula.get_nombre().equals(“El Padrino”) &&
pelicula.get_director().equals(“Francis Ford Coppola”); }});
// Por disyunción : Recuperar las películas llamadas “El Padrino” o// cuyo director se llame “Stanley Kubrick”List <Pelicula> peliculas = this.db.query(new Predicate <Pelicula>(){ public boolean match (Pelicula pelicula) { return pelicula.get_nombre().equals(“El Padrino”) ||
pelicula.get_director().equals(“Stanley Kubrick”); }});
// Por similitud: Recuperar las películas que contengan “God” en su títuloList <Pelicula> peliculas = this.db.query(new Predicate <Pelicula>(){ public boolean match (Pelicula pelicula) {
return pelicula.get_nombre().contains("God");}});
// Ordenación: Alfabéticamente por nombre especificando un criterio // de comparación con un delegado anónimoComparator<Pelicula> peliculaCmp = new Comparator<Pelicula>() { public int compare (Pelicula pel1, Pelicula pel2){ return pel1.get_nombre().compareTo(pel2.get_nombre()); }}; List <Pelicula> peliculas = this.db.query(new Predicate<Pelicula>() { public boolean match (Pelicula pelicula) {
return true; }}, peliculaCmp);
C#
// Consulta por dato particular : Recuperar la(s) Pelicula(s) // llamada(s) “El Padrino” si existe(n).
IList<Pelicula> peliculas = db.Query<Pelicula>(delegate(Pelicula peliculaDb) { return peliculaDb.Nombre(“El Padrino” );});
// Por negación : Recuperar las películas que no se llamen // “El Padrino” si existen.IList<Pelicula> peliculas = db.Query<Pelicula>(delegate(Pelicula peliculaDb) { return ¡peliculaDb.Nombre(“El Padrino” );
});
// Por conjunción : Recuperar las películas llamadas “El Padrino” y// cuyo director sea “Francis Ford Coppola”IList<Pelicula> peliculas = db.Query<Pelicula>(delegate(Pelicula peliculaDb) { return (peliculaDb.Nombre == “El Padrino”) & (peliculaDb.Director == “Francis Ford Coppola”); });
// Por disyunción : Recuperar las películas llamadas “El Padrino” o// cuyo director se llame “Stanley Kubrick”IList<Pelicula> peliculas = db.Query<Pelicula>(delegate(Pelicula peliculaDb) { return (peliculaDb.Nombre == “El Padrino”) | (peliculaDb.Director == “Stanley Kubrick”); });
// Por similitud: Recuperar las películas que contengan “God” en su título
IList<Pelicula> peliculas = db.Query<Pelicula>(delegate(Pelicula peliculaDb) { return (peliculaDb.Nombre.Contains == “God”); });
// Ordenación: Alfabéticamente por nombre especificando un criterio de // comparación con un delegado anónimoComparison<Pelicula> peliculaCmp = new Comparison<Pelicula>(delegate(Pelicula p1, Pelicula p2){ return p2.Nombre.CompareTo(p1.Nombre);});
IList<Pelicula> peliculas = db.Query<Pelicula> (delegate(Pelicula peliculaDb) { return true; }, peliculaCmp);
Debido a las ventajas mencionadas anteriormente, existe dentro de la comunidad Db4o una marcada tendencia hacia las Consultas Nativas a pesar de su reciente novedad en el mundo de desarrolladores Java y .Net. Una forma conveniente de acostumbrarse al trabajo con Consultas Nativas es primero escribir la expresión de la consulta, luego agregar la colección devuelta, y finalmente encabezado correspondiente al delegado para C# o el predicado en el caso de Java, que suelen tener una forma más similar entre todas ellas.
Muchos desarrolladores configuran y utilizan unos archivos en formato XML llamados snippets en VisualStudio o templates en Eclipse, que insertan el texto reusable en nuevos contextos o aplicaciones (boilerplates) y pueden ser de especial utilidad en estos casos.
Herramientas
Existen algunas herramientas y conectores (plugins) adicionales interesantes, descargables desde el Centro de Descargas de Db4o:
ObjectManager GUI Administration Tool: Es una interface gráfica realizada en Java para manipular bases Db4o.
db4o Replication System (dRS): Es una herramienta de compatibilidad basada en Hibernate (un framework de persistencia y de mapeo objetorelacional muy popular) que permite replicar datos desde db4o hacia db4o o algún RDBMS, o bien desde un RDBMS hacia db4o.
Db4o.Binding.NET: Incluye clases para trabajo con listas en memoria y enlace de resultados de consultas con controles de Windows Forms y Windows Presentation Forms (parcialmente). Soporta consultas, filtros, ordenación, paginación, etc.
Db4o Eclipse: Plugin documentado para Eclipse con acciones para realizar copias de resguardo, defragmentación, etc.
Conclusión
Tal vez le haya sorprendido lo poco que hay que aprender con db4o. En la jerga académica esto se llama “gap semántico”, lo que significa en pocas palabras, es que el uso de la tecnología de objetos (T.O.) está “más cerca“ del lenguaje natural de los humanos, por el simple hecho de que requiere que usted conozca menos cuestiones relacionadas con la máquina en sí. Esto permite a los desarrolladores concentrarse más en el problema a resolver, el llamado dominio del sistema, en vez de invertir tiempo en cuestiones propias de la tecnología elegida.
Enlaces
Sitiohttp://www.db4o.com/espanol (con documentación en castellano)Forohttp://developer.db4o.com/forums/15/ShowForum.aspx (para interactuar con la comunidad)Wikihttp://developer.db4o.com/ProjectSpaces/view.aspx/Espanol (un espacio web gratuito de trabajo comunitario en castellano)Bloghttp://developer.db4o.com/blogs/espanol/default.aspx (para anuncios y noticias)Centro de Descargas:http://developer.db4o.com/filesDescargas de la Comunidad Hispanahttp://developer.db4o.com/files/folders/spanish/default.aspx
Referencias
[1] Cómo: Utilizar el archivo de configuración de una aplicación para determinar la versión de .NET Framework que se va a usar : http://msdn2.microsoft.com/eses/library/9w519wzk(VS.80).aspx[3] Sitio de S.O.D.A. http://sodaquery.sourceforge.net[2] David Taylor. Object Technology. Editorial Addison Wesley (2da Ed. 1997) capítulo 6.[3] William R. Cook, Carl Rosenberg. Native Queries for Persistent Objects. A Design White Paper. National Science Fundation (15 de febrero de 2006).[4] Adele Goldberg, D. Robson. Smalltalk80: the Language and Its Implementation. Editorial Addison Wesley (1983).[5] JRE Test (verificar si está instalada y funcional la máquina virtual de Java de Sun) http://java.com/en/download/help/testvm.xml[6] Máquina Virtual de Java de Sun : http://developers.sun.com/downloads/top.jsp[7] Jim Paterson, Stefan Edlich, Henrik Horning, Reidar Horning. The definitive guide to Db4o. Apress (2006).