1270 lines
60 KiB
XML
1270 lines
60 KiB
XML
|
<chapter id="tutorial">
|
||
|
<title>Introducción a Hibernate</title>
|
||
|
|
||
|
<sect1 id="tutorial-intro">
|
||
|
<title>Prefacio</title>
|
||
|
|
||
|
<para>
|
||
|
Este capítulo es un tutorial introductorio de Hibernate. Comenzamos con
|
||
|
una aplicación simple de línea de comandos usando un base de datos
|
||
|
en-memoria y desarrollándola en fácil para entender los pasos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Este tutorial está concebido para usuarios nuevos de Hibernate pero
|
||
|
requiere conocimiento en Java y SQL. Está basado en un tutorial de
|
||
|
Michael Gloegl. Las bibliotecas de terceros que mencionamos son para JDK 1.4
|
||
|
y 5.0. Podrías necesitar otras para JDK 1.3.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="tutorial-firstapp">
|
||
|
<title>Parte 1 - La primera Aplicación Hibernate</title>
|
||
|
|
||
|
<para>
|
||
|
Primero, crearemos una aplicación simple de Hibenate basada en consola.
|
||
|
Usamos usamos una base de datos en-memoria (HSQL DB), de modo que no necesitamos
|
||
|
instalar ningún servidor de base de datos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Asumamos que necesitamos una aplicación pequeña de base de datos que
|
||
|
pueda almacenar eventos que queremos atender, e información acerca de los
|
||
|
hostales de estos eventos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La primera cosa que hacemos, es armar nuestro directorio de desarrollo y poner
|
||
|
en él todas las bibliotecas Java que necesitamos. Descarga la distribución
|
||
|
de Hibernate del sitio web de Hibernate. Extrae el paquete y coloca todas las
|
||
|
bibliotecas requeridas encontradas en <literal>/lib</literal> dentro del directorio
|
||
|
<literal>/lib</literal> de nuestro nuevo directorio de desarrollo de trabajo.
|
||
|
Debe asemejarse a esto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[.
|
||
|
+lib
|
||
|
antlr.jar
|
||
|
cglib-full.jar
|
||
|
asm.jar
|
||
|
asm-attrs.jars
|
||
|
commons-collections.jar
|
||
|
commons-logging.jar
|
||
|
ehcache.jar
|
||
|
hibernate3.jar
|
||
|
jta.jar
|
||
|
dom4j.jar
|
||
|
log4j.jar ]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Este es el conjunto mínimo de bibliotecas requeridas para Hibernate (observa que
|
||
|
también hemos copiado hibernate3.jar, el fichero principal). Ver el fichero
|
||
|
<literal>README.txt</literal> en el directorio <literal>lib/</literal> de la distribución
|
||
|
de Hibernate para más información sobre bibliotecas de terceros requeridas y
|
||
|
opcionales. (Realmente, Log4J no es requerida aunque preferida por muchos desarrolladores).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Por siguiente, creamos una clase que represente el evento que queremos
|
||
|
almacenar en base de datos.
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-firstclass">
|
||
|
<title>La primera clase</title>
|
||
|
|
||
|
<para>
|
||
|
Nuestra primera clase persistente es un JavaBean simple con algunas propiedades:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[import java.util.Date;
|
||
|
|
||
|
public class Event {
|
||
|
private Long id;
|
||
|
|
||
|
private String title;
|
||
|
private Date date;
|
||
|
|
||
|
Event() {}
|
||
|
|
||
|
public Long getId() {
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
private void setId(Long id) {
|
||
|
this.id = id;
|
||
|
}
|
||
|
|
||
|
public Date getDate() {
|
||
|
return date;
|
||
|
}
|
||
|
|
||
|
public void setDate(Date date) {
|
||
|
this.date = date;
|
||
|
}
|
||
|
|
||
|
public String getTitle() {
|
||
|
return title;
|
||
|
}
|
||
|
|
||
|
public void setTitle(String title) {
|
||
|
this.title = title;
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes ver que esta clase usa las convenciones de nombrado estándar de JavaBean
|
||
|
para métodos getter y setter de propiedad, así como visibilidad privada
|
||
|
para los campos. Esto es un diseño recomendado, aunque no requerido. Hibernate
|
||
|
también puede acceder a los campos directamente; el beneficio de los métodos
|
||
|
de acceso es la robustez para la refactorización.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La propiedad <literal>id</literal> tiene un valor único de identificador para
|
||
|
un evento en particular. Todas las clase de entidad persistentes ( también hay
|
||
|
clases dependientes menos importantes) necesitarán una propiedad identificadora
|
||
|
similar si queremos usar el conjunto completo de funcionalidades de Hibernate. De hecho,
|
||
|
la mayoría de las aplicaciones (esp. aplicaciones web) necesitan distinguir
|
||
|
objetos por identificador, de modo que debes considerar esto como un aspecto en vez de una
|
||
|
limitación. Sin embargo, usualmente no manipulamos la identidad de un objeto, por
|
||
|
lo tanto el método setter debe ser privado. Sólo Hibernate asignará
|
||
|
identificadores cuando un objeto sea salvado. Puedes ver que Hibernate puede acceder a
|
||
|
métodos de acceso públicos, privados y protegidos, tanto como directamente a
|
||
|
campos (públicos, privados y protegidos). La elección está en ti,
|
||
|
y puedes ajustarla a tu diseño de aplicación.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
El constructor sin argumentos es un requerimiento para todas las clases persistentes.
|
||
|
Hibernate tiene que crear objetos para ti, usando reflección Java. El constructor
|
||
|
puede ser privado, sin embargo, la visibilidad de paquete es requerida para la generación
|
||
|
de proxies en tiempo de ejecución y la recuperación de datos sin
|
||
|
instrumentación del bytecode.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Coloca este fichero de código Java en un directorio llamado <literal>src</literal>
|
||
|
en la carpeta de desarrollo. El directorio ahora debe verse como esto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[.
|
||
|
+lib
|
||
|
<Hibernate and third-party libraries>
|
||
|
+src
|
||
|
Event.java]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
En el próximo paso, le decimos a Hibernate sobre esta clase persistente.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-mapping">
|
||
|
<title>El fichero de mapeo</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate necesita saber cómo cargar y almacenar objetos de la
|
||
|
clase persistente. Aquí es donde el fichero de mapeo de Hibernate
|
||
|
entra en juego. El fichero de mapeo le dice a Hibernate a qué tabla en
|
||
|
la base de datos tiene que acceder, y qué columnas en esta tabla debe usar.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La estructura básica de un fichero de mapeo se parece a esto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<?xml version="1.0"?>
|
||
|
<!DOCTYPE hibernate-mapping PUBLIC
|
||
|
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||
|
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
|
||
|
|
||
|
<hibernate-mapping>
|
||
|
[...]
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Observa que el DTD de Hibernate es muy sofisticado. Puedes usarlo para
|
||
|
autocompleción de los elementos y atributos XML de mapeo en tu
|
||
|
editor o IDE. Debes también abrir el fichero DTD en tu editor de
|
||
|
texto. Es la forma más fácil para tener un panorama de todos
|
||
|
los elementos y atributos y ver los valores por defectos, así como
|
||
|
algunos comentarios. Nota que Hibernate no cargará el fichero DTD de
|
||
|
la web, sino que primero buscará en el classpath de la aplicación.
|
||
|
El fichero DTD está incluído en <literal>hibernate3.jar</literal>
|
||
|
así como también en el directorio <literal>src/</literal> de la
|
||
|
distribución de Hibernate.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Omitiremos la declaración de DTD en futuros ejemplos para acortar
|
||
|
el código. Por supuesto, no es opcional.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Entre las dos etiquetas <literal>hibernate-mapping</literal>, incluye
|
||
|
un elemento <literal>class</literal>. Todas las clases de entidad
|
||
|
persistentes (de nuevo, podría haber más adelante clases
|
||
|
dependientes, que no sean entidades de-primera-clase) necesitan dicho mapeo
|
||
|
a una tabla en la base de datos SQL:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||
|
|
||
|
<class name="Event" table="EVENTS">
|
||
|
|
||
|
</class>
|
||
|
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Hasta ahora dijimos a Hibernate cómo persistir y cargar el objeto
|
||
|
de clase <literal>Event</literal> a la tabla <literal>EVENTS</literal>,
|
||
|
cada instancia representada por una fila en esta tabla. Ahora continuamos con
|
||
|
un mapeo de la propiedad de identificado único a la clave primaria
|
||
|
de la tabla. Además, como no queremos cuidar del manejo de este identificador,
|
||
|
configuramos la estrategia de generación de identificadores para una columna
|
||
|
clave primaria delegada:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||
|
|
||
|
<class name="Event" table="EVENTS">
|
||
|
<id name="id" column="EVENT_ID">
|
||
|
<generator class="increment"/>
|
||
|
</id>
|
||
|
</class>
|
||
|
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El elemento <literal>id</literal> el la declaración de la propiedad
|
||
|
identificadora, <literal>name="id"</literal> declara el nombre de la
|
||
|
propiedad Java. Hibernate usará los métodos getter y setter
|
||
|
para acceder a la propiedad. El attributo de columna dice a Hibernate cuál
|
||
|
columna de la tabla <literal>EVENTS</literal> usamos para esta clave primaria.
|
||
|
El elemento anidado <literal>generator</literal> especifica la estrategia de
|
||
|
generación de identificadores, en este caso usamos <literal>increment</literal>,
|
||
|
que es un método muy simple de incremento de número en-memoria
|
||
|
útil mayormente para testeo (y tutoriales). Hibernate también
|
||
|
soporta identificadores generados por base de datos, globalmente únicos,
|
||
|
así como también asignados por aplicación (o cualquier
|
||
|
estrategia para la que hayas escrito una extensión).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Finalmente incluímos declaraciones para las propiedades persistentes
|
||
|
de la clases en el fichero de mapeo. Por defecto, ninguna propiedad de la clase
|
||
|
se considera persistente:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[
|
||
|
<hibernate-mapping>
|
||
|
|
||
|
<class name="Event" table="EVENTS">
|
||
|
<id name="id" column="EVENT_ID">
|
||
|
<generator class="increment"/>
|
||
|
</id>
|
||
|
<property name="date" type="timestamp" column="EVENT_DATE"/>
|
||
|
<property name="title"/>
|
||
|
</class>
|
||
|
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Al igual que con el elemento <literal>id</literal>, el atributo <literal>name</literal>
|
||
|
del elemento <literal>property</literal> dice a Hibernate cáles métodos
|
||
|
getter y setter usar.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
¿Por qué el mapeo de la propiedad <literal>date</literal>
|
||
|
incluye el atributo <literal>column</literal>, pero el de la de
|
||
|
<literal>title</literal> no? Sin el atributo <literal>column</literal>
|
||
|
Hibernate usa por defecto el nombre de propiedad como nombre de columna.
|
||
|
Esto funciona bien para <literal>title</literal>. Sin embargo,
|
||
|
However, <literal>date</literal> es una palabra reservada en la
|
||
|
mayoría de las bases de datos, así que mejor la mapeamos
|
||
|
a un nombre diferente.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La próxima cosa interesante es que el mapeo de <literal>title</literal>
|
||
|
carece de un atributo <literal>type</literal>. Los tipos que declaramos y usamos
|
||
|
en el fichero de mapeo no son, como podrías esperar, tipos de datos Java.
|
||
|
Tampoco son tipos de base de datos SQL. Estos tipos son los llamados así
|
||
|
<emphasis>Tipos de mapeo de Hibernate</emphasis>, convertidores que pueden
|
||
|
traducir de tipos Java a SQL y vice versa. De nuevo, Hibernate intentará
|
||
|
determinar la conversión y el mapeo mismo de tipo correctos si el atributo
|
||
|
<literal>type</literal> no estuviese presente en el mapeo. En algunos casos esta
|
||
|
detección automática (usando reflección en la clase Java)
|
||
|
puede no tener lo que esperas o necesitas. Este es el caso de la propiedad
|
||
|
<literal>date</literal>. Hibernate no puede saber is la propiedad mapeará
|
||
|
a una columna <literal>date</literal>, <literal>timestamp</literal> o
|
||
|
<literal>time</literal>. Declaramos que queremos preservar la información
|
||
|
completa de fecha y hora mapeando la propiedad con un <literal>timestamp</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Este fichero de mapeo debe ser salvado como <literal>Event.hbm.xml</literal>,
|
||
|
justo en el directorio próximo al fichero de código fuente de
|
||
|
la clase Java <literal>Event</literal>. El nombrado de los ficheros de mapeo
|
||
|
puede ser arbitrario, sin embargo, el sufijo <literal>hbm.xml</literal> se ha
|
||
|
vuelto una convención el la comunidad de desarrolladores de Hibernate.
|
||
|
La estructura de directorio debe ahora verse como esto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[.
|
||
|
+lib
|
||
|
<Hibernate and third-party libraries>
|
||
|
+src
|
||
|
Event.java
|
||
|
Event.hbm.xml]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Continuamos con la configuración principal de Hibernate.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-configuration">
|
||
|
<title>Configuración de Hibernate</title>
|
||
|
|
||
|
<para>
|
||
|
Tenemos ahora una clase persistente y su fichero de mapeo en su sitio. Es momento de
|
||
|
configurar Hibernate. Antes que hagamos esto, necesitaremos una base de datos.
|
||
|
HSQL DB, un DBMS SQL en-memoria basado en Java, puede ser descargado del sitio web
|
||
|
de HSQL DB. Realmente, de esta descarga sólo necesitas el <literal>hsqldb.jar</literal>.
|
||
|
Coloca este fichero en el directorio <literal>lib/</literal> de la carpeta de desarrollo.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Crea un directorio llamado <literal>data</literal> en la raíz del directorio de
|
||
|
desarrollo. Allí es donde HSQL DB almacenará sus ficheros de datos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Hibernate es la capa en tu aplicación que se conecta a esta base de datos,
|
||
|
de modo que necesita información de conexión. Las conexiones se hacen
|
||
|
a través de un pool de conexiones JDBC, que tambén tenemos que configurar.
|
||
|
La distribución de Hibernate contiene muchas herramientas de pooling de conexiones
|
||
|
JDBC de código abierto, pero para este tutorial usaremos el pool de conexiones
|
||
|
prefabricado dentro de Hibernate. Observa que tienes que copiar la biblioteca requerida
|
||
|
en tu classpath y usar diferentes configuraciones de pooling de conexiones si quieres
|
||
|
usar un software de pooling JDBC de terceros de calidad de producción.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Para la configuración de Hibernate, podemos usar un fichero
|
||
|
<literal>hibernate.properties</literal> simple, un fichero <literal>hibernate.cfg.xml</literal>
|
||
|
ligeramente más sofisticado, o incluso una configuración completamente
|
||
|
programática. La mayoría de los usuarios prefieren el fichero de
|
||
|
configuración XML:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<?xml version='1.0' encoding='utf-8'?>
|
||
|
<!DOCTYPE hibernate-configuration PUBLIC
|
||
|
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||
|
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||
|
|
||
|
<hibernate-configuration>
|
||
|
|
||
|
<session-factory>
|
||
|
|
||
|
<!-- Database connection settings -->
|
||
|
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
|
||
|
<property name="connection.url">jdbc:hsqldb:data/tutorial</property>
|
||
|
<property name="connection.username">sa</property>
|
||
|
<property name="connection.password"></property>
|
||
|
|
||
|
<!-- JDBC connection pool (use the built-in) -->
|
||
|
<property name="connection.pool_size">1</property>
|
||
|
|
||
|
<!-- SQL dialect -->
|
||
|
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
|
||
|
|
||
|
<!-- Echo all executed SQL to stdout -->
|
||
|
<property name="show_sql">true</property>
|
||
|
|
||
|
<!-- Drop and re-create the database schema on startup -->
|
||
|
<property name="hbm2ddl.auto">create</property>
|
||
|
|
||
|
<mapping resource="Event.hbm.xml"/>
|
||
|
|
||
|
</session-factory>
|
||
|
|
||
|
</hibernate-configuration>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Observa que esta configuración XML usa un DTD diferente.
|
||
|
Configuramos la <literal>SessionFactory</literal> de Hibernate, una
|
||
|
fábrica global responsable de una base de datos en particular.
|
||
|
Si tienes varias bases de datos, usa varias configuraciones
|
||
|
<literal><session-factory></literal> , usualmente en varios
|
||
|
ficheros de configuración (para un arranque más fácil).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Los primeros cuatro elementos <literal>property</literal> contienen la configuración
|
||
|
necesaria para la conexión JDBC. El elemento de dialecto <literal>property</literal>
|
||
|
especifica la variante de SQL en particular que genera Hibernate. La opción
|
||
|
<literal>hbm2ddl.auto</literal> activa la generación automática de esquemas
|
||
|
de base de datos, directamente en la base de datos. Esto, por supuesto, puede desactivarse
|
||
|
(quitando la opción config) o redirigido a un fichero con la ayuda de la tarea
|
||
|
de Ant <literal>SchemaExport</literal>. Finalmente, agregamos el(los) fichero(s) de mapeo
|
||
|
para clases persistentes.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Copia este fichero dentro del directorio de código fuente, de modo que
|
||
|
termine ubicado en la raiíz del classpath. Hibernate busca automáticamente
|
||
|
un fichero llamado <literal>hibernate.cfg.xml</literal> en la raíz del classpath
|
||
|
al arrancar.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-ant">
|
||
|
<title>Construyendo con Ant</title>
|
||
|
|
||
|
<para>
|
||
|
Construiremos ahora el tutorial con Ant. Necesitarás tener Ant instalado.
|
||
|
Obténlo de <ulink url="http://ant.apache.org/bindownload.cgi">Página
|
||
|
de descarga de Ant</ulink>. No se cubrirá aquí cómo instalar Ant.
|
||
|
Por favor refiérete al <ulink url="http://ant.apache.org/manual/index.html">
|
||
|
Manual de Ant</ulink>. Después que hayas instalado Ant, podemos comenzar a
|
||
|
crear el buildfile. Será llamado <literal>build.xml</literal> y colocado
|
||
|
directamente en el directorio de desarrollo.
|
||
|
</para>
|
||
|
|
||
|
<note>
|
||
|
<title>Reparar Ant</title>
|
||
|
<para>
|
||
|
Observa que la distribución de Ant está por defecto rota
|
||
|
(como se describe en el FAQ de Ant) y tiene que ser reparado por ti,
|
||
|
por ejemplo, si quisieras usar JUnit desde dentro de tu fichero de construcción.
|
||
|
Para hacer que funcione la tarea de JUnit (no lo necesitaremos en este tutorial),
|
||
|
copia junit.jar a <literal>ANT_HOME/lib</literal> o quita el trozo de plugin
|
||
|
<literal>ANT_HOME/lib/ant-junit.jar</literal>.
|
||
|
</para>
|
||
|
</note>
|
||
|
|
||
|
<para>
|
||
|
Un fichero de construcción básico se ve como esto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<project name="hibernate-tutorial" default="compile">
|
||
|
|
||
|
<property name="sourcedir" value="${basedir}/src"/>
|
||
|
<property name="targetdir" value="${basedir}/bin"/>
|
||
|
<property name="librarydir" value="${basedir}/lib"/>
|
||
|
|
||
|
<path id="libraries">
|
||
|
<fileset dir="${librarydir}">
|
||
|
<include name="*.jar"/>
|
||
|
</fileset>
|
||
|
</path>
|
||
|
|
||
|
<target name="clean">
|
||
|
<delete dir="${targetdir}"/>
|
||
|
<mkdir dir="${targetdir}"/>
|
||
|
</target>
|
||
|
|
||
|
<target name="compile" depends="clean, copy-resources">
|
||
|
<javac srcdir="${sourcedir}"
|
||
|
destdir="${targetdir}"
|
||
|
classpathref="libraries"/>
|
||
|
</target>
|
||
|
|
||
|
<target name="copy-resources">
|
||
|
<copy todir="${targetdir}">
|
||
|
<fileset dir="${sourcedir}">
|
||
|
<exclude name="**/*.java"/>
|
||
|
</fileset>
|
||
|
</copy>
|
||
|
</target>
|
||
|
|
||
|
</project>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Esto dirá a Ant que agregue todos los ficheros en el directorio lib que terminen con
|
||
|
<literal>.jar</literal> al classpath usado para la compilación. También copiará
|
||
|
todos los ficheros que no sean código Java al directorio objetivo, por ejemplo,
|
||
|
ficheros de configuración y mapeos de Hibernate. Si ahora corres Ant, debes obtener
|
||
|
esta salida:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant
|
||
|
Buildfile: build.xml
|
||
|
|
||
|
copy-resources:
|
||
|
[copy] Copying 2 files to C:\hibernateTutorial\bin
|
||
|
|
||
|
compile:
|
||
|
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
|
||
|
|
||
|
BUILD SUCCESSFUL
|
||
|
Total time: 1 second ]]></programlisting>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-helpers">
|
||
|
<title>Arranque y ayudantes</title>
|
||
|
|
||
|
<para>
|
||
|
Es momento de cargar y almacenar algunos objetos <literal>Event</literal>,
|
||
|
pero primero tenemos que completar la configuración de algún
|
||
|
código de infraestructura. Tenemos que arrancar Hibernate. Este
|
||
|
arranque incluye construir un objeto <literal>SessionFactory</literal> global
|
||
|
y almacenarlo en algún sitio de fácil acceso en el código
|
||
|
de aplicación. Una <literal>SessionFactory</literal> puede abrir nuevas
|
||
|
<literal>Session</literal>'s. Una <literal>Session</literal> representa un unidad
|
||
|
de trabajo mono-hebra. La <literal>SessionFactory</literal> es un objeto global
|
||
|
seguro entre hebras, instanciado una sola vez.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Crearemos una clase de ayuda <literal>HibernateUtil</literal> que cuide del
|
||
|
arranque y haga conveniente el manejo de <literal>Session</literal>.
|
||
|
El así llamado patrón <emphasis>Sesión de Hebra Local
|
||
|
(ThreadLocal Session)</emphasis> es útil aquí; mantenemos la unidad
|
||
|
de trabajo actual asociada a la hebra actual. Echemos una mirada a la implementación:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[import org.hibernate.*;
|
||
|
import org.hibernate.cfg.*;
|
||
|
|
||
|
public class HibernateUtil {
|
||
|
|
||
|
public static final SessionFactory sessionFactory;
|
||
|
|
||
|
static {
|
||
|
try {
|
||
|
// Create the SessionFactory from hibernate.cfg.xml
|
||
|
sessionFactory = new Configuration().configure().buildSessionFactory();
|
||
|
} catch (Throwable ex) {
|
||
|
// Make sure you log the exception, as it might be swallowed
|
||
|
System.err.println("Initial SessionFactory creation failed." + ex);
|
||
|
throw new ExceptionInInitializerError(ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static final ThreadLocal session = new ThreadLocal();
|
||
|
|
||
|
public static Session currentSession() throws HibernateException {
|
||
|
Session s = (Session) session.get();
|
||
|
// Open a new Session, if this thread has none yet
|
||
|
if (s == null) {
|
||
|
s = sessionFactory.openSession();
|
||
|
// Store it in the ThreadLocal variable
|
||
|
session.set(s);
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
public static void closeSession() throws HibernateException {
|
||
|
Session s = (Session) session.get();
|
||
|
if (s != null)
|
||
|
s.close();
|
||
|
session.set(null);
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Esta clase no ólo produce la <literal>SessionFactory</literal> global en
|
||
|
su inicializador static (llamado sólo una vez por la JVM al cargar la clase),
|
||
|
sino que también tiene una variable <literal>ThreadLocal</literal> para
|
||
|
tener la <literal>Session</literal> para la hebra actual. No importa cuándo
|
||
|
llames a <literal>HibernateUtil.currentSession()</literal>, siempre devolverá
|
||
|
la misma unidad de trabajo de Hibernate en la misma hebra. Una llamada a
|
||
|
<literal>HibernateUtil.closeSession()</literal> termina la unidad de trabajo actualmente
|
||
|
asociada a la hebra.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Asegúrate de entender el concepto Java de una variable local a una hebra antes
|
||
|
de usar esta ayuda. Una clase <literal>HibernateUtil</literal> más potente puede
|
||
|
encontrarse en <literal>CaveatEmptor</literal>, http://caveatemptor.hibernate.org/,
|
||
|
así como en el libro "Hibernate in Action". Observa que esta clase no es necesaria
|
||
|
si despliegas Hibernate en un servidor de aplicaciones J2EE: una <literal>Session</literal>
|
||
|
será automáticamente ligada a la transacción JTA actual, y puedes
|
||
|
buscar la <literal>SessionFactory</literal> a través de JNDI. Si usas JBoss AS,
|
||
|
Hibernate puede ser desplegado como un servicio de sistema manejado y automáticamente
|
||
|
ligará la <literal>SessionFactory</literal> a un nombre JNDI.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Coloca <literal>HibernateUtil.java</literal> en el directorio de fuentes de desarrollo,
|
||
|
junto a <literal>Event.java</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[.
|
||
|
+lib
|
||
|
<Hibernate and third-party libraries>
|
||
|
+src
|
||
|
Event.java
|
||
|
Event.hbm.xml
|
||
|
HibernateUtil.java
|
||
|
hibernate.cfg.xml
|
||
|
+data
|
||
|
build.xml]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Esto también debe compilar sin problemas. Finalmente necesitamos configurar
|
||
|
un sistema de logging (registro). Hibernate usa commons logging y te deja la elección
|
||
|
entre Log4J y logging de JDK 1.4. La mayoría de los desarrolladores prefieren
|
||
|
Log4J: copia <literal>log4j.properties</literal> de la distribución de Hibernate
|
||
|
(está en el directorio <literal>etc/</literal>) a tu directorio <literal>src</literal>,
|
||
|
junto a <literal>hibernate.cfg.xml</literal>. Echa una mirada a la configuración de
|
||
|
ejemplo y cambia los ajustes si te gusta tener una salida más verborrágica.
|
||
|
Por defecto, sólo se muestra el mensaje de arranque de Hibernate en la salida.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La infraestructura del tutorial está completa, y estamos listos para hacer
|
||
|
algún trabajo real con Hibernate.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-firstapp-workingpersistence">
|
||
|
<title>Cargando y almacenando objetos</title>
|
||
|
|
||
|
<para>
|
||
|
Finalmente, podemos usar Hibernate para cargar y almacenar objetos.
|
||
|
Escribimos una clase <literal>EventManager</literal> con un método
|
||
|
<literal>main()</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[import org.hibernate.Transaction;
|
||
|
import org.hibernate.Session;
|
||
|
|
||
|
import java.util.Date;
|
||
|
|
||
|
public class EventManager {
|
||
|
|
||
|
public static void main(String[] args) {
|
||
|
EventManager mgr = new EventManager();
|
||
|
|
||
|
if (args[0].equals("store")) {
|
||
|
mgr.createAndStoreEvent("My Event", new Date());
|
||
|
}
|
||
|
|
||
|
HibernateUtil.sessionFactory.close();
|
||
|
}
|
||
|
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Leemos algunos argumentos de la línea de comandos, y si el primer
|
||
|
argumento es "store", creamos y almacenamos un nuevo Event:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[private void createAndStoreEvent(String title, Date theDate) {
|
||
|
Session session = HibernateUtil.currentSession();
|
||
|
Transaction tx = session.beginTransaction();
|
||
|
|
||
|
Event theEvent = new Event();
|
||
|
theEvent.setTitle(title);
|
||
|
theEvent.setDate(theDate);
|
||
|
|
||
|
session.save(theEvent);
|
||
|
|
||
|
tx.commit();
|
||
|
HibernateUtil.closeSession();
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Creamos un nuevo objeto <literal>Event</literal>, y se lo damos a Hibernate.
|
||
|
Hibernate cuida ahora del SQL y ejecuta <literal>INSERT</literal>s en la base
|
||
|
de datos. Echemos una mirada al código de manejo de <literal>Session</literal>
|
||
|
y <literal>Transaction</literal> antes de ejecutar esto.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Una <literal>Session</literal> es una sola unidad de trabajo. Podría sorprenderte
|
||
|
que tengamos una API adicional, <literal>Transaction</literal>. Esto implica que una unidad
|
||
|
de trabajo puede ser "más larga" que una sola transacción de base de datos;
|
||
|
imagina una unidad de trabajo que se abarca varios ciclos petición/respuesta HTTP
|
||
|
(por ejemplo, un diálogo asistente) en una aplicación web. Separar las
|
||
|
transacciones de base de datos de "las unidades de trabajo de la aplicación desde
|
||
|
el punto de vista del usuario" es uno de los conceptos básicos de diseño de
|
||
|
Hibernate. Llamamos una unidad de trabajo larga <emphasis>Transacción de
|
||
|
Aplicación</emphasis>, usualmente encapsulando varias transacciones de base de
|
||
|
datos más cortas. Por ahora mantendremos las cosas simples y asumiremos una
|
||
|
granularidad uno-a-uno entre una <literal>Session</literal> y una <literal>Transaction</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
¿Qué es lo que hacen <literal>Transaction.begin()</literal> y <literal>commit()</literal>?
|
||
|
¿Dónde está el rollback en caso que algo vaya mal? La API de <literal>Transaction</literal>
|
||
|
de Hibernate es opcional realmente, pero la usamos por conveniencia y portabilidad. Si manejases
|
||
|
la transacción de base de datos por ti mismo (por ejemplo, llamando a
|
||
|
<literal>session.connection.commit()</literal>), ligarías el código a un entorno
|
||
|
de despliegue particular, en este JDBC directo no manejado. Estableciendo la fábrica
|
||
|
de <literal>Transaction</literal> en tu configuración de Hibernate puedes desplegar
|
||
|
tu capa de persistencia en cualquier sitio. Echa una mirada al <xref linkend="transactions"/>
|
||
|
para más información sobre manejo y demarcación de transacciones.
|
||
|
Hemos saltado también cualquier manejo de excepciones y rollback en este ejemplo.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Para ejecutar la primera rutina tenemos que agregar un objetivo llamable al fichero
|
||
|
de construcción de Ant:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<target name="run" depends="compile">
|
||
|
<java fork="true" classname="EventManager" classpathref="libraries">
|
||
|
<classpath path="${targetdir}"/>
|
||
|
<arg value="${action}"/>
|
||
|
</java>
|
||
|
</target>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El valor del argumento <literal>action</literal> es establecido por línea de
|
||
|
comandos al llamar al objetivo:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant run -Daction=store]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Debes ver, después de la compilación, a Hibernate arrancando y, dependiendo
|
||
|
de tu configuración mucha salida de registro (log). Al final encontrarás
|
||
|
la siguiente línea:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Esta es la <literal>INSERT</literal> ejecutada por Hibernate, los signos de preguntas
|
||
|
representan parámetros de ligado JDBC. Para ver los valores ligados como
|
||
|
argumentos, o para reducir la verborragia del registro, chequea tu
|
||
|
<literal>log4j.properties</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Ahora quisiéramos listar acontecimientos almacenados también,
|
||
|
así que agregamos una opción al método principal:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[if (args[0].equals("store")) {
|
||
|
mgr.createAndStoreEvent("My Event", new Date());
|
||
|
}
|
||
|
else if (args[0].equals("list")) {
|
||
|
List events = mgr.listEvents();
|
||
|
for (int i = 0; i < events.size(); i++) {
|
||
|
Event theEvent = (Event) events.get(i);
|
||
|
System.out.println("Event: " + theEvent.getTitle() +
|
||
|
" Time: " + theEvent.getDate());
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Agregamos también un nuevo método <literal>listEvents()</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[private List listEvents() {
|
||
|
Session session = HibernateUtil.currentSession();
|
||
|
Transaction tx = session.beginTransaction();
|
||
|
|
||
|
List result = session.createQuery("from Event").list();
|
||
|
|
||
|
tx.commit();
|
||
|
session.close();
|
||
|
|
||
|
return result;
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Lo que hacemos aquí es usar una consulta HQL (Lenguaje de Consulta de Hibernate
|
||
|
o Hibernate Query Language) para cargar todos los objetos <literal>Event</literal>
|
||
|
existentes de la base de datos. Hibernate generará el SQL apropiado, lo enviará
|
||
|
a la base de datosy poblará los objetos <literal>Event</literal> con datos.
|
||
|
Puedes, por supuesto, crear consultas más complejas con HQL.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Si ahora llamas a Ant con <literal>-Daction=list</literal>, debes ver los eventos
|
||
|
que has almacenado hasta ahora. Puede sorprenderte que esto no funcione, al menos
|
||
|
si has seguido este tutorial paso por paso; el resultado siempre estará
|
||
|
vacío. La razon de esto es la opción <literal>hbm2ddl.auto</literal>
|
||
|
en la configuración de Hibernate: Hibernate recreará la base de datos
|
||
|
en cada ejecución. Deshabilítala quitando la opción, y verás
|
||
|
resultados en tu listado después que llames a la acción <literal>store</literal>
|
||
|
unas cuantas veces. La generación y exportación de esquema es útil
|
||
|
mayormente en testeo unitario.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="tutorial-associations">
|
||
|
<title>Part 2 - Mapeando asociaciones</title>
|
||
|
|
||
|
<para>
|
||
|
Hemos mapeado un clase de entidad persistente a una tabla. Construyamos sobre esto y agreguemos
|
||
|
algunas asociaciones de clase. Primero agregaremos personas a nuestra aplicación,
|
||
|
y almacenaremos una lista de eventos en las que participan.
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="tutorial-associations-mappinguser">
|
||
|
<title>Mapeando la clase Person</title>
|
||
|
|
||
|
<para>
|
||
|
El primer corte de la clase <literal>Person</literal> es simple:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public class Person {
|
||
|
|
||
|
private Long id;
|
||
|
private int age;
|
||
|
private String firstname;
|
||
|
private String lastname;
|
||
|
|
||
|
Person() {}
|
||
|
|
||
|
// Accessor methods for all properties, private setter for 'id'
|
||
|
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Crea un fichero de mapeo llamado <literal>Person.hbm.xml</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||
|
|
||
|
<class name="Person" table="PERSON">
|
||
|
<id name="id" column="PERSON_ID">
|
||
|
<generator class="increment"/>
|
||
|
</id>
|
||
|
<property name="age"/>
|
||
|
<property name="firstname"/>
|
||
|
<property name="lastname"/>
|
||
|
</class>
|
||
|
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Finalmente, agrega el nuevo mapeo a la configuración de Hibernate:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[ <mapping resource="Event.hbm.xml"/>
|
||
|
<mapping resource="Person.hbm.xml"/>
|
||
|
]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Crearemos ahora una asociación entre estas dos entidades. Obviamente,
|
||
|
las personas pueden participar en eventos, y los eventos tienen participantes.
|
||
|
Las cuestiones de diseño con que tenemos que tratar son: direccionalidad,
|
||
|
multiplicidad y comportamiento de colección.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-associations-unidirset">
|
||
|
<title>Una asociación unidireccional basada en Set</title>
|
||
|
|
||
|
<para>
|
||
|
Agregaremos una colección de eventos a la clase <literal>Person</literal>.
|
||
|
De esta forma podemos navegar facilmente a los eventos de una persona en particular,
|
||
|
sin ejecutar una consulta explícita, llamando a <literal>aPerson.getEvents()</literal>.
|
||
|
Usamos una colección Java, un <literal>Set</literal>, porque la colección no
|
||
|
contendrá elementos duplicados y el ordenamiento no nos es relevante.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Hasta ahora hemos diseñado asociaciones unidireccionales multivaluadas, implementadas con un
|
||
|
<literal>Set</literal>. Escribamos el código para esto en las clases Java y luego lo
|
||
|
mapeemos:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public class Person {
|
||
|
|
||
|
private Set events = new HashSet();
|
||
|
|
||
|
public Set getEvents() {
|
||
|
return events;
|
||
|
}
|
||
|
|
||
|
public void setEvents(Set events) {
|
||
|
this.events = events;
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Antes que mapeemos esta asociación, piensa sobre el otro lado. Claramente, podemos
|
||
|
mantener esto solamente unidireccional. O podemos crear otra colección en el
|
||
|
<literal>Event</literal>, si queremos ser capaces de navegarlos bidireccionalmente;
|
||
|
por ejemplo, <literal>anEvent.getParticipants()</literal>. Esta es una elección
|
||
|
de diseño que recae en ti, pero lo que está claro de esta discusión
|
||
|
es la multiplicidad de la asociación: "multi" valuada a ambos lados, llamamos a esto
|
||
|
una asociación <emphasis>muchos-a-muchos</emphasis>. Por lo tanto, usamos un mapeo
|
||
|
many-to-many de Hibernate:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<class name="Person" table="PERSON">
|
||
|
<id name="id" column="PERSON_ID">
|
||
|
<generator class="increment"/>
|
||
|
</id>
|
||
|
<property name="age"/>
|
||
|
<property name="firstname"/>
|
||
|
<property name="lastname"/>
|
||
|
|
||
|
<set name="events" table="PERSON_EVENT">
|
||
|
<key column="PERSON_ID"/>
|
||
|
<many-to-many column="EVENT_ID" class="Event"/>
|
||
|
</set>
|
||
|
|
||
|
</class>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Hibernate soporta todo tipo de mapeos de colección, siendo el más común
|
||
|
un <literal><set></literal>. Para una asociación muchos-a-muchos (o relación
|
||
|
de entidad <emphasis>n:m</emphasis>), se necesita una tabla de asociación. Cada fila en esta
|
||
|
tabla representa un enlace entre una persona y un evento. Esta tabla se configura con el atributo
|
||
|
<literal>table</literal> del elemento <literal>set</literal>. El nombre de la columna identificadora
|
||
|
en la asociación, para el lado de la persona, se define con el elemento
|
||
|
<literal><key></literal>. El nombre de columna para el lado del evento se define con el atributo
|
||
|
<literal>column</literal> del <literal><many-to-many></literal>. También tienes que decirle
|
||
|
a Hibernate la clase de los objetos en tu colección (correcto: la clase del otro lado de la
|
||
|
colección de referencias).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
El esquema de base de datos para este mapeo es, por lo tanto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[
|
||
|
_____________ __________________
|
||
|
| | | | _____________
|
||
|
| EVENTS | | PERSON_EVENT | | |
|
||
|
|_____________| |__________________| | PERSON |
|
||
|
| | | | |_____________|
|
||
|
| *EVENT_ID | <--> | *EVENT_ID | | |
|
||
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
|
||
|
| TITLE | |__________________| | AGE |
|
||
|
|_____________| | FIRSTNAME |
|
||
|
| LASTNAME |
|
||
|
|_____________|
|
||
|
]]></programlisting>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-associations-working">
|
||
|
<title>Trabajando la asociación</title>
|
||
|
|
||
|
<para>
|
||
|
Traigamos alguna gente y eventos juntos en un nuevo método en
|
||
|
<literal>EventManager</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[private void addPersonToEvent(Long personId, Long eventId) {
|
||
|
Session session = HibernateUtil.currentSession();
|
||
|
Transaction tx = session.beginTransaction();
|
||
|
|
||
|
Person aPerson = (Person) session.load(Person.class, personId);
|
||
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
||
|
|
||
|
aPerson.getEvents().add(anEvent);
|
||
|
|
||
|
tx.commit();
|
||
|
HibernateUtil.closeSession();
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Después de cargar una <literal>Person</literal> y un <literal>Event</literal>, simplemente
|
||
|
modifica la colección usando sus métodos normales. Como puedes ver, no hay una llamada
|
||
|
explícita a <literal>update()</literal> o <literal>save()</literal>. Hibernate detecta
|
||
|
automáticamente que la colección ha sido modificada y necesita ser salvada. Esto
|
||
|
es llamado <emphasis>chequeo sucio automótico (automatic dirty checking)</emphasis>, y
|
||
|
también puedes intentarlo modificando el nombre de la propiedad de fecha de cualquiera de tus
|
||
|
objetos. Mientras estén en estado <emphasis>persistente</emphasis>, esto es, ligados a una
|
||
|
<literal>Session</literal> de Hibernate particular (es decir, justo han sido cargados o almacenados
|
||
|
en una unidad de trabajo), Hibernate monitoriza cualquier cambio y ejecuta SQL en estilo
|
||
|
escribe-por-detrás. El proceso de sincronización del estado de memoria con la base
|
||
|
de datos, usualmente sólo al final de una unidad de trabajo,
|
||
|
es llamado <emphasis>limpieza (flushing)</emphasis>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Podrías, por supuesto, cargar persona y evento en unidades de trabajo diferentes. O
|
||
|
modificas un objeto fuera de una <literal>Session</literal>, cuando no está en estado
|
||
|
persistente (si antes era persistente llamamos a este estado <emphasis>separado (detached)
|
||
|
</emphasis>). En código (no muy realista), esto se vería como sigue:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[ private void addPersonToEvent(Long personId, Long eventId) {
|
||
|
|
||
|
Session session = HibernateUtil.currentSession();
|
||
|
Transaction tx = session.beginTransaction();
|
||
|
|
||
|
Person aPerson = (Person) session.load(Person.class, personId);
|
||
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
||
|
|
||
|
tx.commit();
|
||
|
HibernateUtil.closeSession();
|
||
|
|
||
|
aPerson.getEvents().add(anEvent); // aPerson is detached
|
||
|
|
||
|
Session session2 = HibernateUtil.currentSession();
|
||
|
Transaction tx2 = session.beginTransaction();
|
||
|
|
||
|
session2.update(aPerson); // Reattachment of aPerson
|
||
|
|
||
|
tx2.commit();
|
||
|
HibernateUtil.closeSession();
|
||
|
}
|
||
|
]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
La llamada a <literal>update</literal> hace a un objeto persistente de nuevo, podrías
|
||
|
decir que la liga a una nueva unidad de trabajo, de modo que cualquier modificación que
|
||
|
le hagas mientras esté separado puede ser salvada a base de datos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Bueno, esto no es muy usado en nuestra situación actual, pero es un concepto
|
||
|
importante que puedes diseñar en tu propia aplicación. Por ahora, completa
|
||
|
este ejercicio agregando una nueva acción al método main de
|
||
|
<literal>EventManager</literal> y llámala desde la línea de comandos.
|
||
|
Si necesitas los identificadores de una persona o evento, el método
|
||
|
<literal>save()</literal> los devuelve.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Esto fue un ejemplo de una asociación entre dos clases igualmente importantes, dos entidades.
|
||
|
Como se ha mencionado anteriormente, hay otras clases y tipos en un modelo típico,
|
||
|
usualmente "menos importantes". Algunos ya los habrás visto, como un <literal>int</literal>
|
||
|
o un <literal>String</literal>. Llamamos a estas clases <emphasis>tipos de valor (value types)</emphasis>,
|
||
|
y sus instancias <emphasis>dependen</emphasis> de una entidad en particular. Las instancias de estos
|
||
|
tipos no tienen su propia identidad, ni son compartidas entre entidades (dos personas no referencian
|
||
|
el mismo objeto <literal>firstname</literal>, incluso si tuvieran el mismo primer nombre). Por supuesto,
|
||
|
los tipos de valor no sólo pueden encontrarse en el JDK (de hecho, en una aplicación
|
||
|
Hibernate todas las clases del JDK son consideradas tipos de valor), sino que además puedes
|
||
|
escribir por ti mismo clases dependientes, por ejemplo, <literal>Address</literal> o
|
||
|
<literal>MonetaryAmount</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
También puedes diseñar una colección de tipos de valor. Esto es conceptualmente
|
||
|
muy diferente de una colección de referencias a otras entidades, pero se ve casi lo mismo en
|
||
|
Java.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-associations-valuecollections">
|
||
|
<title>Colección de valores</title>
|
||
|
|
||
|
<para>
|
||
|
Agregamos una colección de objetos tipificados en valor a la entidad <literal>Person</literal>.
|
||
|
Queremos almacenar direcciones de email, de modo que el tipo que usamos es <literal>String</literal>,
|
||
|
y la colección es nuevamente un <literal>Set</literal>:
|
||
|
</para>
|
||
|
<programlisting><![CDATA[private Set emailAddresses = new HashSet();
|
||
|
|
||
|
public Set getEmailAddresses() {
|
||
|
return emailAddresses;
|
||
|
}
|
||
|
|
||
|
public void setEmailAddresses(Set emailAddresses) {
|
||
|
this.emailAddresses = emailAddresses;
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El mapeo de este <literal>Set</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
|
||
|
<key column="PERSON_ID"/>
|
||
|
<element type="string" column="EMAIL_ADDR"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
La diferencia comparada con el mapeo anterior es la parte <literal>element</literal>, que le dice
|
||
|
a Hibernate que la colección no contiene referencias a otra entidad, sino una colección
|
||
|
de elementos de tipo <literal>String</literal> (el nombre en minúsculas te dice que es un
|
||
|
tipo/conversor de mapeo de Hibernate). Una vez más, el atributo <literal>table</literal> del
|
||
|
elemento <literal>set</literal> determina el nombre de la tabla para la colección. El elemento
|
||
|
<literal>key</literal> define el nombre de la columna clave foránea en la tabla de colección.
|
||
|
El atributo <literal>column</literal> en el elemento <literal>element</literal> define el nombre de
|
||
|
columna donde realmente serán almacenados los valores <literal>String</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Echa una mirada al esquema actualizado:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[
|
||
|
_____________ __________________
|
||
|
| | | | _____________
|
||
|
| EVENTS | | PERSON_EVENT | | | ___________________
|
||
|
|_____________| |__________________| | PERSON | | |
|
||
|
| | | | |_____________| | PERSON_EMAIL_ADDR |
|
||
|
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
|
||
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
|
||
|
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|
||
|
|_____________| | FIRSTNAME | |___________________|
|
||
|
| LASTNAME |
|
||
|
|_____________|
|
||
|
]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes ver que la clave primaria de la tabla de colección es de hecho una clave
|
||
|
compuesta, usando ambas columnas. Esto implica también que no pueden haber
|
||
|
direcciones de email duplicadas por persona, que es exactamente la semántica
|
||
|
que necesitamos para un conjunto en Java.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Puedes ahora intentar y agregar elementos a esta colección, al igual que
|
||
|
hicimos antes enlazando personas y eventos. Es el mismo código en Java.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-associations-bidirectional">
|
||
|
<title>Asociaciones bidireccionales</title>
|
||
|
|
||
|
<para>
|
||
|
A continuacion vamos a mapear una asociación bidireccional, haciendo que la
|
||
|
asociación entre persona y evento funcione desde ambos lados en Java. Por supuesto,
|
||
|
el esquema de base de datos no cambia; todavía necesitamos multiplicidad muchos-a-muchos.
|
||
|
Una base de datos relacional es más flexible que un lenguaje de programación
|
||
|
de red, así que no necesita nada parecido a una dirección de navegación;
|
||
|
los datos pueden ser vistos y recuperados en cualquier forma posible.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Primero agrega una colección de participantes a la clase de eventos
|
||
|
<literal>Event</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[private Set participants = new HashSet();
|
||
|
|
||
|
public Set getParticipants() {
|
||
|
return participants;
|
||
|
}
|
||
|
|
||
|
public void setParticipants(Set participants) {
|
||
|
this.participants = participants;
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Ahora mapea este lado de la asociación también, en
|
||
|
<literal>Event.hbm.xml</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<set name="participants" table="PERSON_EVENT" inverse="true">
|
||
|
<key column="EVENT_ID"/>
|
||
|
<many-to-many column="PERSON_ID" class="Person"/>
|
||
|
</set>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Como ves, estos son mapeos normales de <literal>set</literal> en ambos documentos de
|
||
|
mapeo. Nota que los nombres de columnas en <literal>key</literal> y
|
||
|
<literal>many-to-many</literal> fueron permutados en ambos documentos de mapeo. Aquí la
|
||
|
adición más importante es el atributo <literal>inverse="true"</literal> en el
|
||
|
elemento <literal>set</literal> del mapeo de colección de <literal>Event</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Lo que esto significa es que Hibernate debe tomar el otro lado - la clase <literal>Person</literal> -
|
||
|
cuando necesite descubrir información sobre el enlace entre las dos. Esto será mucho
|
||
|
más fácil de entender una vez que veas cómo se crea el enlace bidireccional
|
||
|
entre nuestras dos entidades.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="tutorial-associations-usingbidir">
|
||
|
<title>Trabajando enlaces bidireccionales</title>
|
||
|
|
||
|
<para>
|
||
|
Primero, ten en mente que Hhibernate no afecta la semántica normal de Java. ¿Cómo
|
||
|
hemos creado un enlace entre una <literal>Person</literal> y un <literal>Event</literal> en el
|
||
|
ejemplo unidireccional? Hemos agregado una instancia de <literal>Event</literal> a la colección
|
||
|
de referencias de eventos de una instancia de <literal>Person</literal>. De modo que, obviamente,
|
||
|
si queremos que este enlace funcione bidireccionalmente, tenemos que hacer lo mismo del otro lado,
|
||
|
agregando una referencia a <literal>Person</literal> a la colección en un <literal>Event</literal>.
|
||
|
Este "establecer el enlace a ambos lados" es absolutamente necesario y nunca debes olvidar hacerlo.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Muchos desarrolladores programan a la defensiva y crean métodos de
|
||
|
manejo de un enlace para establecer correctamente ambos lados, por ejemplo
|
||
|
en <literal>Person</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[protected Set getEvents() {
|
||
|
return events;
|
||
|
}
|
||
|
|
||
|
protected void setEvents(Set events) {
|
||
|
this.events = events;
|
||
|
}
|
||
|
|
||
|
public void addToEvent(Event event) {
|
||
|
this.getEvents().add(event);
|
||
|
event.getParticipants().add(this);
|
||
|
}
|
||
|
|
||
|
public void removeFromEvent(Event event) {
|
||
|
this.getEvents().remove(event);
|
||
|
event.getParticipants().remove(this);
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Nota que los métodos get y set para esta colección son ahora protegidos. Esto le
|
||
|
permite a clases en el mismo paquete y a subclases acceder aún a los métodos, pero
|
||
|
previene a cualquier otro de ensuciarse con la colección directamente (bueno, casi).
|
||
|
Probablemente debas hacer lo mismo con la colección al otro lado.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Y ¿qué del atributo de mapeo <literal>inverse</literal>? Para ti, y para Java, un enlace
|
||
|
bidireccional es simplemente cuestión de establecer correctamente las referencias a ambos
|
||
|
lados. Hibernate, sin embargo, no tiene suficiente información para arreglar correctamente
|
||
|
sentencias <literal>INSERT</literal> y <literal>UPDATE</literal> de SQL (para evitar violación
|
||
|
de restricciones), y necesita alguna ayuda para manejar asociaciones bidireccionales apropiadamente.
|
||
|
El hacer un lado de la asociación <literal>inverse</literal> le dice a Hibernate que basicamente
|
||
|
lo ignore, que lo considere un <emphasis>espejo</emphasis> del otro lado. Esto es todo lo necesario para
|
||
|
que Hibernate resuelva todas las incidencias al transformar un modelo de navegación direccional a
|
||
|
un esquema SQL de base de datos. Las reglas que tienes que recordar son directas: Todas las asociaciones
|
||
|
bidireccionales necesitan uno de los lados como <literal>inverse</literal>. En una asociación
|
||
|
uno-a-muchos debe ser el lado-de-muchos. En una asociación muchos-a-muchos, puedes tomar cualquier
|
||
|
lado, no hay diferencia.
|
||
|
</para>
|
||
|
<!--
|
||
|
<para>
|
||
|
In the next section we integrate Hibernate with Tomcat and WebWork - the <literal>EventManager</literal>
|
||
|
doesn't scale anymore with our growing application.
|
||
|
</para>
|
||
|
-->
|
||
|
</sect2>
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="tutorial-summary">
|
||
|
<title>Summary</title>
|
||
|
|
||
|
<para>
|
||
|
Este tutorial cubrió los fundamentos de escribir una simple aplicación independiente
|
||
|
de Hibernate.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Si ya te sientes confidente con Hibernate, continúa navegando a través de la
|
||
|
tabla de contenidos de la documentación de referencia para los temas que encuentres
|
||
|
interesantes. Los más consultados son procesamiento transaccional (<xref linkend="transactions"/>),
|
||
|
rendimiento de recuperación (<xref linkend="performance"/>), o el uso de la API
|
||
|
(<xref linkend="objectstate"/>) y las funcionalidades de consulta (<xref linkend="objectstate-querying"/>).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
No olvides chequear el sitio web de Hibernate por más (especializados) tutoriales.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|