Personal Website

My Web: MindEchoes.com

Wednesday, August 12, 2009

QuickDB - 1.1-SNAPSHOT

Bien, hace unos dias venia programando esta libreria de Persistencia, y aunque todavia esta en desarrollo, ya tiene muchas funcionalidades implementadas que pueden usarse y queria mostrar como el uso de esta libreria puede facilitar mucho el manejo de persistencia.

Para mostrar las cosas que se pueden hacer con la libreria desarrolle un ejemplo utilizando una Base de Datos MySQL (la unica soportada por el momento).
El script de creación de la Base de Datos puede obtenerse del siguiente link.

Las Tablas de la Base de Datos estan representadas en la siguiente imagen:


Bien ahora que tenemos nuestra Base de Datos con nuestras Tablas, vamos a ir creando nuestras clases Entidades que se mapean con nuestras tablas y explicando el uso de QuickDB con ejemplos concretos.

Primero vamos a crear la Clase Person:
import cat.inkubator.annotation.*;

@Table
public class Person{

@Column(type=Properties.TYPES.PRIMARYKEY)
private int id;
@Column(name="name")
private String personName;
@Column(getter="getPersonAge", setter="setPersonAge")
private int age;
@Column(type=Properties.TYPES.SQL)
private String sql;

public Person(String name, int age){
this.id = 0;
this.personName = name;
this.age = age;
this.sql = "";
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getPersonName() {
return personName;
}

public void setPersonName(String personName) {
this.personName = personName;
}

public int getPersonAge() {
return age;
}

public void setPersonAge(int age) {
this.age = age;
}

public String getSql() {
return sql;
}

public void setSql(String sql) {
this.sql = sql;
}

}
Bueno, la libreria utiliza solo 2 Anotaciones para el manejo de los datos de la Entidad:
  • Table
  • Column
Table puede estar acompañada de un parámetro o no, el parametro de Table indica a que tabla de la Base de Datos se mapeara esta entidad. Al no incluir el parametro simplemente se asume que la Tabla lleva el mismo nombre que la Clase.
Por el contrario si la Tabla tuviera otro nombre simplemente se podria hacer:
@Table("[nombre-tabla]")
public class Person{

En cuanto a las anotaciones Column, estas pueden recibir 4 parámetros:
  • name: Nombre del Campo de dicha Tabla en la Base de Datos (si no se especifica toma el mismo nombre del atributo).
  • type: Indica el tipo de dato del atributo, el cual puede ser [PRIMITIVE, PRIMARYKEY, FOREIGNKEY, SQL]. PRIMITIVE hace referencia a aquellos tipos que se mapean directamente a la base de datos como enteros, cadenas, etc (si no se especifica se asume que el tipo es PRIMITIVE).
  • getter: Este atributo presenta la posibilidad de poder expresar explicitamente cual sera el metodo del cual obtendremos el valor de dicho atributo (si no se especifica se asume que si el atributo lleva el nombre "atributo" el metodo getter sera "getAtributo").
  • setter: El mismo caso anterior pero para los metodos de seteo.
Como se puede ver en el ejemplo de la Clase Person, al atributo "id" se le especifica que es el atributo que se mapeara con la PRIMARYKEY en la Base de Datos, esto es de importancia para que al realizar una inserción, no intente ingresar este dato si lo tenemos configurado para que se auto-incremente.
Al Atributo "personName" se le especifica en su anotacion cual sera el nombre del Campo en la Tabla de la Base de Datos al que se mapeara su valor al no tener una relación directa.
En cuanto al Atributo "age" se le especifican explicitamente los metodos de "set" y "get" al no seguir la convención por defecto.
Y por ultimo al ultimo atributo "sql" se le asigna tipo SQL lo cual nos indica que a traves de ese atributo podremos realizar algunas interacciones directas con la Base de Datos o especificar simplemente la parte del WHERE para las busquedas como ya se vera.

Ahora que ya se encuentran explicadas el uso de las anotaciones, veamos el resto de las Entidades:

Address:
(Es necesario tener en nuestras entidades todos los Getters y Setters, pero en los ejemplos no se mostraran para que el código no quede tan largo)
import cat.inkubator.annotation.*;

@Table("address")
public class Address{

@Column(type=Properties.TYPES.PRIMARYKEY)
private int id;
@Column
private String street;
@Column(type=Properties.TYPES.FOREIGNKEY)
private District idDistrict;

public Address(){
this.street = "";
this.idDistrict = new District("");
}

public Address(String street, District district){
this.street = street;
this.idDistrict = district;
}

}
En este caso al ser MySQL case-sensitive (al menos en Linux) y la Tabla llevar el nombre "address" con la primer letra en minuscula, se utiliza el parametro de @Table para especificar la Tabla a la que se mapeara.
En el caso del Atributo "street" al seguir las convenciones y valores por defecto simplemente se le agregar la anotación @Column arriba sin parametros y listo.
Y para el tercer caso "idDistrict" es en realidad un objeto de otra Clase nuestra, que a su vez se mapea en otra Tabla como se ve en el diagrama de arriba, por esto se indica en su tipo FOREIGNKEY, y la libreria automaticamente se encarga de guardar ese objeto en su respectiva Tabla y luego obtener su "id" para insertarlo en el campo de la Tabla "address".

District:
import cat.inkubator.annotation.*;

@Table("district")
public class District{

@Column(type=Properties.TYPES.PRIMARYKEY)
private int id;
@Column
private String name;

public District(String name){
this.name = name;
}

}
Ahora vamos a utilizar estas clases creadas para hacer algunas operaciones antes de seguir con el resto.
Ahora creamos nuestro main donde crearemos distintas instancias de nuestras Entidades y realizaremos operaciones sobre la Base de Datos:
import cat.inkubator.db.AdminBase;
import java.util.ArrayList;

public class App
{

public static void main( String[] args )
{
AdminBase admin = new AdminBase("localhost", "3306", "testQuickDB",
"root", "", AdminBase.DATABASE.MYSQL);
Person person = new Person("diego", 23);
admin.save(person);
}

}
De esta forma, creamos un Objeto "AdminBase" pasandole los parametros necesarios para que realice la conexión a la Base de Datos correspondiente, y luego simplemente nos dedicamos a utilizar AdminBase pasandole a sus distintos metodos nuestro objetos para que haga operaciones con ellos.
Como en el ejemplo que se ve arriba, se crea una instancia de la Clase Person, y se ejecuta el método "save" de AdminBase para guardar ese objeto.

Ahora si quisieramos guardar un conjunto de Objetos Person, simplemente hariamos lo siguiente:
        ArrayList array = new ArrayList();
array.add(new Person("name1", 20));
array.add(new Person("name2", 20));
admin.saveAll(array);
Para realizar busquedas, y por ejemplo obtener un Objeto Person, de una persona que tenga edad 23 (como se cargo en el primer ejemplo de aplicación) se empezaria a utilizar el atributo marcado como SQL de la siguiente forma:
        Person p = new Person("",0);
p.setSql("age=23");
admin.obtainSingle(p);
Es importante antes de cualquier operación crear una instancia (vacía) del objeto al que se esta haciendo referencia para que la librería pueda inferir cuales son los atributos que debe manejar.

Para realizar una modificación y luego eliminar ese objeto de la Base de Datos simplemente hacemos (en este caso se sigue trabajando con el objeto obtenido en el ejemplo anterior):
        p.setPersonName("utopia");
admin.modify(p);

admin.delete(p);
Ahora supongamos que queremos hacer una busqueda de aquellas personas que tengan edad 20 y obtener todos los resultados posibles. AdminBase tiene un metodo que nos permite realizar esta operación y nos devuelve un boolean indicando si se pudieron encontrar objetos o no y nos completa los datos del objeto que le pasamos por parametro. Luego para ir explorando la lista completa de los objetos obtenidos se utilizan los metodos "obtainNext" y "obtainPrevious", los cuales nos devuelven true si existe el dato que queremos ver o false si por el contrario ya no hay mas datos en esa dirección.
Ahora si quisieramos tener de una sola vez nuestro Array de objetos completo podriamos hacer lo siguiente:
        Person per = new Person("",0);
per.setSql("age=20");
ArrayList results = new ArrayList();
if(admin.obtainAll(per)){
results.add(per);
App app = new App();
app.loadArray(admin, array);
}

public void loadArray(AdminBase admin, ArrayList array){
Person per = new Person("",0);
if(admin.obtainNext(per)){
array.add(per);
this.loadArray(admin, array);
}
}
Ahora supongamos que tenemos un Objeto compuesto por otro Objeto, lo que se mapearia en la Base de Datos como una Tabla con una referencia de Foreign Key hacia otra Tabla como es el caso de las Tablas: "Address" y "District".
Bueno, para este caso simplemente marcamos cual de nuestro atributos es una Foreign Key (como hicimos en la clase Address), y QuickDB se encargara de guardar todos los objetos en sus respectivas Tablas y asignar el "id" obtenido en la inserción a la Tabla que la referencia.
Entonces para realizar la inserción de un Objeto Compuesto simplemente hacemos:
        District d = new District("nva cba");
Address a = new Address("puey 600", d);
admin.save(a);
AdminSave tambien permite la inserción de objetos compuestos de forma masiva utilizando como se vio antes "saveAll".

Ahora supongamos que nuestras Entidades en nuestro programa se mapean DIRECTAMENTE a nuestra Base de Datos, es decir, nuestras Tablas se llaman igual que nuestras Clases y nuestro Campos igual que nuestros Atributos, en ese caso podriamos optar simplemente por crear las Clases sin anotación alguna como en los siguientes ejemplos:

Dog:
public class Dog{

private int id;
private String name;
private String color;
private Race idRace;
private String sql;

public Dog(){
this.idRace = new Race();
this.name = "";
this.color = "";
}

}
Race:
public class Race{

private int id;
private String name;
private String sql;

public Race(){
this.name = "";
}

}
(Los metodos de "set" y "get" fueron obviados para el ejemplo pero deben estar presentes)

Como podemos ver estas 2 clases no presentan ninguna de las anotaciones presentadas anteriormente. Cuando trabajamos con clases asi que pueden ser mapeadas directamente a la Base de Datos, podemos desactivar el modo "Verbose" de AdminBase y trabajar con estas Clases de la misma forma que hariamos si contuvieran las anotaciones, incluso para objetos compuestos como puede verse en el ejemplo:
        admin.setVerbose(false);
Dog dog = new Dog();
dog.setName("sasha");
dog.setColor("black");

Race race = new Race();
race.setName("collie");

dog.setIdRace(race);
admin.save(dog);

dog.setSql("name='sasha'");
admin.obtainSingle(dog);
En el ejemplo anterior se crea un objeto "Dog" y un objeto "Race" se asigna a Dog el objeto Race y despues se guardan esos datos, de esta forma se guardara el Objeto Race en su respectiva Tabla, devolvera el Id de la posición en la que se guardo y luego se guardara el Objeto Dog en su respectiva Tabla colocando en el campo de Foreign Key de Race el Id obtenido previamente.
Luego simplemente se realiza un "obtainSingle" para obtener un objeto con los mismos valores que se acaban de guardar para mostrar que tambien se pueden realizar consultas de objetos compuestos sin necesidad de anotar las clases.

El código fuente del Proyecto se encuentra en: QuickDB
Y el ejemplo que se presento puede ser descargo completamente junto con los fuentes y la libreria compilada de QuickDB y el script de creación de la Base de Datos del siguiente link: Ejempo

2 comments:

migue said...

muy buena la librería.. estoy jugando un poco con ella.. es posible hacer una referencia de clave foránea a la misma tabla? como en el caso de address y district.. por ejemplo en persona un objeto persona que (es el primer ejemplo que se me viene) represente su padre o madre.. gracias.. un saludo..

Diego Sarmentero said...

Si, si por ejemplo quisieras hacer un objeto Persona que por algún motivo contuviera una referencia a otra Persona (supone un jefe que tiene a una persona a su cargo), simplemente creas la clase de esta forma:

public class Person{

private int id;
private String name;
private int age;
private Person person;
}

Tal cual como lo trabajas desde el lenguaje en forma de objeto, haces que la clase apunte a un objeto de su mismo tipo.

Entonces después podes hacer:

Person p = new Person("diego", 22);
Person p2 = new Person("leeloo", 2);
p.setPerson(p2);

admin.save(p);


Como este post es medio viejo de cuando la librería estaba hosteada en otro lado, te paso por las dudas la nueva dirección:
http://quickdb.googlecode.com

En esa pagina hay un tutorial en español completo y cualquier duda también podes mandar mensaje al grupo de QuickDB.

Saludos!