NHibernate es la conversión de Hibernate de lenguaje Java a C# para su integración en la plataforma .NET. Al igual que muchas otras herramientas libres para esta plataforma, NHibernate también funciona en Mono.
Al usar NHibernate para el acceso a datos el desarrollador se asegura de que su aplicación es agnóstica en cuanto al motor de base de datos a utilizar en producción, pues NHibernate soporta los más habituales en el mercado: MySQL, PostgreSQL, Oracle, MS SQL Server, etc. Sólo se necesita cambiar una línea en el fichero de configuración para que podamos utilizar una base de datos distinta.
NHibernate es software libre, distribuido bajo los términos de la LGPL (Licencia Pública General Menor de GNU).
Introducción
Puesto a que este mundo es muy cambiante debemos estar al tanto de las diferentes tipos de tecnologías que vienen saliendo al campo informático. Herramientas que van a facilitar la vida de cualquier programados, analista de base de datos, administrador de red, en fin en este campo tan variado y complicado que es la informática. Con este trabajo daremos a conocer una de las tantas tecnologías nuevas en las que el campo de programación va avanzando y mejorando para lograr hacer la vida de dichos más fácil.
Marco Teórico
Hibernate es una herramienta de Mapeo objeto-relacional (ORM) para la plataforma Java (y disponible también para .Net con el nombre de NHibernate) que facilita el mapeo de atributos entre una base de datos relacional tradicional y el modelo de objetos de una aplicación, mediante archivos declarativos (XML) o anotaciones en los beans de las entidades que permiten establecer estas relaciones. Hibernate es software libre, distribuido bajo los términos de la licencia GNU LGPL.
Características
Como todas las herramientas de su tipo, Hibernate busca solucionar el problema de la diferencia entre los dos modelos de datos coexistentes en una aplicación: el usado en la memoria de la computadora (orientación a objetos) y el usado en las bases de datos (modelo relacional). Para lograr esto permite al desarrollador detallar cómo es su modelo de datos, qué relaciones existen y qué forma tienen. Con esta información Hibernate le permite a la aplicación manipular los datos de la base operando sobre objetos, con todas las características de la
POO. Hibernate convertirá los datos entre los tipos utilizados por Java y los definidos por SQL. Hibernate genera las sentencias SQL y libera al desarrollador del manejo manual de los datos que resultan de la ejecución de dichas sentencias, manteniendo la portabilidad entre todos los motores de bases de datos con un ligero incremento en el tiempo de ejecución.
Hibernate está diseñado para ser flexible en cuanto al esquema de tablas utilizado, para poder adaptarse a su uso sobre una base de datos ya existente. También tiene la funcionalidad de crear la base de datos a partir de la información disponible.
Hibernate ofrece también un lenguaje de consulta de datos llamado HQL (Hibernate Query Language), al mismo tiempo que una API para construir las consultas programáticamente (conocida como "criteria").
Hibernate para Java puede ser utilizado en aplicaciones Java independientes o en aplicaciones Java EE, mediante el componente Hibernate Annotations que implementa el estándar JPA, que es parte de esta plataforma.
La "crema completa" arquitectura de la aplicación de los resúmenes de distancia de la base API de ADO.NET y permite NHibernate cuidar de los detalles.
Aqui las definiciones de los objetos en los diagramas:
ISessionFactory (NHibernate.ISessionFactory)
A multi-hilo (inmutable) de caché de asignaciones compilado una base de datos única. Una fábrica de ISession y un cliente de IConnectionProvider. Podría tener un accesorio opcional (segundo nivel) de caché de datos que es reutilizable entre las transacciones, en un proceso o un conjunto de nivel.
ISession (NHibernate.ISession)
Un único subproceso, de corta duración objeto que representa una conversación entre la aplicación y el almacenamiento persistente. Coloca una conexión de ADO.NET. Fábrica de ITransaction. Tiene un caché obligatoria (primer nivel) de los objetos persistentes, que se utiliza cuando se navega el gráfico de objetos o de búsqueda de objetos por identificador.
Objetos persistentes y Colecciones
De corta duración, los objetos de un solo subproceso que contiene el estado persistente y función empresarial. Estos pueden ser POCOS ordinaria, la única cosa especial sobre ellos es que están actualmente asociados con (exactamente uno) ISession. Tan pronto como se cierre la sesión, que será independiente y libre para usar en cualquier capa de aplicación (por ejemplo, directamente como objetos de transferencia de datos hacia y desde la presentación).
Objetos transitorios y Colecciones
Las instancias de las clases persistentes que no está asociado con un ISession. Es posible que se hayan creado instancias de la aplicación y no (todavía) persisten o pueden haber sido instanciadas por un ISession cerrado.
ITransaction (NHibernate.ITransaction)
(Opcional) Un único subproceso, de corta vida de objetos utilizados por la aplicación para especificar unidades atómicas de trabajo. Resúmenes de la aplicación de la base de transacciones de ADO.NET. Un ISession puede abarcar varios ITransactions en algunos casos.
IConnectionProvider (NHibernate.Connection.IConnectionProvider)
(Opcional) Una fábrica de conexiones de ADO.NET y los comandos. Resúmenes de la aplicación de lo concreto proveedor implementaciones específicas de IDbConnection y IDbCommand. No expuestos a la aplicación, pero puede ser extendido / implementado por el desarrollador.
Idriver (NHibernate.Driver.IDriver)
(Opcional) Una interfaz encapsula las diferencias entre los proveedores de ADO.NET, como las convenciones de nombres de parámetros, y funciones de ADO.NET.
ITransactionFactory (NHibernate.Transaction.ITransactionFactory)
(Opcional) Una fábrica de instancias de ITransaction. No expuestos a la aplicación, pero puede ser extendido / implementado por el desarrollador.
Dada una "lite" la arquitectura, la aplicación pasa por el ITransaction / ITransactionFactory y / o APIs IConnectionProvider hablar con ADO.NET directamente.
Código de ejemplo
En el siguiente ejemplo será solamente puntapié inicial para introducirnos en este framework de persistencia, si bien contiene algunos consejos como buenas prácticas pero no es lo que persigue, por esta razón los ejemplos serán sencillos. De hecho, el modelo posee una clara correspondencia de 1 objeto => 1 tabla, lo cual en la realidad no debe buscarse, ya que los modelos de dominios y de datos buscan resolver problemas diferentes, y si estamos en presencia de esto, es muy probable que no estemos explotando todo el potencial de ambos modelos, y por sobre todo de esta herramienta. NHibernate nos da la libertad de realizar un modelo de dominio, y después mapearlo a un modelo de tablas, y viceversa.
Modelo de Dominio
El modelo de dominio con el cual trabajaremos será el siguiente:
Modelo relacional
El modelo de datos estará representado por estas tres relaciones (tablas):
Archivos de Mapeo:
NHibernate para poder conocer la correspondencia que existe entre los objetos y las tablas lo hace por medio de la configuración de mapeo. Esta configuración se puede hacer de forma programática o bien, la más utilizada, que consiste en archivos de mapeo XML (mapping files). Estos archivos poseen la información necesaria para poder saber en qué tabla/s se tiene/n que guardar cada objeto ó en que tabla tengo que buscar para obtener un objeto. Los nombres de los archivos terminan con el sufijo .hbm.xml. Las extensiones de estos archivos xml es .hbm.xml. Por ejemplo Factura.hbm.xml, LineaFactura.hbm.xml, Producto.hbm.xml.
Archivo de Mapeo de la clase Factura (Factura.hbm.xml):
!--?xml version="1.0" encoding="utf-8" ?-->
hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dario.NH01" namespace="Dario.NH01.Entidades">
class name="Factura">
id name="Id" column="IdFactura" type="int" value="0">
generator class="identity">
/generator>
property name="Fecha" type="DateTime" null="true">
bag name="Lineas" cascade="all-delete-orphan">
key column="IdFactura">
one-to-many class="LineaFactura">
/one-to-many>
/property>
/hibernate-mapping>
Veamos los atributos del archivo. Al comienzo del archivo estamos indicando el nombre del assembly: Dario.NH01, donde van a estar ubicadas las clases. Luego el namespace de dicha clase. Luego comenzamos a tratar las características de la clase que queremos mapear con el atributo class, en este caso Factura. Dentro de los tags
Con el tag
Pero para hacer un poco más interesante esto, agreguemos la relación 1:N con la clase LineaFactura. Para esto vamos a utilizar el atributo
Archivo de Mapeo de la clase LineaFactura (LineaFactura.hbm.xml):
!--?xml version="1.0" encoding="utf-8" ?-->
hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dario.NH01" namespace="Dario.NH01.Entidades">
class name="LineaFactura">
id name="Id" column="IdLineaFactura" type="int" value="0">
/generator>
property name="Cantidad" type="int">
property name="Precio" type="Decimal">
many-to-one name="Factura" column="IdFactura">
many-to-one name="Producto" column="IdProducto">
/many-to-one>
/hibernate-mapping>
Algo a notar en este archivo es la declaración de la relación many-to-one para la propiedad Factura y también para Producto.
Archivos de mapeo como recursos embebidos
Es una práctica muy común y recomendable el incluir los archivos de mapeo como recursos embebidos dentro de un assembly. Cualquier IDE de desarrollo moderna -Sharp Develop, Visual Studio ó Monodevelop-, nos permiten configurar las propiedades de los archivos y que acción se debe tomar con ellos, dicha opción se llama Action Build / Acción de Construcción y entonces debemos configurarla como: Embedded Resource/Recurso embebido. Si olvidamos hacer esto con los archivos de mapeo, es posible que no tengamos ningún error en algunos casos, pero no funcionará correctamente.
Preparando las clases
Métodos y Propiedades
Al estar trabajando con clases con lazy="true", que es el valor por defecto, todos los métodos y propiedades deben ser declarados como virtual. De este modo NH puede crear un proxy de nuestras entidades, esto es transparente para nosotros.
Sobrescribiendo Equals y GetHashCode
Para poder trabajar correctamente con colecciones debemos sobrescribir estos métodos. Sino sobrescribimos el Equals al hacer una operación como lineaFactura1.Equals(lineaFactura2) podría devolver false aún tratándose de la misma linea de factura, debido a que la comparación se está haciendo por la posiciones de memoria. Cuando sobrescribimos Equals por ejemplo de esta manera:
public override bool Equals(object obj)
{
if (this == obj) return true;
LineaFactura lineaFactura = obj as LineaFactura;
if (lineaFactura == null) return false;
return id == lineaFactura.id && Equals(factura, lineaFactura.factura);
}
De este modo nos aseguramos de que 2 objetos son iguales si son iguales sus propiedades Id y Factura.
Ya hemos sobrescrito el Equals, ahora debemos hacer lo mismo con GetHashCode, debido a que 2 objetos iguales deben tener un mismo número de hash (y 2 objetos distintos pueden o no tener el mismo número de hash). El número hash no representa un id, no tiene que ser único.
Una posible implementación podría ser:
public override int GetHashCode()
{
return id + 29*factura.GetHashCode();
}
Con este método nos aseguramos que 2 líneas factura devuelvan el mismo número al ser iguales. Al trabajar con números primos obtenemos buenas funciones hash. Lo importante aquí es hacer bien la función, la optimización de esta función queda fuera del alcance del tutorial.
Implementando IEquatable
Todas las entidades implementan IEquatable
ISessionFactory e ISession
Una sesión es un marco de trabajo en el cual NH 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 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 relacional. 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.
Configuración del ISessionFactory de la aplicación
Para configurar el ISessionFactory, es decir, para decirle con qué motor de base de datos vamos a trabajar, la cadena de conexión (connection string), el driver que utilizaremos entre otras cosas, como toda configuración en NH, se puede realizar de manera programática (por código) o mediante archivos de configuración XML, y dentro de esta última podemos hacerlo mediando el App.config o mediante el hibernate.cfg.xml. Utilizaremos la última en este caso:
!--?xml version="1.0" encoding="utf-8" ?-->
hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
session-factory name="NH01">
property name="connection.provider">NHibernate.Connection.DriverConnectionProvider
name="dialect">NHibernate.Dialect.MsSql2005Dialect
property name="connection.connection_string">Data Source=localhost\SQLEXPRESS;Initial Catalog=NH01;Integrated Security=True
mapping assembly="Dario.NH01">
/mapping>
/hibernate-configuration>
En esta configuración hemos incluido el nombre del assembly donde se encontrarán embebidos los archivos de mapeo, en este caso el assembly es: Dario.NH01.
El código necesario para configurar NH al comienzo de la aplicación es:
Configuration cfg = new Configuration();
cfg.Configure("hibernate.cfg.xml");
ISessionFactory sesiones = cfg.BuildSessionFactory();
ISession sesion = sesiones.OpenSession();
Primeramente debemos crear un objeto del tipo Configuration, configurarlo mediante Configure("hibernate.cfg.xml") y que se encargue de crear el ISessionFactory, por medio del método BuildSessionFactory(). Una vez que creamos el objeto sesiones del tipo ISessionFactory, ya podemos comenzar a crear objetos ISession. En este caso trabajaremos con una sola sesión en la aplicación: sesion.
Es una buena práctica realizar un singleton del objeto ISessionFactory debido a que es un proceso costoso para la aplicación, en otras palabras, la ejecución de BuildSessionFactory se debe hacer una vez durante la ejecución.
Creamos Productos, Facturas y Líneas
Ahora comenzamos con el código de ejemplo, primero creamos Productos y los guardamos:
Producto prod1 = new Producto("Leche Entera", "Lacteos");
Producto prod2 = new Producto("Lavandina", "Limpieza");
Producto prod3 = new Producto("Vasos", "Bazar");
sesion.Save(prod1);
sesion.Save(prod2);
sesion.Save(prod3);
Creamos 3 productos y los guardamos en la sesión sesion mediante el método Save. Una vez hecho esto podemos continuar con la creación de la Factura y sus LineaFactura.
Factura factura = new Factura(DateTime.Now);
factura.Lineas.Add(new LineaFactura(factura, prod1, 2.25m, 5));
factura.Lineas.Add(new LineaFactura(factura, prod2, 3.5m, 1));
factura.Lineas.Add(new LineaFactura(factura, prod3, 5.4m, 10));
sesion.Save(factura);
sesion.Flush();
sesion.Close();
Una vez creada factura, podemos realizar un Save . Notése aquí que hemos guardado la factura, y las líneas también se guardaron. Esto es debido a que en el mapping de Factura hemos configurado el Agregado/Actualización/Borrado en cascada mediante la siguiente línea cascade="all-delete-orphan". Después de realizar todos las inserciones, procedemos a realizar Flush() para que se ejecuten las sentencias SQL propiamente dichas contra el servidor, en ese momento se guardan todos nuestros objetos, antes de esto los objetos permanecían en el objeto sesion (en la cache de 1er. nivel).
Luego de que hemos usado la sesión, liberamos los recursos realizando un Close().
Intellisense para los archivos de mapeo en Visual Studio
Los archivos de mapeo de este ejemplo fueron escritos sin ayuda de una herramienta de generación de código. Solamente con ayuda del Intellisense de Visual Studio en los archivos XML. Para tener esta funcionalidad se deben copiar los archivos http://www.blogger.com/img/blank.gifnhibernate-mapping-2.2.xsd y nhibernate-configuration-2.2.xsd al directorio C:\Archivos de programa\Microsoft Visual Studio 8\Xml\Schemas donde se tiene instalado el Visual Studio.
Conclusiones
Utilizar un framework de ORM simplifica enormemente la programación de lógica de persistencia. Se trata de una idea completamente madura que cada vez se vuelve más popular. Nuestro código se abstrae completamente del schema y tipo de base de datos utilizada. Para hacer que este ejemplo funcione contra otra base de datos (ej. MySQL) lo único que tendríamos que hacer sería cambiar el dialecto y la cadena de conexión en el archivo de configuración de nuestra aplicación…nada más.
Nuestra lógica de negocios trabaja contra un modelo de dominio completamente orientado a objetos. Generamos entre un 30% y un 40% menos de código y como si esto fuera poco el tipo de código generado es mucho más sencillo y mantenible.
Aqui les dejamos el ejemplo para que lo puedan descargar!!!!
http://https://skydrive.live.com/?cid=970a25aa4e0a2538&sc=documents&uc=1&id=970A25AA4E0A2538!448#
Autores
Henry Rafael Mendoza Figueroa
Roberto Alexander Posada Lemus
No hay comentarios:
Publicar un comentario