introduccion a nhibernate (1)

36
Introducción a NHibernate Francisco Daines O. 20/01/2007

Upload: isabel-martinez

Post on 25-Jul-2015

118 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Introduccion a NHibernate (1)

Introducción a NHibernateFrancisco Daines O.

20/01/2007

Page 2: Introduccion a NHibernate (1)

1.- Que es NHibernate y para qué sirve.........................................................................3

2.- Instalación de NHibernate.........................................................................................6

3.- Configuración de NHibernate...................................................................................7

4.- Ejemplos sencillos de mapeo OR..............................................................................9

4.1 Mapeo de clases sencillo.......................................................................................114.2 Mapeo con herencia...............................................................................................124.3 Mapeo de composiciones......................................................................................164.4 Mapeo de relaciones uno a muchos.......................................................................204.5 Mapeo de relaciones muchos a muchos................................................................24

5.- Manipulación de datos persistentes........................................................................26

5.1 Recuperando un objeto de la base de datos...........................................................265.2 Realizando consultas simples................................................................................27

Introducción a NHibernate 2

Page 3: Introduccion a NHibernate (1)

1.- Que es NHibernate y para qué sirve.

El desarrollo de aplicaciones utilizando el paradigma OO es cada vez más adoptado por empresas de desarrollo de software, ya que provee una gran cantidad de ventajas que otros paradigmas de desarrollo no permiten de forma sencilla. De manera análoga, nanie puede negar el dominio absoluto del modelo relacional en el mundo de las bases de datos.

El problema angular es que al utilizar OO con BDR se pierde gran parte de la abstracción que provee la POO. Claramente el impacto puede variar dependiendo de la estrategia que se utilice para comunicar nuestras clases con las tablas de un modelo relacional. En el peor de los casos, cada clase del sistema se comunicará con la BDR, creando una dependencia directa entre el modelo OO y el modelo relacional. Esto significa que si realizo un simple cambio, como cambiar el nombre a una tabla, esto afecta directamente el código escrito en una gran cantidad de líneas de, quizás, una gran cantidad de clases. Por lo que la pérdida de tiempo es notable.

Existen alternativas al modelo relacional, son las emergentes Bases de Datos Orientadas a Objetos, las cuales trabajan directamente con objetos y en muchos casos proveen accesos para una gran cantidad de lenguajes de programación tales como C++, Java y la plataforma MS.NET. Además de ser escasas, la mayoría de estas bases de datos están en versiones de testing, por lo que implementar estas bases de datos significa un riesgo que muchas empresas no están dispuestas a correr.

Hasta hace un tiempo la tarea de comunicar OO con BDR era difícil, luego, con la creación del modelo DAO (Data Access Object) se simplificó el asunto, definiendo que serían sólo algunos objetos los que se comunicarían con la BDR, disminuyendo notoriamente el impacto de realizar cambios en la Base de Datos. Pero todavía no era posible obtener independencia entre el código de las clases y el acceso a la base de datos.

Hace un par de años se liberó la primera versión de un Framework de persistencia llamado NHibernate, una implementación para el Microsoft .Net FrameWork de Hibernate (implementado originalmente sólo para Java). Bueno pero ¿Qué es precisamente NHibernate? y ¿Para qué utilizar NHibernate? son algunas de las preguntas que trataré de responder a continuación.

NHibernate es un Framework de persistencia, es decir provee herramientas que facilitan la tarea de persistir objetos (i.e. almacenar el estado de un objeto con el fin de recuperarlo en el futuro). La motivación principal de NHibernate es abstraer por completo al desarrollador de la base de datos asociada al proyecto en desarrollo, es decir, el desarrollador debe pensar que sólo trabaja con objetos, los cuales puede guardar en una base de datos utilizando métodos de los mismos objetos, pero nunca escribir ni analizar una sentencia SQL en su código.

La arquitectura de NHibernate se puede representar de una manera simple como el siguiente diagrama:

Introducción a NHibernate 3

Page 4: Introduccion a NHibernate (1)

Podemos notar que la aplicación trabajará con objetos persistentes, pero sin comunicarse directamente con la base de datos. En su lugar, la comunicación será con el framework Nhibernate, el cual se compone de una sección de configuración (puede ser archivo App.config o Web.config según nuestro proyecto sea Windows Forms o Web) y un conjunto de mapeos Objeto-Relacionales. Utilizando estos elementos, Nhibernate se comunicará con la base de datos y realizará las acciones requeridas por los objetos persistentes (inserción, actualización, borrado, selección).

Entrando un poco más en detalle, una arquitectura “ligera” de NHibernate es cuando la aplicación proporciona sus propias conexiones ADO.NET y maneja lo que son transacciones, entonces la arquitectura será:

Introducción a NHibernate 4

Page 5: Introduccion a NHibernate (1)

Los objetos persistentes requieren almacenar estados, para esto es necesario utilizar una sesión (canal de comunicación entre la aplicación y la base de datos). La sesión de comunicación será creada por una SessionFactory, que es un caché de los mapeos de una base de datos en particular. La SessionFactory puede ser configurada utilizando código o configurando los archivos App.config o Web.config.

Ahora, si deseamos utilizar todas las características que provee NHibernate, podemos utilizar una arquitectura como la que se muestra a continuación:

En esta arquitectura, NHibernate provee lo que es el control de transacciones (utilizando Transactions creadas por una TransactionFactory) y control de conexiones ADO.NET que no están expuestas a la aplicación, sin embargo pueden ser extendidas o implementadas por los desarrolladores.

En resumen, NHibernate es un Framework que pretende abstraer por completo al desarrollador de lo que es el manejo de persistencia en las aplicaciones. Al utilizar NHibernate el desarrollador sólo trabajará con objetos capaces de almacenar y recuperar estados, con todo lo que ello significa (manejo de relaciones entre objetos, jerarquía de objetos, etc.).

Introducción a NHibernate 5

Page 6: Introduccion a NHibernate (1)

2.- Instalación de NHibernate.

NHibernate es una librería (.dll) que se utiliza con Microsoft Visual Studio .NET. Actualmente la versión 1.0.3 es stable y soporta las características del .Net Framework 1.1 (VS 2003). También existe la versión 1.2.0 beta, desarrollada con el objetivo de brindar soporte a todas las características del .Net Framework 2.0 (VS 2005). Ambas versiones de NHibernate se pueden obtener desde la página oficial del proyecto (http://nhibernate.sourceforge.net).

Existe un paquete de extensiones llamado Nhibernate Contrib disponible para la versión 1.0.2 de NHibernate, que ofrece características extra a las ofrecidas en el paquete original de NHibernate. Hoy en día sólo existe el paquete Contrib para la versión 1.0.2 de NHibernate.

Bueno, una vez obtenido el paquete de NHibernate deseado (viene en un archivo .zip) es necesario descomprimir el archivo en algún directorio del disco duro. Para trabajar con NHibernate desde un proyecto de VS 2003 o 2005 sólo es necesario agregar referencias a las siguientes librerías de NHibernate:

NHibernate.dll

Las siguientes librerías son de utilidad en los proyectos que implementen el framework Nhibernate.

log4net.dll: Permite controlar un log de los eventos ocurridos durante la ejecución de una aplicación. Este log puede almacenarse en un archivo plano o en una base de datos.

Iesi.Collections.dll: Implementa algunas colecciones, como Sets, que no están disponibles en el .NET Framework.

En este momento nuestro proyecto está listo para poder utilizar las

características del Framework NHibernate.

Introducción a NHibernate 6

Page 7: Introduccion a NHibernate (1)

3.- Configuración de NHibernate.

Como bien sabemos, la idea de utilizar NHibernate es facilitar la interacción entre objetos y tablas en un modelo de datos relacional. Si bien, el desarrollador no interactúa directamente con la base de datos, Nhibernate sí lo hace, por lo que es necesario indicar en un archivo de configuración cuál será la base de datos a utilizar y cómo conectarse con dicha base de datos.

Existen varias formas de configurar la comunicación entre NHibernate y la base de datos, sin embargo la más recomendable es utilizar un archivo App.config (configuración de proyecto) ya que permite cambiar la configuración de acceso sin cambiar el código de la aplicación en sí.

Un archivo de configuración de aplicaciones es un archivo XML que permite configurar algunas opciones específicas de la aplicación que desarrollamos. La estructura básica del archivo de configuración App.config es la siguiente.

<?xml version=”1.0” encoding=”utf-8”?><configuration> <configSections> <section name=”nhibernate” type=”System.Configuration.NameValueSectionHandler, System,

Version=”1.0.5000.0,Culture=neutral,PublicKeyToken=b77a5c561934e089”

/> </configSections> <nhibernate> <!-- configuración de nhibernate --> </nhibernate></configuration>

En la zona de configuración es posible agregar las configuraciones específicas para cada aplicación utilizada en la solución actual. En este caso es necesario configurar las opciones de NHibernate para que acceda a la base de datos utilizada. A continuación un ejemplo de la configuración de NHibernate para acceder a una base de datos.

<nhibernate> <add key=”hibernate.connection.provider” value=”Nhibernate.Connection.DriverConnectionProvider”/> <add key=”hibernate.dialect” value=”Nhibernate.Dialect.XXX” /> <add key=”hibernate.connection.driver_class”

value=”Nhibernate.Driver.YYY”/> <add key=”hibernate.connection.connection_string” value=”ZZZ” /></nhibernate>

Donde se debe reemplazar los valores XXX, YYY y ZZZ según sea la base de datos que estemos utilizando para almacenar los datos de nuestra aplicación.

La versión 1.2 de Nhibernate soporta las bases de datos más utilizadas hoy en día. A continuación se listan algunas de las bases de datos soportadas por Nhibernate 1.2 y los valores de configuración de las variables XXX, YYY y ZZZ del ejemplo anterior.

Introducción a NHibernate 7

Page 8: Introduccion a NHibernate (1)

Microsoft SQL Server 2000 XXX: MsSql2000Dialect YYY: SqlClientDriver ZZZ: “Server=dbServer;Initial catalog=db;Integrated Security=SSPI”

Microsoft SQL Server 2005 XXX: MsSql2005Dialect YYY: SqlClientDriver ZZZ: “Server=Server;Database=EjemplosNHibernate;User

Id=usuario;Password=pwd”

MySQL XXX: MySQLDialect o MySQL5Dialect (MySQL 5) YYY: MySqlDataDriver ZZZ: “Server=server;Database=database;User ID=user; Password=password”

Oracle XXX: OracleDialect / Oracle9Dialect YYY: OracleClientDriver ZZZ: “Data Source=fuente;User Id=user;Password=pwd;”

PostgreSQL XXX: PostgreSQLDialect YYY: NpgslDriver ZZZ: “Server=Server;Database=db;User Id=user;Password=pwd;

Encoding=UNICODE”

Teniendo la configuración del archivo App.config definida, sólo nos queda obtener el driver necesario para la conexión y agregar una referencia a él en nuestro proyecto.

SQL Server 2000-2005: System.Data.SqlClient en System.Data.dll (VS2005). MySQL: http://dev.mysql.com/downloads/connector/net/1.0.html Oracle: System.Data.OracleClient.dll (en VS2005). PostgreSQL: http://pgfoundry.org/projects/npgsql/

Introducción a NHibernate 8

Page 9: Introduccion a NHibernate (1)

4.- Ejemplos sencillos de mapeo OR.

NHibernate permite la implementación de varios modelos de diseño para la persistencia de objetos, por tanto se puede optar por utilizar objetos DAO, patrón AbstractFactory, entre otros.

Se recomienda crear una clase que controle la creación de sesiones y acceso a la base de datos, luego, según sea el modelo de objetos que utilicemos, llamaremos a esta clase desde los objetos Save(), Update(), etc. de cada objeto que maneje persistencia en la base de datos.

Es recomendable construir una clase manager de la conexión a la base de datos, esta clase manejará la creación de sesiones para poder realizar las operaciones de búsqueda, inserción, actualización y eliminación.

A continuación se presenta un ejemplo del código de la clase NHibernateManager que utilizaremos en los ejemplos de esta sección.

public class NHibernateManager(){

private static IsessionFactory nhFactory;private static Configuration nhConfig;

static void NHibernateManager(){

nhConfig = new Configuration();nhConfig.AddAssembly(“NombreAssembly”);nhFactory = nhConfig.BuildSessionFactory();

}

public static Isession NHSession {

get { return nhFactory.OpenSession(); }}

public static void CloseSessionFactory(){

If ( nhFactory != null )nhFactory.Close();

}}

Esta clase nos permitirá crear una nueva conexión a la base de datos y asociar una sesión a dicha conexión.

Antes de ver los ejemplos básicos de almacenamiento de objetos persistentes y su correspondiente mapeo objeto-relacional vamos a necesitar implementar el método Save en cada objeto persistente. El contenido de este método se muestra a continuación.

Introducción a NHibernate 9

Page 10: Introduccion a NHibernate (1)

public void Save(){

NhibernateManager nhm = new NHibernateManager();

ISession session = NHibernateManager.NHSession;

session.Save(this);session.Close();

NHibernateManager.CloseSessionFactory();}

Existe la posibilidad de manejar el acceso a la base de datos en base a transacciones, es decir los cambios en la base de datos se realizan de manera atómica controlados por una transacción. En caso de suceder alguna anomalía durante una transacción abierta, se puede indicar a la aplicación que aborte la transacción y esto anulará cualquier posible cambio en la base de datos. Para utilizar transacciones en el método Save de los objetos persistentes se puede utilizar el siguiente código de ejemplo.

public void Save(){

NhibernateManager nhm = new NHibernateManager();ISession session = NHibernateManager.NHSession;ITransaction tx = session.Transaction;

tx.Begin();

try {session.Save(this);tx.Commit();

} catch {tx.RollBack();

}

session.Close();NHibernateManager.CloseSessionFactory();

}

Otro requerimiento para el mapeo Objeto-Relacional es el poseer un archivo de mapeo, este archivo está en formato xml y su nombre por convención es clasePersistente.hbm.xml. Todo archivo de mapeo debe contener como nodo raíz lo siguiente:

<hibernate-mapping xmlns=”urn:nhibernate-mapping-2.2”namespace=”miNamespace” assembly=”miAssembly”default-lazy=”false” ><class . . . >

<!-- mapeo de la clase --> </class>

Introducción a NHibernate 10

Page 11: Introduccion a NHibernate (1)

</hibernate-mapping>

Donde se hace referencia al assembly donde estará contenida la clase mapeada y su archivo de mapeo. Además se referencia el namespace de la clase mapeada. NHibernate 1.2 considera por defecto la opción default-lazy=”true”, lo que requiere que todas las propiedades públicas de las clases persistentes sean virtuales, si queremos evitar esta opción seteamos el atributo a false.

4.1 Mapeo de clases sencillo.

El primer ejemplo que veremos será crear una clase persistente simple, es decir, esta clase no tendrá relaciones de ningún tipo (asociación, agregación, etc.) con otra clase.

Nuestra clase de ejemplo seguirá siendo Cat, la cual ya posee el método Save definido recientemente. Si nuestra clase Cat está definida como sigue:

public class Cat{

private string id;private string nombre;

public string Id{

get { return id; }set { id = value; }

}

public string Nombre{

get { return nombre; }set { nombre = value; }

}

public void Save(){

/* implementacion */}

}

Donde el identificador del gato será un string de tipo hash (32 caracteres), entonces nuestra tabla SQL debe estar definida como sigue:

CREATE TABLE gato(idCat varchar(32) NOT NULL,name varchar(30) NOT NULL

);

Introducción a NHibernate 11

Page 12: Introduccion a NHibernate (1)

Teniendo ambas partes del sistema, es decir, la clase persistente y la tabla sql correspondiente a dicha clase, ahora debemos realizar el mapeo de atributos de la clase a columnas de la tabla. Para esto debemos crear un archivo llamado Cat.hbm.xml, que será el archivo por el cual NHibernate pregunte cuando se requiera guardar un nuevo objeto Cat en la base de datos.

Un ejemplo del archivo Cat.hbm.xml para mapear la clase Cat a la base de datos es el que se muestra a continuación:

<class name="Cat" table="gato"> <id name=”Id”>

<column name=”idCat” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id> <property name="Nombre" column="name"/></class>

Teniendo todo esto listo, guardar un objeto Cat en la base de datos es algo tan simple como:

Cat tom = new Cat();tom.Nombre = “Tom”;tom.Save();

4.2 Mapeo con herencia.

NHibernate permite 3 estrategias para manejo de persistencia de jerarquías de clases. Las estrategias permitidas son:

table-per-class-hierarchy table-per-subclass table-per-concrete-class (polimorfismo implícito)

Para efectos de este documento sólo se verán las estrategias table-per-class-hierarchy y table-per-subclass.

Supongamos que tenemos el siguiente ejemplo. Una empresa necesita manejar la información personal tanto de sus empleados como de sus clientes, por lo tanto genera un modelo jerárquico, donde existe una clase Persona que contiene los datos personales y subclases Empleado y Cliente que contienen los detalles requeridos para empleados y clientes.

La definición de clases se muestra a continuación.

public class Persona{

private string id;private string nombre;private int edad;

Introducción a NHibernate 12

Page 13: Introduccion a NHibernate (1)

public string Id{

get { return id; }set { id = value;}

}

public string Nombre{

get { return nombre; }set { nombre = value; }

}

public int Edad{

get { return edad; }set { edad = value; }

}

Public void Save(){

/* implementacion */}

}

public class Empleado : Persona{

private int sueldo;public int Sueldo{

get { return sueldo; }set { sueldo = value; }

}}

public class Cliente : Persona{

private string tipoCliente;public string TipoCliente{

get { return tipoCliente; }set { tipoCliente = value; }

}}

La primera estrategia es llamada table-per-class-hierarchy, es decir, en el modelo de datos se tiene sólo una tabla por jerarquía de datos, lo que significa que en la base de datos sólo habrá una tabla llamada, por ejemplo, tblPersona. Esta tabla contendrá los campos necesarios para almacenar a la clase persona y todos los campos necesarios para mapear los atributos de toda las subclases de persona, en este caso se requiere que tblPersona agregue los campos sueldo y tipoCliente a su definición. Un campo extra es requerido para manejar polimorfismo, este campo se conocerá como

Introducción a NHibernate 13

Page 14: Introduccion a NHibernate (1)

discriminador e indicará a NHibernate a qué clase corresponde una fila particular de la tabla tblPersona.

Un ejemplo de la definición de la tabla tblPersona para la estrategia table-per-class-hierarchy será:

CREATE TABLE tblPersona(idPersona varchar(32) NOT NULL,name varchar(30) NOT NULL,edad int,TipoPersona varchar(255),sueldo int,tipocliente varchar(30)

);

En este caso el discriminador será la columna TipoPersona y será de tipo string que caracterice a la clase a la cual pertenece la fila correspondiente en la tabla. El discriminador puede ser de cualquier tipo NHibernate.

<class name="Persona" table="tblPersona" discriminator-value=”PERSONA”>

<id name=”Id”><column name=”idPersona” sql-type=”char(32)”

not-null=”true”/><generator class=”uuid.hex”/>

</id> <discriminator column="TipoPersona" type="String"/> <property name="Nombre" column="name"/> <property name="Edad" column="edad"/>

<subclass name="Cliente" discriminator-value="CLIENTE"> <property name="TipoCliente" column="tipocliente"/> </subclass>

<subclass name="Empleado" discriminator-value="EMPL"> < property name="Sueldo" column="sueldo"/> </subclass></class>

La segunda estrategia es llamada table-per-subclass que, tal como lo indica, utiliza una tabla por cada clase de la jerarquía. En este caso tendremos las tablas Persona, Cliente y Empleado, las cuales se definen a continuación.

CREATE TABLE Persona(idPersona varchar(32) NOT NULL,name varchar(30) NOT NULL,edad int

);

CREATE TABLE Cliente(

Introducción a NHibernate 14

Page 15: Introduccion a NHibernate (1)

personaId varchar(32) NOT NULL,tipocliente varchar(30)

);

CREATE TABLE Empleado(personaId varchar(32) NOT NULL,sueldo int

);

Para manejar la relación de jerarquía entre las clases, es necesario agregar un campo a las tablas Cliente y Empleado que será una referencia a la fila en la tabla Persona donde estarán sus datos personales. Este campo extra puede ser una clave foránea en la base de datos, pero eso es tarea del DBA, por lo que no se detallará sobre ese tema en esta sección.

Ahora, el mapeo de las clases pertenecientes a esta jerarquía se muestra a continuación:

<class name="Persona" table="Persona"> <id name=”Id”>

<column name=”idPersona” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id> <property name="Nombre" column="name"/> <property name="Edad" column="edad"/>

<joined-subclass name="Cliente" table="tblCliente"> <key column="PersonaId"/> <property name="TipoCliente" column="tipocliente"/> </joined-subclass> <joined-subclass name="Empleado" table="tblEmpleado"> <key column="PersonaId"/> <property name="Sueldo" column="sueldo"/> </joined-subclass></class>

Cabe señalar que, por ejemplo, si Persona fuese una clase abstracta, ambas estrategias no sufren cambios, el único cambio será que no podremos instanciar un objeto de tipo Persona, pero esta es una restricción de la OO. En cambio, si optamos por la estrategia table-per-concrete-class tendremos que sólo realizaremos mapeos de las clases concretas (no abstractas).

Realizado el mapeo OR de la jerarquía Persona, utilizar las clases persistentes es tan simple como:

Persona p = new Persona();p.Nombre = “Rosa”;p.Edad = 35;p.Save();

Introducción a NHibernate 15

Page 16: Introduccion a NHibernate (1)

Empleado e = new Empleado();e.Nombre = “Paulina Mondaca”;e.Edad = 24;e.Sueldo = 120000;((Persona)e).Save();

Empleado e = new Empleado();c.Nombre = “Jorge Garay”;c.Edad = 34;c.TipoCliente = “basico”;((Persona)c).Save();

4.3 Mapeo de composiciones.

Una composición corresponde a una relación uno-a-uno entre dos clases donde al eliminar la instancia de la clase contenedora se elimina la instancia de la clase compuesta. La equivalencia en modelo relacional de esta relación entre clases se ve materializada como una agregación de columnas en la tabla principal de los atributos de la clase contenida. A continuación se presenta un ejemplo de una composición de clases.

Es posible representar esta composición en una tabla SQL agregando los atributos de la clase Dirección a la tabla que corresponde a la clase Persona. Haciendo esto, nuestra tabla tblPersona quedaría de la siguiente manera:

Introducción a NHibernate 16

Page 17: Introduccion a NHibernate (1)

Lo que pretende este mapeo es que si una clase contiene a otra, entonces la tabla SQL debe contener un campo de un tipo de dato complejo, lo que se representa como un conjunto de columnas que en realidad corresponden a una misma columna, en este caso las columnas DIR_* corresponden a un tipo de datos complejo (tipo de datos Direccion) y deben tratarse como un todo. Si bien se pierde la relación de equivalencia directa entre modelos, este mapeo tiene sentido ya que al eliminar una instancia de Persona efectivamente se eliminará la instancia Dirección asociada a dicha Persona.

Ahora veremos como llevar a la práctica el mapeo del ejemplo recién expuesto. Para este caso primero debemos crear una clase Dirección, la cual se define a continuación.

public class Direccion{

private string region;private string ciudad;private string calle;private int numero;

public string Region{

get { return region; }set { region = value;}

}

public string Ciudad{

get { return ciudad; }set { ciudad = value;}

}

public string Calle{

get { return calle; }set { calle = value;}

}

public int Numero{

get { return numero; }set { numero = value;}

}}

Para representar una composición de Dirección en la clase Persona, debemos agregar un atributo a Persona que sea de tipo Dirección como se muestra a continuación.

public class Persona

Introducción a NHibernate 17

Page 18: Introduccion a NHibernate (1)

{private string id;private string rut;private string nombre;private string apellido;private int edad;private char sexo;private Direccion dir;

/* Implementacion de propiedades públicas */

}

Teniendo estas definiciones para ambas clases ahora sólo falta configurar correctamente el archivo Persona.hbm.xml para mapear la clase Persona y su relación con la clase Dirección. Para indicarle a NHibernate que esta es una relación de Composición el contenido de Persona.hbm.xml debe ser similar al siguiente:

<class name="Persona" table="tblPersona"> <id name=”Id”>

<column name=”PersId” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id> <property name="Rut" column="rut"/> <property name="Nombre" column="nombre"/> <property name="Apellido" column="apellido"/> ...

<component name=”dir” class=”Direccion” ><property name=”Region” column=”DIR_REGION”/><property name=”Ciudad” column=”DIR_CIUDAD”/><property name=”Calle” column=”DIR_CALLE”/><property name=”Numero” column=”DIR_NUMERO”/>

</component></class>

Un ejemplo de cómo crear una nueva persona es el siguiente:

Persona p = new Persona();p.Rut = “15000000-0”;p.Nombre = “Nombre Persona”;p.Apellido = “Apellido”;p.Edad = 41;p.Sexo = “M”;p.Dir = new Direccion();p.Dir.Region = “Metropolitana”;p.Dir.Ciudad = “Santiago”;p.Dir.Calle = “Agustinas”;p.Dir.Numero = 123;

Introducción a NHibernate 18

Page 19: Introduccion a NHibernate (1)

. . .// guardar objeto p.p.Save();

4.3.1 Agregación.

También es posible encontrarse con que una clase A contiene un objeto de una clase B, pero si elimino un objeto de la clase A no se debe borrar el objeto de la clase B asociado al objeto eliminado. Esta relación en OO se conoce como Agregación.

El siguiente ejemplo representa la relación entre Persona y Dirección, ahora desde el punto de vista de agregación

Es posible implementar una agregación de dos maneras, dependiendo de las propiedades de la agregación que se requieran.

Una relación 1 a 1 entre las clases: En este caso el objeto “agregado” sólo estará ligado al objeto contenedor, pero al borrar el objeto contenedor no es necesario borrar el objeto agregado.

Una relación n a 1 entre las clases: En este caso el objeto agregado podrá estar ligado a cero o más objetos contenedores. También es posible determinar si al eliminar el objeto agregado se debe eliminar todo objeto que lo contenga (eliminación en cascada).

Dadas las mismas definiciones de clases vistas para el caso de composición, entonces necesitamos crear los archivos Persona.hbm.xml y Direccion.hbm.xml. El contenido del archivo de mapeo de Direccion debe ser un mapeo de una clase simple, es decir:

<class name="Direccion" table="tblDireccion"> <id name=”IdDireccion”>

<column name=”IdDireccion” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id>

<property name=”Region” column=”region”/><property name=”Ciudad” column=”ciudad”/><property name=”Calle” column=”calle”/><property name=”Numero” column=”numero”/>

</class>

Introducción a NHibernate 19

Page 20: Introduccion a NHibernate (1)

Para el caso del mapeo de Persona, en la tabla SQL debemos agregar un campo que sea del mismo tipo que el campo IdDireccion de la tabla tblDireccion, que manejará la relación entre las tablas (a nivel lógico). Debemos agregar una directiva que indique la relación que queramos construir entre Persona y Dirección. Para esto mapeamos todos los atributos de manera simple (salvo el atributo dirección) y agregamos el mapeo de Dirección dependiendo del tipo de relación que queramos construir:

Relación 1 a 1: Agregamos.<one-to-one name="Dir" class="Direccion"

column="IdDireccion" />

Relación n a 1: Agregamos.<many-to-one name="Dir" class="Direccion"

column="IdDireccion" />

4.4 Mapeo de relaciones uno a muchos.

El siguiente es un ejemplo de una clase que contiene una colección de objetos de otra clase y la relación es uno a muchos. Un Jefe de Proyecto puede manejar uno o más proyectos a la vez, y un proyecto debe estar asociado a un Jefe de Proyecto.

A continuación se expone un ejemplo del código asociado al modelo de clases:

public class Proyecto{

private string idProyecto;private string descripcion;

public string IdProyecto{

get { return idProyecto; }set { idProyecto = value; }

}

public string Descripcion{

get { return descripcion; }set { descripcion = value; }

}}

Introducción a NHibernate 20

Page 21: Introduccion a NHibernate (1)

Imports Iesi.Collections;

public class JefeProyecto{

private string idJefeProyecto;private ISet proyectos; //requiere Iesi.Collections

public string IdJefeProyecto{

get { return idProyecto; }set { idProyecto = value; }

}

public ISet Proyectos{

get { return proyectos; }set { proyectos = value; }

}}

Las colecciones pueden ser implementadas como Listas, Arreglos, Diccionarios o cualquier clase que implemente la interfaz System.Collections.IDictionary, Systems.Collections.IList o Iesi.Collections.ISet. Esta última se encuentra en la librería Iesi.Collections, que se distribuye junto con NHibernate, por lo tanto es necesario agregar la referencia correspondiente a nuestro proyecto.

Existe un detalle al implementar una colección en una clase y es que en la propiedad la colección debe definirse del tipo de la interfaz correspondiente a la clase de colección, es decir una propiedad puede ser de tipo IList, IDictionary o ISet pero no, por ejemplo, de tipo Set.

Teniendo idea de las posibles definiciones en el código de la clase para los distintos tipos de colecciones ahora es necesario conocer cómo realizar el mapeo entre nuestra clase con colección y nuestra base de datos. Para esto, primero es necesario modelar la base de datos, pero esto no es tarea difícil. En el caso que estudiamos, una relación uno a muchos puede ser representada por una tabla tblJefeProyecto que contenga el id del jefe de proyecto y varias tablas tblProyecto que hagan referencia al id del jefe del proyecto como clave foránea. En pocas palabras un esquema relacional puede ser el siguiente:

Luego, el mapeo de la clase Proyecto se realiza de manera simple, como se muestra en el siguiente código.

<class name="Proyecto" table="tblProyecto"> <id name=”IdProyecto”>

<column name=”IdProyecto” sql-type=”char(32)”

Introducción a NHibernate 21

Page 22: Introduccion a NHibernate (1)

not-null=”true”/> <generator class=”uuid.hex”/>

</id><property name=”Descripcion” column=”descripcion”/>

</class>

Por otro lado, el mapeo de la clase JefeProyecto requiere de una etiqueta extra que haga referencia a la relación entre un jefe de proyecto con varios proyectos. Esta etiqueta dependerá del tipo de colección que se haya implementado. A continuación el mapeo para la clase JefeProyecto que implementa una colección de tipo ISet.

<class name="JefeProyecto" table="tblJefeProyecto"> <id name=”IdJefeProyecto”>

<column name=”IdJefeProyecto” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id>

<set name="Proyectos"> <key column="idJefeProyecto" /> <one-to-many class="Proyecto" />

</set></class>

Lo que se indica en la etiqueta <set> es el nombre de la propiedad de la colección de proyectos. Luego se indica que la relación entre proyectos y jefe de proyecto se hará mediante la columna idJefeProyecto de la tabla asociada a la clase Proyecto.

Con esta configuración es posible crear proyectos y asociarlos a un jefe de proyecto en particular. Un ejemplo de cómo realizar esta acción es el siguiente;

JefeProyecto jp = new JefeProyecto();Jp.Proyectos = new Iesi.Collections.HashedSet();

Proyecto p1 = new Proyecto();p1.Descripcion = “proyecto 1”;Proyecto p2 = new Proyecto();p2.Descripcion = “proyecto 2”;Proyecto p1 = new Proyecto();p1.Descripcion = “proyecto 3”;p1.Save();p2.Save();p3.Save();

jp.Proyectos.Add(p1);jp.Proyectos.Add(p2);jp.Proyectos.Add(p3);jp.Save();

Introducción a NHibernate 22

Page 23: Introduccion a NHibernate (1)

/* Se puede implementar que en el método Save de JefeProyecto se guarden primero los proyectos y luego el objeto JefeProyecto. */

Eventualmente, la clase Proyecto puede tener el otro extremo de la relación uno-a-muchos, es decir tendrá un elemento de relación muchos-a-uno (como el visto en agregación). Esto implica agregar una propiedad de tipo JefeProyecto en la clase Proyecto y luego definir el siguiente archivo de mapeo de la clase Proyecto.

<class name="Proyecto" table="tblProyecto"> <id name=”IdProyecto”>

<column name=”IdProyecto” sql-type=”char(32)” not-null=”true”/>

<generator class=”uuid.hex”/> </id>

<property name=”Descripcion” column=”descripcion”/><many-to-one name=”jpId” class=”JefeProyecto”

column=”IdJefeProyecto”/></class>

En este ejemplo, la clase Proyecto tiene un atributo llamado jpId de tipo JefeProyecto, el cual será una referencia al contenido de la columna IdJefeProyecto de la tabla asociada a la clase JefeProyecto.

Las colecciones de objetos pueden ser mapeadas utilizando las etiquetas <set>, <list>, <map> y <bag>. Cada una de estas etiquetas está orientada a soportar un tipo de colección en particular. A continuación se listan las principales características de cada uno de estos elementos:

<set>: Colección desordenada sin duplicados. Compatible con la interfaz Iesi.Collections.ISet. <list>: Colección ordenada, requiere una columna índice. Compatible con la interfaz System.Collections.IList. <map>: Colección que almacena pares de elementos. Compatible con la interfaz System.Collections.IDictionary. <bag>: Colección desordenada que permite elementos duplicados. Sirve para representar listas no ordenadas.

También es posible utilizar las etiquetas <array> y <primitive-array> pero no se ha encontrando información sobre estas etiquetas.

Una propiedad importante del mapeo de colecciones de objetos es conocida como inicialización perezosa o carga perezosa (lazy loading, lazy initialization). Esta estrategia indica que si un objeto X tiene una colección de objetos Y, entonces al recuperar X desde la base de datos, sólo se recuperarán las propiedades atómicas de X (tipo String, Int, Char, Boolean, etc) pero no las colecciones de objetos Y asociados a X sino hasta que realmente se quiera utilizar la colección de objetos Y. En el caso de JefeProyecto sólo se recupera el identificador del jefe de proyecto, y luego, si en algún momento se requiere recorrer la colección de proyectos cargo de este jefe de proyecto, sólo entonces se recuperará la colección de proyectos desde la base de datos. Esta

Introducción a NHibernate 23

Page 24: Introduccion a NHibernate (1)

característica se puede definir en el archivo de mapeo indicando lazy=”true” en los elementos <set>, <list>, <map> o <bag>.

4.5 Mapeo de relaciones muchos a muchos.

El mapeo muchos a muchos consiste en que dadas dos clases, A y B, entonces A tiene una colección de objetos de tipo B y a la vez, la clase B tiene una colección de objetos de tipo A.

Esta relación entre A y B se puede llevar al modelo de BD utilizando una tabla intermedia que relacione los códigos de los elementos de A con los elementos de B. Esta tabla intermedia sólo estará constituida por claves foráneas de las claves principales de las tablas mapeadas de A y B.

En el siguiente caso tenemos que un banco tiene una cartera de clientes, y a su vez, lo clientes pueden ser clientes de varios bancos. El modelo de este caso se vería algo como sigue:

Esta relación muchos a muchos debe ser representada en un modelo relacional añadiendo una tercera tabla que haga de NUB entre Banco y Cliente, es decir esta tercera tabla relacionará a los clientes con los bancos, teniendo plena libertad de relacionar cualquier cliente con cualquier banco (claro sin permitir repetición).

Teniendo este modelo de clases y su respectivo modelo relacional podemos realizar el mapeo OR cosa de poder persistir nuestras clases.

<class name=”Banco” table=”Banco”><id name=”ID”>

<generator class=”identity”/></id><set name=”Clientes” lazy=”true”

Introducción a NHibernate 24

Page 25: Introduccion a NHibernate (1)

table=”Banco_Cliente”><key column=”BancoID”/><many-to-many class=”Cliente”

column=”ClienteID”/></set>

</class>

<class name=”Cliente” table=”Cliente”><id name=”ID”>

<generator class=”identity”/></id><property name=”Nombre” column=”Nombre” /><property name=”Apellido” column=”Apellido” />

</class>

Para explicar cómo se realiza el mapeo muchos-a-muchos veremos en detalle la etiqueta de la colección:

<set name=”Clientes” lazy=”true” table=”Banco_Cliente”><key column=”BancoID”/><many-to-many class=”Cliente” column=”ClienteID”/>

</set>

La relación entre Banco y Cliente se realiza buscando una instancia en la tabla Banco_Cliente que contenga en la columna BancoID el identificador del banco y en la columna ClienteID el identificador asociado a la clase Cliente.

Las colecciones soportadas por las relaciones muchos-a-muchos son las mismas que para el caso de uno-a-muchos y las reglas de definición son las mismas (atributos de clase definidos como de tipo de la interfaz correspondiente (ISet, IList, etc.).

Introducción a NHibernate 25

Page 26: Introduccion a NHibernate (1)

5.- Manipulación de datos persistentes.

En la sección anterior, vimos ejemplos básicos de cómo crear objetos persistentes y cómo registrar esta persistencia en la base de datos. Ahora veremos cómo recuperar objetos de la base de datos para poder modificar sus estados y guardar estos nuevos estados en la base de datos.

5.1 Recuperando un objeto de la base de datos.

Hay dos modos de recuperar objetos desde la base de datos, ambos basados en el Id del objeto (clave primaria de la tabla asociada). El primero de ellos se invoca cuando creamos el objeto (en este caso tom). El método Load retorna un objeto, por lo que es necesario hacer el cast al objeto requerido. Como parámetros debemos indicar el tipo del objeto requerido y el id que estamos buscando.

Cat tom = (Cat) session.Load(typeof(Cat), idBuscado);

Otra opción es crear el objeto y luego llamar al método Load, ahora como primer parámetro indicamos el objeto donde será almacenado el resultado de la carga y como segundo parámetro el identificador del objeto a recuperar.

Cat tom = new Cat();Session.Load(tom, idBuscado);

El método Load lanza una NHibernate.UnresolvableObjectException en caso de no existir el objeto asociado al identificador dado como parámetro.

Una alternativa al uso del método Load es utilizar el método Get de la clase session. Este método devuelve el objeto indicado y en caso de no encontrarlo retorna null, por lo que es más indicado para comprobar la existencia de un objeto. También tiene la opción de invocarlo determinando el bloqueo que se quiera obtener para el objeto recuperado.

Cat tom = (Cat) session.Get(typeof(Cat), idBuscado);Cat perkins = (Cat) session.Get(typeof(Cat), id2, bloqueo);

Una opción interesante es refrescar el contenido de un objeto (en caso que haya sido actualizado desde otro sistema o sesión). El método a utilizar es Refresh, al cual se le debe indicar el objeto a refrescar.

session.Refresh(cat);

Introducción a NHibernate 26

Page 27: Introduccion a NHibernate (1)

5.2 Realizando consultas simples.

Es común que no conozcamos con anterioridad el identificador del o los objetos que queramos recuperar de la base de datos. Frente a estas situaciones los métodos Load y Get no son útiles y es necesario utilizar otra estrategia.

Se pueden recuperar objetos desde la base de datos utilizando una consulta en lenguaje HQL (Hibernate Quero Language), el cual se verá en profundidad más adelante.

A continuación se presentan algunos ejemplos de consultas a la base de datos

IQuery q1 = session.createQuery(“from Cat as cat”);IList lista = q1.List();

IQuery q2 = session.createQuery(“from Cat as cat ” +“where cat.Nombre=:nombreGato”);

q2.SetParameter(“nombreGato”,valor);IList lista2 = q2.List();Cat primerGato = q2.List()[0]

Podemos realizar consultas que obtengan más de un campo como resultado de la siguiente manera:

INumerable resultado = session.Enumerable( “select mc.b, mc.c, count(mc) from miClass mc” + “group by mc.a” );

foreach ( object[] filaActual in resultado ){/* tipo1 y tipo2 son los tipos de las columnas 1 y 2 de la consulta que hemos realizado */tipo1 col1 = (tipo1) filaActual[0];tipo2 col2 = (tipo2) filaActual[1];int total = (int) filaActual[2];

}

Introducción a NHibernate 27