Mostrando entradas con la etiqueta SQL. Mostrar todas las entradas
Mostrando entradas con la etiqueta SQL. Mostrar todas las entradas

domingo, 20 de febrero de 2011

Trabajando con NHibernate

Hibernate, NHibernate si estamos trabajando sobre la plataforma NET, es un framework de persistencia, con el que podemos mapear atributos del modelo relacional de una base de datos, con el modelo de objetos de una aplicacion. Basicamente, si tengo la clase Cliente, y estoy utilizando una base de datos para alamacenar información de los clientes, seguramente voy a tener una tabla de este estilo donde voy a persistir toda la información de cada uno.

Una de las grandes ventajas que ofrece Hibernate es que es independiente al motor de base de datos que se utilice. Simplemente, haciendo una modificacion al archivo de configuración, que nos permite conectarnos con la BD, es mas que suficiente. Esto es muy interesante ya que al no trabajar con codigo SQL explícito, no es necesario andar haciendo retoques para que funcione con diferentes motores.

Porsupuesto Hibernate es software libre, por lo cual no tendremos que desembolsar un solo centavo para trabajar con el.

Como primer paso creamos un nuevo proyecto desde Visual Studio. En mi caso voy a utilizar un proyecto del tipo Aplicación de Consola, ya que solamente voy a mostrar un caso bastante sensillo. Desde ya que ustedes pueden implementar Hibernate en una infinidad de proyectos.

Una vez que hemos creado el proyecto, para implementar NHibernate, necesitamos descargar las librerias a las cuales le agregaremos las referencias correspondientes.

Para descargar la última version pueden acceder a la página:

http://sourceforge.net/projects/nhibernate/files/NHibernate/3.0.0.GA/NHibernate-3.0.0.GA-bin.zip/download

Desde este sitio se pueden bajar la última disponible.

Una vez que la descarguen, simplemente le agregan las referencias a las siguientes librerias:

Castle.Core
Castle.DynamicProxy
Iesi.Collections
log4net
NHibernate
NHibernate.ByteCode.Castle

Como podrán observar en este caso voy a utilizar Castle. No necesariamente deben utilizar este framework, ustedes pueden elegir Spring, si así lo desean.

Una vez que agregamos las referencias al proyecto, procedamos a crear dos carpetas. En una vamos a alojar nuestras clases y en otra nuestros archivos de mapeo.

En la carpeta donde guardaremos nuestras clases, voy a generar una clase con el nombre Vehiculo. Esta clase, debe ser Serializable, por lo que debemos marcarla como tal, utilizando el atributo [Serializable]. De esta forma puedo obtener una representacion binaria de la clase que puede ser, por ejemplo escrita en disco. Esto se realiza ya que voy a serializar mi objeto con un archivo xml, que me va a servir como mapeo.

Por último los metodos de mi clase (en mi caso las Properties), tienen que implementar el modificador public virtual, para trabajar con NHibernate, y que este pueda realizar lazy loading.

Este sería un ejemplo de una clase sensilla:


Ahora pasemos a escribir nuestro xml para mapear este objeto.

Al comienzo del archivo estamos indicando el nombre del assembly "NHibernate_1", donde van a estar nuestras clases. Luego el namespace de dicha clase, y comenzamos a tratar las características de la clase que queremos mapear con el atributo class, en este caso Vehiculo. Dentro de los tags <class name="Vehiculo"...> ... </class> vamos a poner todas las configuraciones de dicha clase. Un atributo que no hemos marcado en el mapping pero que viene activado por defecto y vale la pena explicarlo es lazy="true". Con esto activamos el lazy load, de modo que las colecciones no se cargarán hasta que sean utilizadas. Este funcionamiento se logra mediante un proxy y NH se encarga de manejarlo, para nosotros es transparente (por ahora).

Con el tag <id> vamos a indicarle a NH qué property se mapeará con la clave primaria de la tabla, en este caso la property id se va a corresponder con la columna IdVehiculo, será de tipo int y para indicar que un objeto se considera como nuevo y no como que ya existe en la base, pondremos el atributo unsaved-value="0". Tambien hemos asignado al generator el valor identity, con esto le decimos a Hibernate que se encargue de generar los valores de la primary key. Si hubiéramos querido asignarles nosotros el valor, deberíamos reemplazar por el valor assigned. Luego le vamos a decir que la propiedad cantPuertas tiene que corresponderse con el atributo cantPuertas, y lo mismo para cantRuedas.

Con esto ya tenemos nuestro archivo para mapear nuestro objeto.

Lo que debemos hacer ahora es escribir nuestro archivo de configuración para que Hibernate pueda operar con nuestra base de datos. En este ejemplo voy a utilizar SQL Server 2005, desde ya que pueden utilizar cualquier otro motor, como Oracle, MySql, etc.


No hay mucho que explicar, copienlo tal cual, y en todo caso lo único que deberían modificar es el connection_string, para indicar los datos de acceso hacia su base de datos. Este archivo de configuración viene con la versión de NHibernate, por lo que con copiarlo, agregarlo a su proyecto y por último hacer las modificaciones necesarias es suficiente.

Muy bien, ahora continuemos con la instanciación de nuestro objeto, y la persistencia del mismo dentro de nuestra BD.


Como primer paso instanciamos un objeto de la clase Configuration, y le pasamos al metodo Configure, el nombre de nuestro archivo de configuración. En este caso es hibernate.cfg.xml.

Como podran observar creé un metodo que se llama CrearTablas, y que recibe la configuracion que instanciamos. Esto es basicamente, para no tener que ir a crear las tablas a mano a la base de datos, y que lo haga Hibernate por si mismo. Como ustedes quieran.

Continuamos instanciando un objeto de la clase ISessionFactory. Una sesión es un marco de trabajo en el cual NHibernate establece una conversación entre la aplicación y el motor de base de datos relacional. Para construir una sesión, representado por ISession alguien que nos provea la sesión, para esto necesitamos de: ISessionFactory. El ISessionFactory se encarga de crear sesiones en nuestra aplicación. En un momento, la aplicación puede tener 1 o más sesiones abiertas. Cada sesión representa un 1er. nivel de caché, los objetos que son traídos desde la base, o son guardados desde la aplicación se encuentran en la cache de primer nivel. Si se solicita un objeto a la base, primero se busca en la caché, si se encuentra ahí el objeto, se lo devuelve a la aplicación sin solicitarlo al motor. Sino se encuentra en la caché, se realiza la consulta a la base.
Por lo general debemos tener un ISessionFactory por aplicación, sería necesario tener más de uno en el caso de que estemos trabajando con más de una base de datos a la vez.

Instanciamos un objeto de nuestra clase Vehiculo. Le asignamos los valores que querramos, e invocamos al método Save() de nuestro objeto sesion, y le enviamos por parametros el objeto vh1, que es la instancia de nuestra clase Vehiculo. Mediante este metodo persistiremos nuestro objeto en nuestra base de datos.

Si queremos podemos ejecutar nuestro programa para ver los resultados.


Si observamos desde nuestro Motor.



Espero que les haya sido util y que puedan seguir investigando al respecto.

sábado, 18 de diciembre de 2010

Tablas PL/SQL [Parte 2]

Continuando con la temática del posteo anterior, les voy a dejar lo que resta para cerrar el tema de nested table.


Como he mencionado en la Parte 1, una tabla anidada se puede almacenar como una columna de una tabla. Para esto hay que definir la tabla anidada con la orden CREATE TYPE para crear el tipo de la tabla anidada en lugar de TYPE.

Ejemplo:

CREATE TYPE inf_libro AS OBJECT(titulo varchar2(40),
                                nombre_autor varchar2(40),
                                isbn number);

CREATE TYPE isbn_libros AS TABLE OF inf_libro;

CREATE TABLE prestamo(fecha_entrega date,
                      nro_socio number(10),
                      libros_prestados isbn_libros)
NESTED TABLE libros_prestados STORE AS prestados_tabla;

Una tabla anidada almacenada en una tabla de la base de datos se puede manipular en su integridad o pueden manipularse sus filas individuales. Cualquiera de los dos casos, se pueden utilizar órdenes SQL. El índice de la tabla anidada sólo puede utilizarse cuando la tabla está en PL/SQL.

Para insertar una fila en una tabla anidada se utiliza la orden INSERT. Hay que tener en cuenta que la tabla se crea e inicializa en PL/SQL y después se inserta en la base de datos.

Ejemplo:

DECLARE
    libros isbn_libros := isbn_libros(inf_libro('Libro 1', 'Autor1',1234));
BEGIN
    INSERT INTO prestamo VALUES (sysdate, 12, isbn_libros(inf_libro('Libro 2', 'Autor 2',1234),
                                                               inf_libro('Libro 3','Autor 3',3456)));
    INSERT INTO prestamo VALUES (sysdate, 24, libros);
END;
De forma similar, se utiliza UPDATE para modificar la tabla almacenada
Ejemplo:

DECLARE
    libros isbn_libros := isbn_libros(inf_libro('Libro 1',
                                                'Autor 1',
                                                1234567),
                                      inf_libro('Libro 2',
                                                'Autor 2',
                                                 1234));
BEGIN
    UPDATE prestamo
    SET libros_prestados = libros
    WHERE nro_socio = 24;
END;
/

Con DELETE puede eliminar una fila que contenga una tabla anidada.

Ejemplo:

BEGIN
    DELETE FROM prestamo WHERE nro_socio = 24;
END;
/

Cuando se recupera una tabla anidada en una variable PL/SQL, se asignan claves comenzando por 1 hasta llegar al número de elementos que contiene la tabla. Este valor puede determinarse mediante el método COUNT, el cual devuelve un número entero correspondiente al número de elementos que tiene actualmente la colección.

Ejemplo:

DECLARE
    libros isbn_libros;
    i NUMBER;
BEGIN
    SELECT libros_prestados INTO libros FROM prestamo WHERE nro_socio=12;
    FOR i IN 1 .. libros.count LOOP
        DBMS_OUTPUT.PUT_LINE('Título: ' || libros(i).titulo|| ' del elemento: '|| i);
    END LOOP;
END;
/

jueves, 16 de diciembre de 2010

Tablas PL/SQL [Parte 1]

Para aquellos que programan en algún lenguaje (lo que estoy diciendo es una obviedad pero es solo por dar un ejemplo didáctico), apuesto a que trabajaron con vectores, también llamados arrays, matrices, n-uplas, etc.  Que no son más que sectores de almacenamiento contiguo para datos de un mismo tipo.
Seguramente se habrán dado cuenta, que no es más que un concepto adoptado del análisis vectorial. Para los que no tuvieron “el placer” de toparse con materias como Análisis Matemático, Álgebra y Geometría Analítica, Física, etc. les comento que el concepto de vector, es puramente Matemático.
Así como estos casos tenemos unos cuantos, y ya que estamos hablando de Bases de Datos, tenemos el claro ejemplo del álgebra de conjuntos. Les aclaro que yo era uno de los que, durante las clases de Matemática, hacia la típica pregunta “Y esto que aplicación tiene??”. Era obvio que no iba a obtener ninguna respuesta de un profesor que se dedicaba a enseñar Matemática en la carrera de Ingeniería. Así que con el tiempo me fui dando cuenta solo (Si bien nunca me pidieron que desarrolle un programa para calcular el flujo  a través de un campo eléctrico con el teorema de Gauss, y que no me pondría a hacer salvo que me presenten un maletín con 200 millones de dólares).
Llendo a la temática de este post, los vectores, así como los podrán utilizar en C, C++, Pascal, etc. También existen en el lenguaje de programación que utiliza Oracle, que es el PL/SQL.
Para comenzar nos vamos a remontar a las tablas PL/SQL. Estas tablas son básicamente un array, y tienen dos componentes: una clave primaria del tipo BINARY_INTEGER para indexarla y otra columna que permite guardar los escalares o registros. Cabe destacar que estas estructuras son dinámicas.
Para manejarlas, lo primero que tenemos que hacer es declarar el tipo de dato.

TYPE nombre_tipo_tabla IS TABLE OF
{tipo_columna | variable%TYPE | tabla.columna%TYPE}[NOT NULL]
INDEX BY BINARY_INTEGER ;

Lo que acabamos de declarar es un tipo tabla, que va a ser el tipo de nuestra variable. Por lo que luego, nuestra declaración no varía en nada a la declaración formal de cualquier variable en PL/SQL:

nombre_var nombre_tipo_tabla;

Si queremos referenciar a un elemento de la tabla, sería como si de un vector se tratase:

Pl/sql_nombre_tabla(valor_primary_key);

El rango de binary integer es –2147483647.. 2147483647, por lo tanto el índice puede ser negativo, lo cual indica que el índice del primer valor no tiene que ser necesariamente el uno.


También es posible declarar los famosos “Tipo Registro”, conocidos como struct en C, como record en Pascal. Y este tipo registro puede ser tranquilamente el tipo de dato de nuestra tabla PL/SQL. Y el concepto es prácticamente el mismo que en los lenguajes de programación mencionados, o sea, tendría mi tipo vector, cuyo tipo de dato seria de un tipo de registro que haya definido. Por lo que en cada posición del vector voy a poder guardar datos de ese tipo registro. Qué ventaja me da esto, que con las posibilidades que me ofrecen los tipos record, podría guardar varios datos, de distinto tipo dentro de las posiciones de un vector. Análogamente para PL/SQL.
Con la llegada de Oracle 8 PL/SQL añade dos tipos de colecciones, las tablas anidadas y los VARRAY. Cada uno con sus atributos y métodos (muy similar a lo que es un objeto en cualquier lenguaje de programación orientada a objetos).
En sí, las tablas anidadas son muy similares a las tablas PLSQL que acabamos de ver. Con la diferencia de que estas incluyen métodos de colección adicionales, que las tablas PL/SQL no tenían, ni tienen. Por otro lado también se abre la posibilidad de guardar una tabla anidad, dentro de una tabla en la base de datos. Digamos, puedo tener una tabla, que posea un campo cuyo dominio sea el mismo que mi tipo tabla, y dentro de ella tranquilamente podría persistir ese dato.
Su declaración es muy simple:
TYPE nombre_tabla IS TABLE OF tipo_tabla [NOT NULL];
Como podrán observar desaparece la clausula para la indexación, que tenían las anteriores, que es BINARY_INTEGER.
Una vez que creamos el tipo, la declaración de nuestra variable es análoga a cualquier otra declaración. Y por supuesto que en la declaración de nuestra variable, podemos inicializarla, como si se tratase de una variable “varchar”, por mencionar un ejemplo, con la diferencia de que para el caso de las nested tables, su inicialización se realiza a través del constructor de la misma.
Un simple ejemplo seria:
DECLARE
    TYPE tabla_numero IS TABLE OF NUMBER;
    var_tabla_1 tabla_numero := tabla_numero(0);
    var_tabla_2 tabla_numero := tabla_numero(1, 2, 3, 4);
    var_tabla_3 tabla_numero := tabla_numero();
    var_tabla_4 tabla_numero;
BEGIN
    var_tabla_1(1):= 123;
    DBMS_OUTPUT.PUT_LINE('Valor 1 de var_1: ' || var_tabla_1(1));
    DBMS_OUTPUT.PUT_LINE('Valor 1 de var_2: ' || var_tabla_2(1));
    IF var_tabla_3 IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('var_tabla_3 SI es NULL');
    ELSE
        DBMS_OUTPUT.PUT_LINE('var_tabla_3 NO es NULL');
    END IF;
    IF var_tabla_4 IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('var_tabla_4 SI es NULL');
    ELSE
        DBMS_OUTPUT.PUT_LINE('var_tabla_4 NO es NULL');
    END IF;
END;
/
Por supuesto que la inicialización no es obligatoria, y si la obviamos tendríamos una tabla sin elementos.
Cuando se inicializa una tabla utilizando el constructor, los elementos de la tabla se numeran secuencialmente desde 1 hasta el número de elementos especificado en la llamada al constructor. En caso de borrado en una tabla anidada de la base de datos, las claves se reenumeran para seguir siendo secuenciales.

Un ejemplo:

DECLARE
    TYPE tabla_numero IS TABLE OF NUMBER;
    var_tabla_2 tabla_numero := tabla_numero(10, 20, 30, 40);
BEGIN
    DBMS_OUTPUT.PUT_LINE('Nro elem de var_tabla_2: ' || var_tabla_2.count);
    DBMS_OUTPUT.PUT_LINE('Primer elem: ' || var_tabla_2.first);
    DBMS_OUTPUT.PUT_LINE('Último elem: ' || var_tabla_2.last);
END;
/

La salida será:
Nro elem de var_tabla_2: 4
Primer elem: 1
Último elem: 4
Aunque las tablas no tienen un tamaño fijo, no se puede asignar un valor a un elemento que todavía no existe. Para poder añadir elementos hay que utilizar el método EXTEND (que ya lo veremos en otro momento).

Ejemplo:

DECLARE
    TYPE tabla_numero IS TABLE OF NUMBER;
    var_tabla_2 tabla_numero := tabla_numero(10, 20, 30, 40);
BEGIN
    var_tabla_2 (1) := 50;
    DBMS_OUTPUT.PUT_LINE('Primer elemento de var_tabla_2: ' || var_tabla_2(1) );
END;
/



La salida es:
Primer elemento de var_tabla_2: 50
Procedimiento PL/SQL terminado con éxito.

Pero si pongo:

DECLARE
    TYPE tabla_numero IS TABLE OF NUMBER;
    var_tabla_2 tabla_numero := tabla_numero(10, 20, 30, 40);
BEGIN
    var_tabla_2 (1) := 50;
    DBMS_OUTPUT.PUT_LINE('Primer elemento de var_tabla_2: ' || var_tabla_2(1) );
    Var_tabla_2 (5) := 60;
    DBMS_OUTPUT.PUT_LINE('Quinto elemento de var_tabla_2: ' || var_tabla_2(5));
END;
/

Salida:
Primer elemento de var_tabla_2: 50
DECLARE
*
ERROR en línea 1:
ORA-06533: Subíndice mayor que el recuento
ORA-06512: en línea 7
Para la próxima parte, concluiremos con este tema hablando sobre algunos aspectos adicionales pero no menos importantes sobre las nested tables.