Hola Estimad@s,
seguimos avanzando con nuestra aplicación, en este post veremos como hacer para que nuestra aplicación tenga soporte para utilizar un mapeador Objeto-Relacional (ORM), particularmente Hibernate.
Introducción
Como puede notarse en los diferentes posts referidos a esta aplicación, en ningún caso profundizamos en los temas, solo vamos definiendo lo mínimo a medida que lo necesitamos, de lo contrario esto sería un "megamanual" y esa no es la intención. Para un manejo más profundo de la temática recomiendo este curso, el cual es de muy alta calidad, el titulo del curso es Curso de "Hibernate con Spring", aunque en realidad solo profundiza (y muy bien) con Hibernate. De he he utilizado algunas clases utilitarias que propone el autor del curso, oportunamente haré la referencia.
Solo diré que hoy en día el uso de los ORM está muy extendido y facilita enormemente la tarea de la persistencia y el mantenimiento de la aplicación, además de dar solución a diferentes problemas inherentes a la temática, por ejemplo: validaciones, caching, logging, tuning, independencia del SGBDR, etc. Hibernate es lejos el ORM Open Source más usado y robusto del que disponemos.
Descargando Hibernate
La versión que utilizaremos es la 4.1.10.Final y se la puede descargar desde:
hibernate-release-4.1.10.Final.zip (Sistemas tipo window$)
o
hibernate-release-4.1.10.Final.tgz (Sistemas tipo Unix)
Una vez descargada la versión, descomprimimos el archivo en una carpeta que denominaremos [HIBERNATE_HOME].
Descargando Hibernate Validator
También utilizaremos Hibernate Validator, al cual nos referiremos en el futuro, por ello debemos descargarlo desde:
hibernate-validator-4.3.1.Final-dist.zip (Sistemas tipo window$)
o
hibernate-validator-4.3.1.Final-dist.tar.gz (Sistemas tipo Unix)
Una vez descargada la versión, descomprimimos el archivo en una carpeta que denominaremos [HIBERNATEVALIDATOR_HOME].
Copiando las librerías a nuestro proyecto
Ahora debemos copiar los archivos jar a nuestra carpeta WEB-INF/lib, el listado de archivos necesarios es:
[HIBERNATE_HOME]/lib/required
antlr-2.7.7.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-4.0.1.Final.jar
hibernate-core-4.1.10.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.15.0-GA.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.0.Final.jar
[HIBERNATEVALIDATOR_HOME]/
hibernate-validator-4.3.1.Final.jar
hibernate-validator-annotation-processor-4.3.1.Final.jar
[HIBERNATEVALIDATOR_HOME]/lib/required
validation-api-1.0.0.GA.jar
[HIBERNATEVALIDATOR_HOME]/lib/optional
joda-time-1.6.jar
jsoup-1.6.1.jar
Archivo de configuración de Hibernate
El archivo de configuración principal de Hibernate es hibernate.cfg.xml y debemos colocarlo en el paquete por defecto de nuestras clases Java, en otras palabras en la carpeta src. El contenido inicial será:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
Donde:
la propiedad<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
define la conexión a la base de datos que utilizaremos, en este caso le diremos a Hibernate que utilice la fuente de datos JNDI que ya hemos definido en esta aplicación. Una forma de crear una conexión JDBC genérica sería: <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost/practico</property> <property name="connection.username">root</property> <property name="connection.password">xxxx</property>
la propiedad<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
Define la implementación del dialecto que se utilizará, en este caso MySQL
la propiedades
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
Definen que se mostrar las sentencias SQL que se generan en el uso de Hibernate y que a su vez se muestren formateadas para su mejor lectura.
por último, la propiedad<property name="hibernate.hbm2ddl.auto">update</property>
Define que las sentencias DDL que se generen serán en forma de delta, esto es que se generarán las modificaciones necesarias para realizar los mapeos correctos entre las clases java y las tablas de MySQL. Bien, hasta aquí hemos cubierto lo que yo llamo las necesidades básicas de infraestructura para que el framework funcione. A continuación haremos un prueba básica para corroborar el correcto funcionamiento. Beans que requieren persistencia Persistiremos dos beans, uno que representa un Cliente y el otro que representa una Zona a la cual el Cliente pertenece. Las tablas que almacenan a los clientes y zonas ya existen en la base de datos que estamos utilizando, el modelo relacional de esas dos tablas es: La clase que representa al cliente (Cliente.java) tiene el siguiente código: package ar.com.magm.model;
public class Cliente { private String cliente; private boolean cuentaHabilitada; private int idCliente; private Zona zona;
public String getCliente() { return cliente; } public int getIdCliente() { return idCliente; } public Zona getZona() { return zona; } public boolean isCuentaHabilitada() { return cuentaHabilitada; } public void setCliente(String cliente) { this.cliente = cliente; } public void setCuentaHabilitada(boolean cuentaHabilitada) { this.cuentaHabilitada = cuentaHabilitada; } public void setIdCliente(int idCliente) { this.idCliente = idCliente; } public void setZona(Zona zona) { this.zona = zona; } } La clase que representa la zona (Zona.java) tiene el siguiente código: package ar.com.magm.model;
public class Zona { private int idZona; private String zona;
public int getIdZona() { return idZona; } public String getZona() { return zona; } public void setIdZona(int idZona) { this.idZona = idZona; } public void setZona(String zona) { this.zona = zona; } }
Archivos de mapeo de los beans
Es necesario "decirle" a Hibernate cuales son las correspondencias entre las clases Java y las tablas relacionales, Hibernate permite hacerlo con anotaciones o con archivos de configuración XML, he optado por los archivos XML.
Ambos archivos se alojan en el mismo paquete que las clases y son:
ar/com/magm/model/Cliente.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ar.com.magm.model.Cliente" table="clientes">
<id column="idCliente" name="idCliente" type="integer" />
<property name="cliente" />
<property name="cuentaHabilitada" />
<many-to-one name="zona" class="ar.com.magm.model.Zona" column="idZona"/>
</class>
</hibernate-mapping>
el elemento
<class name="ar.com.magm.model.Cliente" table="clientes">
Mapea la clase ar.com.magm.model.Cliente con la tabla clientes
el elemento<id column="idCliente" name="idCliente" type="integer" />
Define que la propiedad idCliente de la clase es la clave principal y que se corresponde con la columna idCliente y es de tipo entero.
los elementos
<property name="cliente" />
<property name="cuentaHabilitada" />
mapean los atributos simples cliente y cuentaHabilitada, a los tipos los calcula automáticamente Hibernate
el elemento<many-to-one name="zona" class="ar.com.magm.model.Zona" column="idZona"/>
define la propiedad compleja zona y la clave foránea que se debe utilizar para unir la tabla clientes con la tabla zonasque es idZona
ar/com/magm/model/Zona.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ar.com.magm.model.Zona" table="zonas">
<id column="idZona" name="idZona" type="integer" />
<property name="zona"/>
</class>
</hibernate-mapping>
No explicaré este archivo, ya que la explicación anterior es suficiente para entenderlo.
Nuevamente el archivo de configuración principal de Hibernate
Modificaremos ahora el archivo hibernate.cfg.xml agregando las siguientes líneas:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="ar/com/magm/model/Cliente.hbm.xml" />
<mapping resource="ar/com/magm/model/Zona.hbm.xml" />
</session-factory>
</hibernate-configuration>
Ambos elementos le icen"a Hibernate cuales son los archivos de mapeos que debe tener en cuenta.
Un Servlet de prueba
Crearemos ahora un Servlet con el fin de probar el correcto funcionamiento de Hibernate y los mapeos que hemos definido, la clase tiene el siguiente código:
package ar.com.magm.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import ar.com.magm.model.Cliente;
@WebServlet("/TestHibernate")
public class TestHibernate extends HttpServlet {
private Session session;
public TestHibernate() {
Configuration configuration = new Configuration();
configuration.configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
SessionFactory sf = configuration.buildSessionFactory(serviceRegistry);
session = sf.openSession();
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int idCliente = 33;
Cliente cl = (Cliente) session.get(Cliente.class, idCliente);
StringBuilder str = new StringBuilder();
if (cl != null) {
str.append("Cliente: " + cl.getCliente() + " (Id: "
+ cl.getIdCliente() + ") - Cta Habilitada: "
+ cl.isCuentaHabilitada() + "\n");
str.append("\tZona: " + cl.getZona().getZona() + " (Id: "
+ cl.getZona().getIdZona() + ")");
} else {
str.append("No existe el cliente con id=" + idCliente);
}
response.getWriter().print(str);
response.getWriter().flush();
}
}
En el constructor obtenemos una sesión, la cual nos permitirá realizar operaciones con Hibernate.
Luego en el método doGet(), ejecutamos:
Cliente cl = (Cliente) session.get(Cliente.class, idCliente);
para que Hibernate obtenga los datos de la base de datos, instancie una clase Cliente y establezca los valores para todos los atributos que hemos mapeado. El resto del código es trivial, por ello omitiré cualquier comentario. Ejecutando el servlet Ahora vamos a nuestro navegador y con la url que ya hemos utilizado colocamos los datos de login: Presionamos el botón "Ingreso" y cuando nos encontremos en la pantalla principal:
Cambiamos la url por: https://localhost:8080/pf/TestHibernate, presionamos enter y veremos un resultado similar al siguiente:
Con esto hemos comprobado el correcto funcionamiento del framework.
Recomiendo ver la salida de la vista "Consola" de eclipse, en la cual se podrán apreciar las sentencias SQL que ha emitido Hibernate para obtener los datos relacionales con los cuales se crearon las instancias de las clases Cliente yZona.
En mi caso es la siguiente:
Hibernate:
select
cliente0_.idCliente as idCliente0_0_,
cliente0_.cliente as cliente0_0_,
cliente0_.cuentaHabilitada as cuentaHa3_0_0_,
cliente0_.idZona as idZona0_0_
from
clientes cliente0_
where
cliente0_.idCliente=?
Hibernate:
select
zona0_.idZona as idZona1_0_,
zona0_.zona as zona1_0_
from
zonas zona0_
where
zona0_.idZona=?
Bien, eso es todo por ahora.
Espero les sea útil.
Saludos
Mariano