somehow this did not get deleted b4
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14112 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
edf6a29cf3
commit
9f527f632d
|
@ -1,279 +0,0 @@
|
|||
<chapter id="architecture">
|
||||
|
||||
<title>Arquitectura</title>
|
||||
|
||||
<sect1 id="architecture-overview" revision="1">
|
||||
<title>Visión General</title>
|
||||
|
||||
<para>
|
||||
Una visión a (muy) alto nivel de la arquitectura de Hibernate:
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/overview.svg" format="SVG" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/overview.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
Este diagrama muestra a Hibernate usando la base de datos y los datos de
|
||||
configuración para proveer servicios de persistencia (y objetos
|
||||
persistentes) a la aplicación.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nos gustaría mostrar una vista más detallada de la arquitectura de tiempo
|
||||
de ejecución. Desafortunadamente, Hibernate es flexible y soporta diferentes
|
||||
enfoques. Mostraremos los dos extremos. En la arquitectura "sencilla", es la
|
||||
aplicación la que provee su propias conexiones JDBC y gestiona sus propias
|
||||
transacciones. Este enfoque usa un mínimo subconjunto de la API de Hibernate:
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/lite.svg" format="SVG" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/lite.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
La arquitectura "full cream" abstrae a la aplicación de las APIs
|
||||
de JDBC/JTA y deja que Hibernate se encargue de los detalles.
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/full_cream.svg" format="SVG" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/full_cream.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
He aquí algunas definiciones de los objetos en los diagramas:
|
||||
<variablelist spacing="compact">
|
||||
<varlistentry>
|
||||
<term>SessionFactory (<literal>org.hibernate.SessionFactory</literal>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Caché threadsafe (inmutable) de mapeos compilados para
|
||||
una sola base de datos. Es una fábrica de <literal>Session</literal>
|
||||
y un cliente de <literal>ConnectionProvider</literal>. Opcionalmente,
|
||||
puede mantener una caché (de segundo nivel) de datos reusables
|
||||
entre transacciones, a un nivel de proceso o de cluster.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Session (<literal>org.hibernate.Session</literal>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Objeto mono-hebra, de corta vida que representa una conversación
|
||||
entre la aplicación y el almacenamiento persistente. Envuelve una
|
||||
conexión JDBC. Es una fábrica de <literal>Transaction</literal>.
|
||||
Mantiene una caché requerida (de primer nivel) de objetos persistentes,
|
||||
usada mientras se navega el grafo de objetos o se recuperen objetos por
|
||||
identificador.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Objetos y colecciones persistentes</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Objetos de corta vida, mono-hebra conteniendo estado persistente y
|
||||
funciónalidad de negocio. Estos pueden ser JavaBeans/POJOs
|
||||
(Plain Old Java Objects, o sea, cualquier objeto Java), la única
|
||||
cosa especial en ellos es que estan asociados actualmente con una
|
||||
(y sólo una) <literal>Session</literal>. Tan pronto como la
|
||||
<literal>Session</literal> sea cerrada, serán separados y
|
||||
estarán libres para ser usados en cualquier capa de aplicación.
|
||||
(por ejemplo, directamente como objetos de transferencia de datos hacia
|
||||
y desde la capa de presentación).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Objetos y colecciones transitorios y separados</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Instancias de clases persistentes que no estan acutualmente asociadas
|
||||
con una <literal>Session</literal>. Pueden haber sido instanciadas por
|
||||
la aplicación y (aún) no haber sido hechas persistentes,
|
||||
o pueden haber sido instanciadas por una <literal>Session</literal> cerrada.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Transaction (<literal>org.hibernate.Transaction</literal>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
(Opcional) Un objeto de corta vida, mono-hebra, usado por la aplicación
|
||||
para especificar unidades atómicas de trabajo. Abstrae a la aplicación
|
||||
de las subyacentes transacciones JDBC, JTA o CORBA. En algunos casos, una
|
||||
<literal>Session</literal> puede extenderse sobre varias <literal>Transaction</literal>s.
|
||||
Sin embargo, la demarcación de la transacción, ya sea usando la API
|
||||
subyacente o <literal>Transaction</literal>, nunca es opcional!
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>ConnectionProvider (<literal>org.hibernate.connection.ConnectionProvider</literal>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
(Opcional) Una fábrica (y pool) de conexiones JDBC. Abstrae a la aplicación
|
||||
del <literal>Datasource</literal> o <literal>DriverManager</literal> subyacente.
|
||||
No se expone a la aplicación, pero puede ser extendido/implementado por
|
||||
el desarrollador.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>TransactionFactory (<literal>org.hibernate.TransactionFactory</literal>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
(Opcional) Una fábrica de instancias de <literal>Transaction</literal>.
|
||||
No se expone a la aplicación, pero puede ser extendido/implementado por
|
||||
el desarrollador.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><emphasis>Interfaces de Extensión</emphasis></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Hibernate ofrece muchas interfaces de extensión opcional que puedes
|
||||
implementar para modificar a medida el comportamiento de tu capa de persistencia.
|
||||
Para más detalles, mira la documentación de la API.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Dada una arquitectura "sencilla", la aplicación pasa por alto las APIs
|
||||
de <literal>Transaction</literal>/<literal>TransactionFactory</literal> y/o
|
||||
<literal>ConnectionProvider</literal>, para hablar directamente a JTA o JDBC.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="architecture-states" revision="1">
|
||||
<title>Estados de instancia</title>
|
||||
<para>
|
||||
Una instancia de una clase persistente puede estar en uno de tres estados
|
||||
diferentes, definidos respecto de su <emphasis>contexto de persistencia</emphasis>.
|
||||
El objeto <literal>Session</literal> de Hibernate es el contexto de persistencia:
|
||||
</para>
|
||||
|
||||
<variablelist spacing="compact">
|
||||
<varlistentry>
|
||||
<term>transitorio</term>
|
||||
<listitem>
|
||||
<para>
|
||||
La instancia no está y nunca estuvo asociada con
|
||||
un contexto de persistencia. No tiene identidad persistente
|
||||
(valor de clave primaria).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>persistente</term>
|
||||
<listitem>
|
||||
<para>
|
||||
La instancia está actualmente asociada con un
|
||||
contexto de persistencia. Tiene una identidad persistente
|
||||
(valor de clave primaria) y, quizás, una fila
|
||||
correspondiente en la base de datos. Para un contexto de
|
||||
persistencia en particular, Hibernate <emphasis>garantiza</emphasis>
|
||||
que la identidad persistente es equivalente a la identidad
|
||||
Java (localización en memoria del objeto).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>separado</term>
|
||||
<listitem>
|
||||
<para>
|
||||
La instancia estuvo una vez asociada con un contexto
|
||||
de persistencia, pero ese contexto fue cerrado, o la
|
||||
instancia fue serializada a otro proceso. Tiene una
|
||||
identidad persistente y, quizás, una fila correspondiente
|
||||
en la base de datos. Para las instancias separadas,
|
||||
Hibernate no establece ninguna garantía sobre
|
||||
la relación entre identidad persistente e identidad Java.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="architecture-jmx" revision="1">
|
||||
<title>Integración JMX</title>
|
||||
|
||||
<para>
|
||||
JMX es el estándar J2EE para la gestión de componentes Java. Hibernate
|
||||
puede ser gestionado por medio de un servicio estándar JMX.
|
||||
Proveemos una implementación de MBean en la distribución,
|
||||
<literal>org.hibernate.jmx.HibernateService</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para ejemplo de cómo desplegar Hibernate como un servicio JMX en un Servidor
|
||||
de Aplicaciones JBoss, por favor, mira la Guía del Usuario de JBoss.
|
||||
En JBoss AS, tienes además estos beneficios si despliegas usando JMX:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis>Gestión de Sesión:</emphasis> El ciclo de vida de la <literal>Session</literal>
|
||||
de Hibernate puede estar automáticamente ligado al ámbito de una transacción
|
||||
JTA. Esto significa que ya no tienes que abrir ni cerrar la <literal>Session</literal> manualmente,
|
||||
esto pasa a ser trabajo de un interceptor EJB de JBoss. Además tampoco tienes
|
||||
que preocuparte más de la demarcación de la transacción (a menos que
|
||||
que quieras escribir una capa de persitencia portable, por supuesto, usa la API de
|
||||
<literal>Transaction</literal> de Hibernate para esto). Para acceder a una
|
||||
<literal>Session</literal> llama al <literal>HibernateContext</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis>Despliegue de HAR:</emphasis> Usualmente despliegas el servicio JMX de Hibernate
|
||||
usando un descriptor de despliegue de servicio de JBoss (en un fichero EAR y/o SAR), que soporta
|
||||
todas las opciones de configuración usuales de una <literal>SessionFactory</literal> de
|
||||
Hibernate. Sin embargo, todavía tienes que nombrar todos tus ficheros de mapeo en el
|
||||
descriptor de despliegue. Si decides usar el depliegue de HAR opcional, JBoss detectará
|
||||
automáticamente todos los ficheros de mapeo en tu fichero HAR.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Para más información sobre estas opciones, consulta la
|
||||
Guía de Usuario del JBoss AS.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Otra funcionalidad disponible como un servicio JMX son las estadísticas en
|
||||
tiempo de ejecución de Hibernate. Mira <xref linkend="configuration-optional-statistics"/>.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="architecture-jca" revision="1">
|
||||
<title>Soporte JCA:</title>
|
||||
<para>
|
||||
Hiberate puede además ser configurado como un conector JCA. Por favor mira el
|
||||
sitio web para más detalles. Por favor ten en cuenta que el soporte de JCA
|
||||
de Hibernate está aún considerado experimental.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,527 +0,0 @@
|
|||
<chapter id="associations">
|
||||
|
||||
<title>Mapeos de Asociación</title>
|
||||
|
||||
<sect1 id="assoc-intro" revision="1">
|
||||
<title>Introducción</title>
|
||||
|
||||
<para>
|
||||
Los mapeos de asociación son frecuentemente las cosas mas difíciles
|
||||
de hacer correctamente. En esta sección iremos a través de los casos
|
||||
canónicos uno a uno, comenzando con los mapeos unidireccionales, y considerando
|
||||
luego los casos bidireccionales. Usaremos <literal>Person</literal> y <literal>Address</literal>
|
||||
en todos los ejemplos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Clasificaremos las asociaciones por cuanto mapeen o no a una tabla
|
||||
de unión interviniente, y por su multiplicidad.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las claves foráneas que aceptan valores nulos (en adelante, nullables)
|
||||
no son consideradas una buena práctica en el modelado tradicional de datos,
|
||||
así que todos nuestros ejemplos usan claves foráneas no nullables.
|
||||
Esto no es un requerimiento de Hibernate, y todos los mapeos funcionarán
|
||||
si quitas las restricciones de nulabilidad.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="assoc-unidirectional" revision="1">
|
||||
<title>Asociaciones Unidireccionales</title>
|
||||
|
||||
<sect2 id="assoc-unidirectional-m21">
|
||||
<title>muchos a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional muchos-a-uno</emphasis> es el tipo
|
||||
más común de asociaciones unidireccionales.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
not-null="true"/>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-unidirectional-121">
|
||||
<title>uno a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional uno-a-uno en una clave primaria</emphasis>
|
||||
es casi idéntica. La única diferencia es la restricción de unicidad
|
||||
de la columna.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
unique="true"
|
||||
not-null="true"/>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
Usualmente, una <emphasis>asociación unidireccional uno-a-uno en una
|
||||
clave primaria</emphasis> usa un generador de id especial. (Observa que hemos
|
||||
invertido el sentido de la asociación en este ejemplo).
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="personId">
|
||||
<generator class="foreign">
|
||||
<param name="property">person</param>
|
||||
</generator>
|
||||
</id>
|
||||
<one-to-one name="person" constrained="true"/>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table Address ( personId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-unidirectional-12m">
|
||||
<title>uno a muchos</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional uno-a-muchos en una clave foránea</emphasis>
|
||||
es un caso muy inusual, y realmente no está recomendada.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="addresses">
|
||||
<key column="personId"
|
||||
not-null="true"/>
|
||||
<one-to-many class="Address"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table Address ( addressId bigint not null primary key, personId bigint not null )
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
Creemos que es mejor usar una tabla de unión para este tipo de asociación.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="assoc-unidirectional-join" revision="1">
|
||||
<title>Asociaciones unidireccionales con tablas de unión</title>
|
||||
|
||||
<sect2 id="assoc-unidirectional-join-12m">
|
||||
<title>uno a muchos</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional uno-a-muchos en una tabla de unión</emphasis>
|
||||
es más preferible. Observa que especificando <literal>unique="true"</literal>, hemos
|
||||
cambiado la multiplicidad de muchos-a-muchos a uno-a-muchos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="addresses" table="PersonAddress">
|
||||
<key column="personId"/>
|
||||
<many-to-many column="addressId"
|
||||
unique="true"
|
||||
class="Address"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId not null, addressId bigint not null primary key )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-unidirectional-join-m21">
|
||||
<title>muchos a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional muchos-a-uno en una tabla de unión</emphasis>
|
||||
es bastante común cuando la asociación es opcional.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<join table="PersonAddress"
|
||||
optional="true">
|
||||
<key column="personId" unique="true"/>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
not-null="true"/>
|
||||
</join>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-unidirectional-join-121">
|
||||
<title>uno a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación unidireccional uno-a-uno en una tabla de unión</emphasis>
|
||||
es inusual en extremo, pero posible.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<join table="PersonAddress"
|
||||
optional="true">
|
||||
<key column="personId"
|
||||
unique="true"/>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
not-null="true"
|
||||
unique="true"/>
|
||||
</join>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-unidirectional-join-m2m">
|
||||
<title>muchos a muchos</title>
|
||||
|
||||
<para>
|
||||
Finalmente, tenemos una <emphasis>asociación unidireccional muchos-a-muchos</emphasis>
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="addresses" table="PersonAddress">
|
||||
<key column="personId"/>
|
||||
<many-to-many column="addressId"
|
||||
class="Address"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="assoc-bidirectional" revision="1">
|
||||
<title>Asociaciones Bidireccionales</title>
|
||||
|
||||
<sect2 id="assoc-bidirectional-m21">
|
||||
<title>uno a muchos / muchos a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación bidireccional muchos-a-uno</emphasis> es
|
||||
el tipo más común de asociación. (Esta es la relación
|
||||
estándar padre/hijo.)
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
not-null="true"/>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="people" inverse="true">
|
||||
<key column="addressId"/>
|
||||
<one-to-many class="Person"/>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-bidirectional-121">
|
||||
<title>uno a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación bidireccional uno-a-uno en una clave foránea</emphasis>
|
||||
es bastante común.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
unique="true"
|
||||
not-null="true"/>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<one-to-one name="person"
|
||||
property-ref="address"/>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación bidireccional uno-a-uno en una clave primaria</emphasis>
|
||||
usa el generador de id especial.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<one-to-one name="address"/>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="personId">
|
||||
<generator class="foreign">
|
||||
<param name="property">person</param>
|
||||
</generator>
|
||||
</id>
|
||||
<one-to-one name="person"
|
||||
constrained="true"/>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table Address ( personId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="assoc-bidirectional-join" revision="1">
|
||||
<title>Asociaciones bidireccionales con tablas de unión</title>
|
||||
|
||||
<sect2 id="assoc-bidirectional-join-12m">
|
||||
<title>uno a muchos / muchos a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación bidireccional uno-a-muchos en una tabla de unión</emphasis>.
|
||||
Observa que el <literal>inverse="true"</literal> puede ir a cualquier lado de la asociación,
|
||||
en la colección, o en la unión.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="addresses"
|
||||
table="PersonAddress">
|
||||
<key column="personId"/>
|
||||
<many-to-many column="addressId"
|
||||
unique="true"
|
||||
class="Address"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<join table="PersonAddress"
|
||||
inverse="true"
|
||||
optional="true">
|
||||
<key column="addressId"/>
|
||||
<many-to-one name="person"
|
||||
column="personId"
|
||||
not-null="true"/>
|
||||
</join>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-bidirectional-join-121">
|
||||
<title>uno a uno</title>
|
||||
|
||||
<para>
|
||||
Una <emphasis>asociación bidireccional uno-a-uno en una tabla de unión</emphasis>
|
||||
es inusual en extremo, pero posible.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<join table="PersonAddress"
|
||||
optional="true">
|
||||
<key column="personId"
|
||||
unique="true"/>
|
||||
<many-to-one name="address"
|
||||
column="addressId"
|
||||
not-null="true"
|
||||
unique="true"/>
|
||||
</join>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<join table="PersonAddress"
|
||||
optional="true"
|
||||
inverse="true">
|
||||
<key column="addressId"
|
||||
unique="true"/>
|
||||
<many-to-one name="address"
|
||||
column="personId"
|
||||
not-null="true"
|
||||
unique="true"/>
|
||||
</join>
|
||||
</class>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="assoc-bidirectional-join-m2m">
|
||||
<title>muchos a muchos</title>
|
||||
|
||||
<para>
|
||||
Finalmente, tenemos una <emphasis>asociación bidireccional muchos-a-muchos</emphasis>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id" column="personId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="addresses">
|
||||
<key column="personId"/>
|
||||
<many-to-many column="addressId"
|
||||
class="Address"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
<id name="id" column="addressId">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<set name="people" inverse="true">
|
||||
<key column="addressId"/>
|
||||
<many-to-many column="personId"
|
||||
class="Person"/>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[
|
||||
create table Person ( personId bigint not null primary key )
|
||||
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||
create table Address ( addressId bigint not null primary key )
|
||||
]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
|
||||
</chapter>
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,192 +0,0 @@
|
|||
<chapter id="batch">
|
||||
<title>Procesamiento por lotes</title>
|
||||
|
||||
<para>
|
||||
Un enfoque ingenuo para insertar 100.000 filas en la base de datos usando Hibernate podría verse así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
for ( int i=0; i<100000; i++ ) {
|
||||
Customer customer = new Customer(.....);
|
||||
session.save(customer);
|
||||
}
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esto podría caer sobre una <literal>OutOfMemoryException</literal> en algún sitio
|
||||
cerca de la fila 50.000. Esto es porque Hibernate tiene en caché todas las instancias
|
||||
de <literal>Customer</literal> recién instanciadas en el caché de nivel de sesión.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
En este capítulo te mostraremos cómo evitar este problema. Primero, sin embargo,
|
||||
si estás haciendo procesamiento por lotes (batch processing), es absolutamente crítico
|
||||
que habilites el uso de loteo JDBC, si pretendes lograr un rendimiento razonable.
|
||||
Establece el tamaño de lote JDBC a un número razonable (digamos 10-50):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
|
||||
|
||||
<para>
|
||||
Podrías además querer hacer este tipo de trabajo en un proceso donde la interacción con el caché de
|
||||
segundo nivel esté completamente deshabilitado:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
|
||||
|
||||
<sect1 id="batch-inserts">
|
||||
<title>Inserciones en lote</title>
|
||||
|
||||
<para>
|
||||
Al hacer persistentes objetos nuevos, debes limpiar con <literal>flush()</literal> y
|
||||
llamar a <literal>clear()</literal> en la sesión regularmente, para controlar el tamaño
|
||||
del caché de primer nivel.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
for ( int i=0; i<100000; i++ ) {
|
||||
Customer customer = new Customer(.....);
|
||||
session.save(customer);
|
||||
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
|
||||
//flush a batch of inserts and release memory:
|
||||
session.flush();
|
||||
session.clear();
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="batch-update" >
|
||||
<title>Actualizaciones en lote</title>
|
||||
|
||||
<para>
|
||||
Para recuperar y actualizar datos se aplican las mismas ideas. Adicionalmente, necesitas usar
|
||||
<literal>scroll()</literal> para sacar ventaja de los cursores del lado del servidor en consultas
|
||||
que devuelvan muchas filas de datos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||
.setCacheMode(CacheMode.IGNORE)
|
||||
.scroll(ScrollMode.FORWARD_ONLY);
|
||||
int count=0;
|
||||
while ( customers.next() ) {
|
||||
Customer customer = (Customer) customers.get(0);
|
||||
customer.updateStuff(...);
|
||||
if ( ++count % 20 == 0 ) {
|
||||
//flush a batch of updates and release memory:
|
||||
session.flush();
|
||||
session.clear();
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="batch-direct">
|
||||
<title>update/delete en masa</title>
|
||||
|
||||
<para>
|
||||
Como ya se ha discutido, el mapeo objeto/relacional automático y transparente se refiere
|
||||
al manejo de estado de objetos. Esto implica que el estado del objeto está disponible
|
||||
en memoria, por lo tanto actualizar o borrar (usando <literal>UPDATE</literal> y
|
||||
<literal>DELETE</literal> de SQL) datos directamente en la base de datos no afectará el
|
||||
estado en memoria. Sin embargo, Hibernate provee métodos para la ejecución de sentencias
|
||||
del estilo de <literal>UPDATE</literal> y <literal>DELETE</literal> de SQL que se realizan
|
||||
a través del Lenguaje de Consulta de Hibernate (Hibernate Query Language o
|
||||
<xref linkend="queryhql">HQL</xref>).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La pseudo-sintáxis para sentencias <literal>UPDATE</literal> y <literal>DELETE</literal> es:
|
||||
<literal>( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?</literal>. Algunos puntos
|
||||
a tener en cuenta:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
En la cláusula-from, la palabra clave FROM es opcional
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Puede haber sólo una clase mencionada en la cláusula-from, y <emphasis>no puede</emphasis>
|
||||
tener un alias.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
No puede especificarse ningún join (bien implícito o explícito) en una consulta masiva de HQL.
|
||||
Pueden usarse subconsultas en la cláusula-where.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
La cláusula-where es también opcional.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Como un ejemplo, para ejecutar un <literal>UPDATE</literal> HQL, usa el
|
||||
método <literal>Query.executeUpdate()</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
String hqlUpdate = "update Customer set name = :newName where name = :oldName";
|
||||
int updatedEntities = s.createQuery( hqlUpdate )
|
||||
.setString( "newName", newName )
|
||||
.setString( "oldName", oldName )
|
||||
.executeUpdate();
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Para ejecutar un <literal>DELETE</literal> HQL, usa el mismo método <literal>Query.executeUpdate()</literal>
|
||||
(el método está nombrado para aquellos familiarizados con
|
||||
<literal>PreparedStatement.executeUpdate()</literal> de JDBC):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
String hqlDelete = "delete Customer where name = :oldName";
|
||||
int deletedEntities = s.createQuery( hqlDelete )
|
||||
.setString( "oldName", oldName )
|
||||
.executeUpdate();
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
El valor <literal>int</literal> devuelto por el método <literal>Query.executeUpdate()</literal>
|
||||
indica el número de entidades afectadas por la operación. Considera que esto puede o no
|
||||
correlacionarse al número de filas afectadas en la base de datos. Una operación masiva HQL podría
|
||||
resultar en que se ejecuten múltiples sentencias de SQL reales, para joined-subclass, por ejemplo.
|
||||
El número devuelto indica el número de entidades reales afectadas por la sentencia. Volviendo al
|
||||
ejemplo de joined-subclass, un borrado contra una de las subclases puede resultar realmente en
|
||||
borrados contra no sólo la tabla a la que está mapeada esa subclase, sino también la tabla "raíz"
|
||||
y potencialmente tablas de joined-subclass más debajo en la jerarquía de herencia.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ten en cuenta que existen actualmente unas pocas limitaciones con las operaciones HQL masivas,
|
||||
que serán atendidas en lanzamientos futuros; consulta la hoja de ruta de JIRA para más detalles.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
|
@ -1,229 +0,0 @@
|
|||
<chapter id="best-practices" revision="3">
|
||||
<title>Mejores Prácticas</title>
|
||||
|
||||
<variablelist spacing="compact">
|
||||
<varlistentry>
|
||||
<term>Escribe clase finamente granularizadas y mapealas usando <literal><component></literal>.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Usa una clase <literal>Dirección</literal> para encapsular <literal>calle</literal>,
|
||||
<literal>distrito</literal>, <literal>estado</literal>, <literal>código postal</literal>.
|
||||
Esto alienta la reutilización de código y simplifica el refactoring.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Declara las propiedades identificadoras en clases persistentes.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Hibernate hace opcionales las propiedades identificadoras. Existen todo tipo de razones
|
||||
por las que debes usarlas. Recomendamos que los identificadores sean 'sintéticos'
|
||||
(generados, sin ningún significado de negocio).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Identifica las claves naturales.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Identifica las claves naturales de todas las entidades, y mapealas usando
|
||||
<literal><natural-id></literal>. Implementa <literal>equals()</literal> y
|
||||
<literal>hashCode()</literal> para comparar las propiedades que componen la clave natural.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Coloca cada mapeo de clase en su propio fichero.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
No uses un solo documento monolítico de mapeo. Mapea <literal>com.eg.Foo</literal> en
|
||||
el fichero <literal>com/eg/Foo.hbm.xml</literal>. Esto tiene sentido particularmente en un
|
||||
ambiente de equipo.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Carga los mapeos como recursos.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Despliega los mapeos junto a las clases que mapean.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Considera externalizar las cadenas de consulta.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Esta es una buena práctica si tus consultas llaman a funciones SQL que no son del
|
||||
estándar ANSI. Externalizar las cadenas de consulta a ficheros de mapeo hará la
|
||||
aplicación más portable.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Usa variables de ligado.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Igual que en JDBC, siempre remplaza valores no constantes con "?". ¡Nunca uses manipulación
|
||||
de cadenas para ligar un valor no constante en una consulta! Incluso mejor, considera usar
|
||||
parámetros con nombre en las consultas.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>No manejes tus propias conexiones JDBC.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Hibernate deja a la aplicación administre las conexiones JDBC. Este enfoque debe considerarse
|
||||
como último recurso. Si no puedes usar los provedores de conexión prefabricados, considera
|
||||
prover tu propia implementación de <literal>org.hibernate.connection.ConnectionProvider</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Considera usar un tipo personalizado.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Supón que tienes un tipo Java, digamos de alguna biblioteca, que necesita hacerse persistente
|
||||
pero no provee los métodos de acceso necesarios para mapearlo como un componente. Debes considerar
|
||||
implementar <literal>org.hibernate.UserType</literal>. Este enfoque libera al código de aplicación
|
||||
de implementar transformaciones a / desde un tipo Hibernate.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Usa JDBC codificado a mano en cuellos de botella.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
En áreas del sistema de rendimiento crítico, algunos tipos de operaciones podrían beneficiarse
|
||||
del JDBC directo. Pero por favor, espero hasta que <emphasis>sepas</emphasis> que algo es
|
||||
un cuello de botella. Y no asumas que el JDBC directo es necesariamente más rápido. Si necesitas
|
||||
usar JDBC directo, podría ser valioso abrir una <literal>Session</literal> de Hibernate y usar esa
|
||||
conexión JDBC. De esta forma puedes usar aún la misma estrategia de transacción y el mismo
|
||||
proveedor de conexiones subyacente.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Comprende la limpieza (flushing) de <literal>Session</literal>.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
De vez en cuando la sesión sincroniza su estado persistente con la base de datos. El rendimiento
|
||||
se verá afectado si este proceso ocurre demasiado frecuentemente. A veces puedes minimizar
|
||||
limpieza innecesaria deshabilitando la limpieza automática o incluso cambiando el orden de las
|
||||
consultas u otras operaciones en una transacción en particular.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>En una aplicación en tres gradas, considera usar objetos separados.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Al usar una arquitectura de servlet / sesión, puedes pasar objetos persistentes en el bean de
|
||||
sesión hacia y desde la capa de servlet / JSP. Usa una sesión nueva para atender el servicio de cada
|
||||
petición. Usa <literal>Session.merge()</literal> o <literal>Session.saveOrUpdate()</literal> para
|
||||
sincronizar los objetos con la base de datos.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>En una arquitectura en dos gradas, considera usar contexto de persistencia largos.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Las transacciones de base de datos tienen que ser tan cortas como sea posible. Sin embargo,
|
||||
frecuentemente es necesario implementar <emphasis>transacciones de aplicación</emphasis>
|
||||
ejecutándose en largo, una sola unidad de trabajo desde el punto de vista de un usuario.
|
||||
Una transacción de aplicación puede abarcar muchos ciclos petición/respuesta del cliente.
|
||||
Es común usar objetos separados para implementar transacciones de aplicación. Una alternativa,
|
||||
extremadamente apropiada en arquitecturas en dos gradas, es mantener un solo contacto de persistencia
|
||||
abierto (sesión) para todo el ciclo de vida de la transacción de aplicación y simplemente
|
||||
desconectar de la conexión JDBC al final de cada petición, y reconectar al comienzo de la
|
||||
petición subsecuente. Nunca compartas una única sesión a través de más de una transacción
|
||||
de aplicación, o estarás trabajando con datos añejos.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>No trates la excepciones como recuperables.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Esto es más una práctica necesaria que una "mejor" práctica. Cuando ocurra una excepción,
|
||||
deshaz (rollback) la <literal>Transaction</literal> y cierra la <literal>Session</literal>.
|
||||
Si no lo haces, Hibernate no puede garantizar que el estado en memoria representa con exactitud
|
||||
el estado persistente. Como un caso especial de esto, no uses <literal>Session.load()</literal>
|
||||
para determinar si una instancia con el identificador dado existe en la base de datos. En cambio,
|
||||
usa <literal>Session.get()</literal> o una consulta.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Prefiere la recuperación perezosa para las asociaciones.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Usa escasamente la recuperación temprana. Usa proxies y colecciones perezosas para la mayoría
|
||||
de asociaciones a clases probablemente no estén mantenidas en el caché de segundo nivel. Para
|
||||
las asociaciones a clases en caché, donde hay una probabilidad de acceso a caché extremadamente
|
||||
alta, deshabilita explícitamente la recuperación temprana usando <literal>lazy="false"</literal>.
|
||||
Cuando sea apropiada la recuperación por unión (join fetching) para un caso de uso en particular,
|
||||
usa una consulta con un <literal>left join fetch</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
Usa el patrón <emphasis>sesión abierta en vista</emphasis>, o una <emphasis>fase de ensamblado</emphasis>
|
||||
disciplinada para evitar problemas con datos no recuperados.
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Hibernate liberal al desarrollador de escribir <emphasis>Objetos de Transferencia de Datos
|
||||
(Data Transfer Objects)</emphasis> (DTO). En una arquitectura tradicional de EJB, los DTOs tienen
|
||||
un propósito doble: primero, atacan el problema que los beans de entidad no son serializables.
|
||||
Segundo, definen implícitamente una fase de ensamblado cuando se recuperan y se forman (marshalling)
|
||||
todos los datos a usar por la vista en los DTOs antes de devolver el control a la grada de
|
||||
presentación. Hibernate elimina el primer propósito. Sin embargo, aún necesitas una fase
|
||||
de ensamblado (piensa en tus métodos de negocio como si tuviesen un contrato estricto con la grada
|
||||
de presentación sobre qué datos están disponibles en los objetos separados) a menos que estés
|
||||
preparado para tener el contexto de persistencia (la sesión) abierto a través del proceso
|
||||
de renderización de la vista. ¡Esta no es una limitación de Hibernate! Es un requerimiento
|
||||
fundamental de acceso seguro a datos transaccionales.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Considera abstraer tu lógica de negocio de Hibernate</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Oculta el código de acceso a datos (Hibernate) detrás de una interface. Combina los patrones
|
||||
<emphasis>DAO</emphasis> y <emphasis>Sesión de Hebra Local</emphasis>. Incluso puedes tener
|
||||
algunas clases hechas persistentes por JDBC escrito a mano, asociadas a Hibernate por medio
|
||||
de un <literal>UserType</literal>. (Este consejo está pensado para aplicaciones "suficientemente
|
||||
grandes"; ¡no es apropiado para una aplicación con cinco tablas!)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>No uses mapeos de asociación exóticos.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Son raros los casos de uso de asociaciones reales muchos-a-muchos. La mayor parte del tiempo
|
||||
necesitas información adicional almacenada en una "tabla de enlace". En este caso, es mucho
|
||||
mejor usar dos asociaciones uno-a-muchos a una clase de enlace intermedia. De hecho, pensamos
|
||||
que la mayoría de asociaciones son uno-a-muchos y muchos-a-uno, debes ser cuidadoso al usr
|
||||
cualquier otro estilo de asociación y preguntarte si es realmente necesario.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Prefiere las asociaciones bidireccionales.</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Las asociaciones unidireccionales son más difíciles de consultar. En una aplicación grande,
|
||||
casi todas las asociaciones deben ser navegables en ambas direcciones en consultas.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
</chapter>
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,403 +0,0 @@
|
|||
<chapter id="components">
|
||||
<title>Mapeo de Componentes</title>
|
||||
|
||||
<para>
|
||||
La noción de un <emphasis>componente</emphasis> es reusada en muchos contextos diferentes,
|
||||
para propósitos diferentes, a través de Hibernate.
|
||||
</para>
|
||||
|
||||
<sect1 id="components-dependentobjects">
|
||||
<title>Objetos dependientes</title>
|
||||
|
||||
<para>
|
||||
Un componente es un objeto contenido que es persistido como un tipo de valor, no una
|
||||
referencia de entidad. El término "componente" hace referencia a la noción orientada a
|
||||
objetos de composición (no a componentes a nivel de arquitectura). Por ejemplo, podrías
|
||||
modelar una persona como:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public class Person {
|
||||
private java.util.Date birthday;
|
||||
private Name name;
|
||||
private String key;
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
private void setKey(String key) {
|
||||
this.key=key;
|
||||
}
|
||||
public java.util.Date getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
public void setBirthday(java.util.Date birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
public Name getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(Name name) {
|
||||
this.name = name;
|
||||
}
|
||||
......
|
||||
......
|
||||
}]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[public class Name {
|
||||
char initial;
|
||||
String first;
|
||||
String last;
|
||||
public String getFirst() {
|
||||
return first;
|
||||
}
|
||||
void setFirst(String first) {
|
||||
this.first = first;
|
||||
}
|
||||
public String getLast() {
|
||||
return last;
|
||||
}
|
||||
void setLast(String last) {
|
||||
this.last = last;
|
||||
}
|
||||
public char getInitial() {
|
||||
return initial;
|
||||
}
|
||||
void setInitial(char initial) {
|
||||
this.initial = initial;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ahora <literal>Name</literal> puede ser persistido como un componente de
|
||||
<literal>Person</literal>. Observa que <literal>Name</literal> define métodos
|
||||
getter y setter para sus propiedades persistentes, pero no necesita declarar
|
||||
ninguna interface ni propiedades identificadoras.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nuestro mapeo de Hibernate se vería así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||
<id name="Key" column="pid" type="string">
|
||||
<generator class="uuid.hex"/>
|
||||
</id>
|
||||
<property name="birthday" type="date"/>
|
||||
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
|
||||
<property name="initial"/>
|
||||
<property name="first"/>
|
||||
<property name="last"/>
|
||||
</component>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
La tabla person tendría las columnas <literal>pid</literal>,
|
||||
<literal>birthday</literal>,
|
||||
<literal>initial</literal>,
|
||||
<literal>first</literal> y
|
||||
<literal>last</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Como todos los tipos de valor, los componentes no soportan referencias compartidas.
|
||||
En otras palabras, dos personas pueden tener el mismo nombre, pero los dos objetos
|
||||
persona contendrían dos objetos nombre independientes, sólo "iguales" en valor.
|
||||
La semántica de valor nulo de un componente es <emphasis>ad hoc</emphasis>.
|
||||
Cuando se recargue el objeto contenedor, Hibernate asumirá que si todas las columnas del
|
||||
componente son nulas, el componente entero es nulo. Esto debe estar bien para la mayoría
|
||||
de propósitos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las propiedades de un componentes pueden ser de cualquier tipo de Hibernate
|
||||
(colecciones, muchos-a-uno, asociaciones, otros componentes, etc). Los componentes
|
||||
anidados <emphasis>no</emphasis> deben ser considerados un uso exótico. Hibernate está
|
||||
concebido para soportar un modelo de objetos granularizado en fino.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El elemento <literal><component></literal> permite un subelemento
|
||||
<literal><parent></literal> que mapee una propiedad de la clase del componente
|
||||
como una referencia de regreso a la entidad contenedora.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||
<id name="Key" column="pid" type="string">
|
||||
<generator class="uuid.hex"/>
|
||||
</id>
|
||||
<property name="birthday" type="date"/>
|
||||
<component name="Name" class="eg.Name" unique="true">
|
||||
<parent name="namedPerson"/> <!-- reference back to the Person -->
|
||||
<property name="initial"/>
|
||||
<property name="first"/>
|
||||
<property name="last"/>
|
||||
</component>
|
||||
</class>]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="components-incollections" revision="1">
|
||||
<title>Colecciones de objetos dependientes</title>
|
||||
|
||||
<para>
|
||||
Las colecciones de componentes están soportadas (por ejemplo,
|
||||
un array de tipo <literal>Name</literal>). Declara tu colección
|
||||
de componentes remplazando la etiqueta <literal><element></literal>
|
||||
por una etiqueta <literal><composite-element></literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="someNames" table="some_names" lazy="true">
|
||||
<key column="id"/>
|
||||
<composite-element class="eg.Name"> <!-- class attribute required -->
|
||||
<property name="initial"/>
|
||||
<property name="first"/>
|
||||
<property name="last"/>
|
||||
</composite-element>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota: si defines un <literal>Set</literal> de elementos compuestos, es muy
|
||||
importante implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||
correctamente.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los elementos compuestos pueden contener componentes pero no colecciones.
|
||||
Si tu elemento compuesto contiene a su vez componentes, usa la etiqueta
|
||||
<literal><nested-composite-element></literal>. Este es un caso bastante
|
||||
exótico - una colección de componentes que a su vez tienen componentes. A esta
|
||||
altura debes estar preguntándote si una asociación uno-a-muchos es más
|
||||
apropiada. Intenta remodelar el elemento compuesto como una entidad - pero
|
||||
observa que aunque el modelo Java es el mismo, el modelo relacional y la
|
||||
semántica de persistencia siguen siendo ligeramente diferentes.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Por favor observa que un mapeo de elemento compuesto no soporta
|
||||
propiedades nulables si estás usando un <literal><set></literal>.
|
||||
Hibernate tiene que usar cada columna para identificar un registro
|
||||
al borrar objetos (no hay una columna clave primaria separada en la tabla del
|
||||
elemento compuesto), lo que es imposible con valores nulos. Tienes que, o bien usar
|
||||
sólo propiedades no nulas en un elemento compuesto o elegir un
|
||||
<literal><list></literal>, <literal><map></literal>,
|
||||
<literal><bag></literal> o <literal><idbag></literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Un caso especial de un elemento compuesto es un elemento compuesto con un
|
||||
elemento anidado <literal><many-to-one></literal>. Un mapeo como este
|
||||
te permite mapear columnas extra de una tabla de asociación muchos-a-muchos
|
||||
a la clase del elemento compuesto. La siguiente es una asociación muchos-a-muchos
|
||||
de <literal>Order</literal> a <literal>Item</literal> donde
|
||||
<literal>purchaseDate</literal>, <literal>price</literal> y
|
||||
<literal>quantity</literal> son propiedades de la asociación:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||
....
|
||||
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||
<key column="order_id">
|
||||
<composite-element class="eg.Purchase">
|
||||
<property name="purchaseDate"/>
|
||||
<property name="price"/>
|
||||
<property name="quantity"/>
|
||||
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
|
||||
</composite-element>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Por supuesto, no puede haber una referencia a la compra del otro lado para la
|
||||
navegación bidireccional de la asociación. Recuerda que los componentes son tipos de
|
||||
valor no permiten referencias compartidas. Una sola <literal>Purchase</literal> puede
|
||||
estar en el conjunto de una <literal>Order</literal>, pero no puede ser referenciada
|
||||
por el <literal>Item</literal> al mismo tiempo.
|
||||
</para>
|
||||
|
||||
<para>Incluso son posibles las asociaciones ternarias (o cuaternarias, etc):</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||
....
|
||||
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||
<key column="order_id">
|
||||
<composite-element class="eg.OrderLine">
|
||||
<many-to-one name="purchaseDetails class="eg.Purchase"/>
|
||||
<many-to-one name="item" class="eg.Item"/>
|
||||
</composite-element>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Los elementos compuestos pueden aparecer en consultas usando la misma
|
||||
sintáxis que las asociaciones a otras entidades.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="components-asmapindex">
|
||||
<title>Componentes como índices de Map</title>
|
||||
|
||||
<para>
|
||||
El elemento <literal><composite-map-key></literal> te permite mapear
|
||||
una clase componente como la clave de un <literal>Map</literal>. Asegúrate que
|
||||
sobrescribes <literal>hashCode()</literal> y <literal>equals()</literal>
|
||||
correctamente en la clase componente.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="components-compositeid" revision="1">
|
||||
<title>Componentes como identificadores compuestos</title>
|
||||
|
||||
<para>
|
||||
Puedes usar un componente como un identidicador de una clase entidad. Tu clase
|
||||
componente debe satisfacer ciertos requerimientos:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
Debe implementar <literal>java.io.Serializable</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Debe re-implementar <literal>equals()</literal> y
|
||||
<literal>hashCode()</literal>, consistentemente con la
|
||||
noción de base de datos de igualdad de clave compuesta.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
<emphasis>Nota: en Hibernat3, el segundo requerimiento no es absolutamente un
|
||||
requerimiento rígido de Hibernate. Pero de todas formas, házlo.</emphasis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
No puedes usar un <literal>IdentifierGenerator</literal> para generar claves
|
||||
compuestas. La aplicación debe, en cambio, asignar sus propios identificadores.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Usa la etiqueta <literal><composite-id></literal> (con elementos
|
||||
anidados <literal><key-property></literal>) en lugar de la usual
|
||||
declaración <literal><id></literal>. Por ejemplo, la clase
|
||||
<literal>OrderLine</literal> tiene una clave primaria que depende de
|
||||
la clave primaria (compuesta) de <literal>Order</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="OrderLine">
|
||||
|
||||
<composite-id name="id" class="OrderLineId">
|
||||
<key-property name="lineId"/>
|
||||
<key-property name="orderId"/>
|
||||
<key-property name="customerId"/>
|
||||
</composite-id>
|
||||
|
||||
<property name="name"/>
|
||||
|
||||
<many-to-one name="order" class="Order"
|
||||
insert="false" update="false">
|
||||
<column name="orderId"/>
|
||||
<column name="customerId"/>
|
||||
</many-to-one>
|
||||
....
|
||||
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ahora, cualquier clave foránea que referencie la tabla de <literal>OrderLine</literal>
|
||||
es también compuesta. Debes declarar esto en tus mapeos de otras clases. Una asociación
|
||||
a <literal>OrderLine</literal> sería mapeado así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<many-to-one name="orderLine" class="OrderLine">
|
||||
<!-- the "class" attribute is optional, as usual -->
|
||||
<column name="lineId"/>
|
||||
<column name="orderId"/>
|
||||
<column name="customerId"/>
|
||||
</many-to-one>]]></programlisting>
|
||||
|
||||
<para>
|
||||
(Nota que la etiqueta <literal><column></literal> es una alternativa al
|
||||
atributo <literal>column</literal> en cualquier sitio.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una asociación <literal>muchos-a-muchos</literal> a <literal>OrderLine</literal>
|
||||
también usa la clave foránea compuesta:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="undeliveredOrderLines">
|
||||
<key column name="warehouseId"/>
|
||||
<many-to-many class="OrderLine">
|
||||
<column name="lineId"/>
|
||||
<column name="orderId"/>
|
||||
<column name="customerId"/>
|
||||
</many-to-many>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
La colección de <literal>OrderLine</literal>s en <literal>Order</literal> usaría:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="orderLines" inverse="true">
|
||||
<key>
|
||||
<column name="orderId"/>
|
||||
<column name="customerId"/>
|
||||
</key>
|
||||
<one-to-many class="OrderLine"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
(El elemento <literal><one-to-many></literal>, como es usual, no declara columnas.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si <literal>OrderLine</literal> posee una colección por sí misma, tiene también
|
||||
una clave foránea compuesta.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="OrderLine">
|
||||
....
|
||||
....
|
||||
<list name="deliveryAttempts">
|
||||
<key> <!-- a collection inherits the composite key type -->
|
||||
<column name="lineId"/>
|
||||
<column name="orderId"/>
|
||||
<column name="customerId"/>
|
||||
</key>
|
||||
<list-index column="attemptId" base="1"/>
|
||||
<composite-element class="DeliveryAttempt">
|
||||
...
|
||||
</composite-element>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="components-dynamic" revision="1">
|
||||
<title>Componentes dinámicos</title>
|
||||
|
||||
<para>
|
||||
Puedes incluso mapear una propiedad de tipo <literal>Map</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<dynamic-component name="userAttributes">
|
||||
<property name="foo" column="FOO" type="string"/>
|
||||
<property name="bar" column="BAR" type="integer"/>
|
||||
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
|
||||
</dynamic-component>]]></programlisting>
|
||||
|
||||
<para>
|
||||
La semántica de un mapeo <literal><dynamic-component></literal> es ídentica
|
||||
a la de <literal><component></literal>. La ventaja de este tipo de mapeos es
|
||||
la habilidad para determinar las propiedades reales del bean en tiempo de despliegue,
|
||||
sólo con editar el documento de mapeo. La manipulación del documento de mapeo en tiempo
|
||||
de ejecución es también posible, usando un analizador DOM. Incluso mejor, puedes acceder
|
||||
(y cambiar) el metamodelo de tiempo de configuración de Hibernate por medio del objeto
|
||||
<literal>Configuration</literal>.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,233 +0,0 @@
|
|||
<chapter id="events">
|
||||
<title>Interceptores y eventos</title>
|
||||
|
||||
<para>
|
||||
Frecuentemente es útil para la aplicación reaccionar a ciertos eventos que ocurran dentro de Hibernate.
|
||||
Esto permite la implementación de ciertos tipos de funcionalidade genérica, y extensión de la
|
||||
funcionalidad de Hibernate.
|
||||
</para>
|
||||
|
||||
<sect1 id="objectstate-interceptors" revision="1">
|
||||
<title>Interceptores</title>
|
||||
|
||||
<para>
|
||||
La interface <literal>Interceptor</literal> provee callbacks desde la sesión a la aplicación
|
||||
permitiendo a ésta última inspeccionar y/o manipular las propiedades de un objeto persistente
|
||||
antes que sea salvado, actualizado, borrado o cargado. Un uso posible de esto es seguir la pista
|
||||
de información de auditoría. Por ejemplo, el siguiente <literal>Interceptor</literal> establece
|
||||
automáticamente el <literal>createTimestamp</literal> cuando un <literal>Auditable</literal> es
|
||||
creado y actualiza la propiedad <literal>lastUpdateTimestamp</literal> cuando un
|
||||
<literal>Auditable</literal> es acutalizado.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package org.hibernate.test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.hibernate.Interceptor;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
public class AuditInterceptor implements Interceptor, Serializable {
|
||||
|
||||
private int updates;
|
||||
private int creates;
|
||||
|
||||
public void onDelete(Object entity,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public boolean onFlushDirty(Object entity,
|
||||
Serializable id,
|
||||
Object[] currentState,
|
||||
Object[] previousState,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
|
||||
if ( entity instanceof Auditable ) {
|
||||
updates++;
|
||||
for ( int i=0; i < propertyNames.length; i++ ) {
|
||||
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||||
currentState[i] = new Date();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onLoad(Object entity,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onSave(Object entity,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
|
||||
if ( entity instanceof Auditable ) {
|
||||
creates++;
|
||||
for ( int i=0; i<propertyNames.length; i++ ) {
|
||||
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||||
state[i] = new Date();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void postFlush(Iterator entities) {
|
||||
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
||||
}
|
||||
|
||||
public void preFlush(Iterator entities) {
|
||||
updates=0;
|
||||
creates=0;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
El interceptor podría ser especificado cuando se crea la sesión:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||||
|
||||
<para>
|
||||
Puedes además establecer un interceptor a un nivel global, usando la <literal>Configuration</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="objectstate-events" revision="2">
|
||||
<title>Sistema de eventos</title>
|
||||
|
||||
<para>
|
||||
Si tienes que reaccionar a eventos particulares en tu capa de persistencia, puedes también la
|
||||
arquitectura de <emphasis>eventos</emphasis> de Hibernate3. El sistema de eventos puede ser usado
|
||||
en adición o como un remplazo a los interceptores.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Esencialmente todos los métodos de la interface <literal>Session</literal> se correlacionan
|
||||
con un evento. Tienes un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
|
||||
(consulta el DTD del fichero de configuración XML o el paquete <literal>org.hibernate.event</literal>
|
||||
para la lista completa de tipos de evento definidos). Cuando se hace una petición de uno de estos
|
||||
métodos, la <literal>Session</literal> de Hibernate genera un evento apropiado y se lo pasa
|
||||
al oyente (listener) de eventos configurado para ese tipo. De fábrica, estos oyentes implementan
|
||||
el mismo procesamiento en los que siempre resultan aquellos métodos. Sin embargo, eres libre de
|
||||
implementar una personalización de una de las interfaces oyentes (es decir, el
|
||||
<literal>LoadEvent</literal> es procesado por la implementación registrada de la interface
|
||||
<literal>LoadEventListener</literal>), en cuyo caso su implementación sería responsable
|
||||
de procesar cualquier petición <literal>load()</literal> hecha a la <literal>Session</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los oyentes deben ser considerados efectivamente singletons; quiere decir, que son compartidos
|
||||
entre las peticiones, y por lo tanto no guardan ningún estado en variables de instancia.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Un oyente personalizado debe implementar la interface apropiada para el evento que quiere procesar y/o
|
||||
extender una de las clases base de conveniencia (o incluso los oyentes de eventos por defecto
|
||||
usados por Hibernate de fábrica al ser éstos declarados non-final para este propósito). Los
|
||||
oyentes personalizados pueden ser registrados programáticamente a través del objeto
|
||||
<literal>Configuration</literal>, o especificados en el XML de configuración de Hibernate
|
||||
(la declaración declarativa a través del fichero de propiedades no está soportada).
|
||||
He aquí un ejemplo de un oyente personalizado de eventos load:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public class MyLoadListener extends DefaultLoadEventListener {
|
||||
// this is the single method defined by the LoadEventListener interface
|
||||
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||||
throws HibernateException {
|
||||
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||||
throw MySecurityException("Unauthorized access");
|
||||
}
|
||||
return super.onLoad(event, loadType);
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Necesitas además una entrada de configuración diciéndole a Hibernate que use el
|
||||
oyente en vez del oyente por defecto:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<hibernate-configuration>
|
||||
<session-factory>
|
||||
...
|
||||
<listener type="load" class="MyLoadListener"/>
|
||||
</session-factory>
|
||||
</hibernate-configuration>]]></programlisting>
|
||||
|
||||
<para>
|
||||
En cambio, puedes registrarlo programáticamente:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||||
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );]]></programlisting>
|
||||
|
||||
<para>
|
||||
Los oyentes registrados declarativamente no pueden compartir instancias. Si el mismo nombre de clase es
|
||||
usado en múltiples elementos <literal><listener/></literal>, cada referencia resultará en una instancia
|
||||
separada de esa clase. Si necesitas la capacidad de compartir instancias de oyentes entre tipos de oyente
|
||||
debes usar el enfoque de registración programática.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
¿Por qué implementar una interface y definir el tipo espcífico durante la configuración?
|
||||
Bueno, una implementación de oyente podría implementar múltiples interfaces de oyente
|
||||
de eventos. Teniendo el tipo definido adicionalmente durante la registración lo hace más
|
||||
fácil para activar o desactivar oyentes personalizados durante la configuración.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="objectstate-decl-security">
|
||||
<title>Seguridad declarativa de Hibernate</title>
|
||||
<para>
|
||||
Usualmente, la seguridad declarativa en aplicaciones Hibernate es manejada en una capa de fachada
|
||||
de sesión. Ahora, Hibernate3 permite que ciertas acciones sean permitidas vía JACC, y autorizadas vía
|
||||
JAAS. Esta en una funcionalidad opcional construída encima de la arquitectura de eventos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Primero, debes configurar los oyentes de eventos apropiados, para habilitar el uso de
|
||||
autorización JAAS.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||||
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||||
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||||
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Seguido, aún en <literal>hibernate.cfg.xml</literal>, liga los permisos a roles:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||||
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Los nombres de role son los roles entendidos por tu proveedor de JACC.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,654 +0,0 @@
|
|||
<chapter id="example-mappings">
|
||||
<title>Ejemplo: Varios Mapeos</title>
|
||||
|
||||
<para>
|
||||
Este capítulo muestra mapeos de asociaciones más complejos.
|
||||
</para>
|
||||
|
||||
<sect1 id="example-mappings-emp">
|
||||
<title>Empleador/Empleado</title>
|
||||
|
||||
<para>
|
||||
El siguiente modelo de la relación entre <literal>Employer</literal> y <literal>Employee</literal>
|
||||
usa una clase de entidad real (<literal>Employment</literal>) para representar la asociación.
|
||||
Esto se ha hecho esto porque podría haber más de un período de empleo para los mismos dos participantes.
|
||||
Se usan componentes para modelar valores monetarios y nombres de empleado.
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
He aquí un documento de mapeo posible:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<hibernate-mapping>
|
||||
|
||||
<class name="Employer" table="employers">
|
||||
<id name="id">
|
||||
<generator class="sequence">
|
||||
<param name="sequence">employer_id_seq</param>
|
||||
</generator>
|
||||
</id>
|
||||
<property name="name"/>
|
||||
</class>
|
||||
|
||||
<class name="Employment" table="employment_periods">
|
||||
|
||||
<id name="id">
|
||||
<generator class="sequence">
|
||||
<param name="sequence">employment_id_seq</param>
|
||||
</generator>
|
||||
</id>
|
||||
<property name="startDate" column="start_date"/>
|
||||
<property name="endDate" column="end_date"/>
|
||||
|
||||
<component name="hourlyRate" class="MonetaryAmount">
|
||||
<property name="amount">
|
||||
<column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
|
||||
</property>
|
||||
<property name="currency" length="12"/>
|
||||
</component>
|
||||
|
||||
<many-to-one name="employer" column="employer_id" not-null="true"/>
|
||||
<many-to-one name="employee" column="employee_id" not-null="true"/>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Employee" table="employees">
|
||||
<id name="id">
|
||||
<generator class="sequence">
|
||||
<param name="sequence">employee_id_seq</param>
|
||||
</generator>
|
||||
</id>
|
||||
<property name="taxfileNumber"/>
|
||||
<component name="name" class="Name">
|
||||
<property name="firstName"/>
|
||||
<property name="initial"/>
|
||||
<property name="lastName"/>
|
||||
</component>
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Y he aquí el esquema de tablas generado por <literal>SchemaExport</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[create table employers (
|
||||
id BIGINT not null,
|
||||
name VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table employment_periods (
|
||||
id BIGINT not null,
|
||||
hourly_rate NUMERIC(12, 2),
|
||||
currency VARCHAR(12),
|
||||
employee_id BIGINT not null,
|
||||
employer_id BIGINT not null,
|
||||
end_date TIMESTAMP,
|
||||
start_date TIMESTAMP,
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table employees (
|
||||
id BIGINT not null,
|
||||
firstName VARCHAR(255),
|
||||
initial CHAR(1),
|
||||
lastName VARCHAR(255),
|
||||
taxfileNumber VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
alter table employment_periods
|
||||
add constraint employment_periodsFK0 foreign key (employer_id) references employers
|
||||
alter table employment_periods
|
||||
add constraint employment_periodsFK1 foreign key (employee_id) references employees
|
||||
create sequence employee_id_seq
|
||||
create sequence employment_id_seq
|
||||
create sequence employer_id_seq]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-mappings-authorwork">
|
||||
<title>Autor/Obra</title>
|
||||
|
||||
<para>
|
||||
Considera el siguiente modelo de las relaciones entre <literal>Work</literal>,
|
||||
<literal>Author</literal> y <literal>Person</literal>. Representamos la relación entre <literal>Work</literal>
|
||||
y <literal>Author</literal> como una asociación muchos-a-muchos. Elegimos representar la relación entre
|
||||
<literal>Author</literal> y <literal>Person</literal> como una asociación uno-a-uno. Otra posibilidad
|
||||
hubiese sido que <literal>Author</literal> extendiera <literal>Person</literal>.
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/AuthorWork.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/AuthorWork.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
El siguiente documento de mapeo representa estas relaciones correctamente:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<hibernate-mapping>
|
||||
|
||||
<class name="Work" table="works" discriminator-value="W">
|
||||
|
||||
<id name="id" column="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<discriminator column="type" type="character"/>
|
||||
|
||||
<property name="title"/>
|
||||
<set name="authors" table="author_work">
|
||||
<key column name="work_id"/>
|
||||
<many-to-many class="Author" column name="author_id"/>
|
||||
</set>
|
||||
|
||||
<subclass name="Book" discriminator-value="B">
|
||||
<property name="text"/>
|
||||
</subclass>
|
||||
|
||||
<subclass name="Song" discriminator-value="S">
|
||||
<property name="tempo"/>
|
||||
<property name="genre"/>
|
||||
</subclass>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Author" table="authors">
|
||||
|
||||
<id name="id" column="id">
|
||||
<!-- The Author must have the same identifier as the Person -->
|
||||
<generator class="assigned"/>
|
||||
</id>
|
||||
|
||||
<property name="alias"/>
|
||||
<one-to-one name="person" constrained="true"/>
|
||||
|
||||
<set name="works" table="author_work" inverse="true">
|
||||
<key column="author_id"/>
|
||||
<many-to-many class="Work" column="work_id"/>
|
||||
</set>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Person" table="persons">
|
||||
<id name="id" column="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name"/>
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Hay cuatro tablas en este mapeo. <literal>works</literal>, <literal>authors</literal> y <literal>persons</literal>
|
||||
tienen los datos de obra, autor y persona respectivamente. <literal>author_work</literal> es una tabla de
|
||||
asociación enlazando autores a obras. He aquí el esquema de tablas, tal como fue generado por
|
||||
<literal>SchemaExport</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[create table works (
|
||||
id BIGINT not null generated by default as identity,
|
||||
tempo FLOAT,
|
||||
genre VARCHAR(255),
|
||||
text INTEGER,
|
||||
title VARCHAR(255),
|
||||
type CHAR(1) not null,
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table author_work (
|
||||
author_id BIGINT not null,
|
||||
work_id BIGINT not null,
|
||||
primary key (work_id, author_id)
|
||||
)
|
||||
|
||||
create table authors (
|
||||
id BIGINT not null generated by default as identity,
|
||||
alias VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table persons (
|
||||
id BIGINT not null generated by default as identity,
|
||||
name VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
alter table authors
|
||||
add constraint authorsFK0 foreign key (id) references persons
|
||||
alter table author_work
|
||||
add constraint author_workFK0 foreign key (author_id) references authors
|
||||
alter table author_work
|
||||
add constraint author_workFK1 foreign key (work_id) references works]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-mappings-customerorderproduct">
|
||||
<title>Cliente/Orden/Producto</title>
|
||||
|
||||
<para>
|
||||
Ahora considera un modelo de las relaciones entre <literal>Customer</literal>,
|
||||
<literal>Order</literal> y <literal>LineItem</literal> y <literal>Product</literal>.
|
||||
Hay una asociación uno-a-muchos entre <literal>Customer</literal> y <literal>Order</literal>,
|
||||
pero, ¿cómo deberíamos representar <literal>Order</literal> / <literal>LineItem</literal> / <literal>Product</literal>?
|
||||
He elegido mapear <literal>LineItem</literal> como una clase de asociación representando la
|
||||
asociación muchos-a-muchos entre <literal>Order</literal> y <literal>Product</literal>. En Hibernate,
|
||||
esto se llama un elemento compuesto.
|
||||
</para>
|
||||
|
||||
<mediaobject>
|
||||
<imageobject role="fo">
|
||||
<imagedata fileref="images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
<imageobject role="html">
|
||||
<imagedata fileref="../shared/images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||
</imageobject>
|
||||
</mediaobject>
|
||||
|
||||
<para>
|
||||
El documento de mapeo:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<hibernate-mapping>
|
||||
|
||||
<class name="Customer" table="customers">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name"/>
|
||||
<set name="orders" inverse="true">
|
||||
<key column="customer_id"/>
|
||||
<one-to-many class="Order"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Order" table="orders">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="date"/>
|
||||
<many-to-one name="customer" column="customer_id"/>
|
||||
<list name="lineItems" table="line_items">
|
||||
<key column="order_id"/>
|
||||
<list-index column="line_number"/>
|
||||
<composite-element class="LineItem">
|
||||
<property name="quantity"/>
|
||||
<many-to-one name="product" column="product_id"/>
|
||||
</composite-element>
|
||||
</list>
|
||||
</class>
|
||||
|
||||
<class name="Product" table="products">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="serialNumber"/>
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<para>
|
||||
<literal>customers</literal>, <literal>orders</literal>, <literal>line_items</literal> y
|
||||
<literal>products</literal> tienen los datos de cliente, orden, ítem de línea de orden y producto
|
||||
respectivamente. Además <literal>line_items</literal> actúa como una tabla de asociación enlazando
|
||||
órdenes con productos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[create table customers (
|
||||
id BIGINT not null generated by default as identity,
|
||||
name VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table orders (
|
||||
id BIGINT not null generated by default as identity,
|
||||
customer_id BIGINT,
|
||||
date TIMESTAMP,
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
create table line_items (
|
||||
line_number INTEGER not null,
|
||||
order_id BIGINT not null,
|
||||
product_id BIGINT,
|
||||
quantity INTEGER,
|
||||
primary key (order_id, line_number)
|
||||
)
|
||||
|
||||
create table products (
|
||||
id BIGINT not null generated by default as identity,
|
||||
serialNumber VARCHAR(255),
|
||||
primary key (id)
|
||||
)
|
||||
|
||||
alter table orders
|
||||
add constraint ordersFK0 foreign key (customer_id) references customers
|
||||
alter table line_items
|
||||
add constraint line_itemsFK0 foreign key (product_id) references products
|
||||
alter table line_items
|
||||
add constraint line_itemsFK1 foreign key (order_id) references orders]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="misc">
|
||||
<title>Mapeos misceláneos de ejemplo</title>
|
||||
|
||||
<para>
|
||||
Todos estos ejemplos están tomados de la batería de pruebas de Hibernate.
|
||||
Encontrarás muchos otros mapeos de ejemplo útiles allí. Mira en la carpeta
|
||||
<literal>test</literal> de la distribución de Hibernate.
|
||||
</para>
|
||||
|
||||
<para>POR HACER: poner palabras alrededor de este material</para>
|
||||
|
||||
<sect2 id="example-mappings-typed-onetone">
|
||||
<title>Asociación uno-a-uno "Tipificada"</title>
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="name"/>
|
||||
<one-to-one name="address"
|
||||
cascade="all">
|
||||
<formula>name</formula>
|
||||
<formula>'HOME'</formula>
|
||||
</one-to-one>
|
||||
<one-to-one name="mailingAddress"
|
||||
cascade="all">
|
||||
<formula>name</formula>
|
||||
<formula>'MAILING'</formula>
|
||||
</one-to-one>
|
||||
</class>
|
||||
|
||||
<class name="Address" batch-size="2"
|
||||
check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
|
||||
<composite-id>
|
||||
<key-many-to-one name="person"
|
||||
column="personName"/>
|
||||
<key-property name="type"
|
||||
column="addressType"/>
|
||||
</composite-id>
|
||||
<property name="street" type="text"/>
|
||||
<property name="state"/>
|
||||
<property name="zip"/>
|
||||
</class>]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="example-mappings-composite-key">
|
||||
<title>Ejemplo de clave compuesta</title>
|
||||
<programlisting><![CDATA[<class name="Customer">
|
||||
|
||||
<id name="customerId"
|
||||
length="10">
|
||||
<generator class="assigned"/>
|
||||
</id>
|
||||
|
||||
<property name="name" not-null="true" length="100"/>
|
||||
<property name="address" not-null="true" length="200"/>
|
||||
|
||||
<list name="orders"
|
||||
inverse="true"
|
||||
cascade="save-update">
|
||||
<key column="customerId"/>
|
||||
<index column="orderNumber"/>
|
||||
<one-to-many class="Order"/>
|
||||
</list>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Order" table="CustomerOrder" lazy="true">
|
||||
<synchronize table="LineItem"/>
|
||||
<synchronize table="Product"/>
|
||||
|
||||
<composite-id name="id"
|
||||
class="Order$Id">
|
||||
<key-property name="customerId" length="10"/>
|
||||
<key-property name="orderNumber"/>
|
||||
</composite-id>
|
||||
|
||||
<property name="orderDate"
|
||||
type="calendar_date"
|
||||
not-null="true"/>
|
||||
|
||||
<property name="total">
|
||||
<formula>
|
||||
( select sum(li.quantity*p.price)
|
||||
from LineItem li, Product p
|
||||
where li.productId = p.productId
|
||||
and li.customerId = customerId
|
||||
and li.orderNumber = orderNumber )
|
||||
</formula>
|
||||
</property>
|
||||
|
||||
<many-to-one name="customer"
|
||||
column="customerId"
|
||||
insert="false"
|
||||
update="false"
|
||||
not-null="true"/>
|
||||
|
||||
<bag name="lineItems"
|
||||
fetch="join"
|
||||
inverse="true"
|
||||
cascade="save-update">
|
||||
<key>
|
||||
<column name="customerId"/>
|
||||
<column name="orderNumber"/>
|
||||
</key>
|
||||
<one-to-many class="LineItem"/>
|
||||
</bag>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="LineItem">
|
||||
|
||||
<composite-id name="id"
|
||||
class="LineItem$Id">
|
||||
<key-property name="customerId" length="10"/>
|
||||
<key-property name="orderNumber"/>
|
||||
<key-property name="productId" length="10"/>
|
||||
</composite-id>
|
||||
|
||||
<property name="quantity"/>
|
||||
|
||||
<many-to-one name="order"
|
||||
insert="false"
|
||||
update="false"
|
||||
not-null="true">
|
||||
<column name="customerId"/>
|
||||
<column name="orderNumber"/>
|
||||
</many-to-one>
|
||||
|
||||
<many-to-one name="product"
|
||||
insert="false"
|
||||
update="false"
|
||||
not-null="true"
|
||||
column="productId"/>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Product">
|
||||
<synchronize table="LineItem"/>
|
||||
|
||||
<id name="productId"
|
||||
length="10">
|
||||
<generator class="assigned"/>
|
||||
</id>
|
||||
|
||||
<property name="description"
|
||||
not-null="true"
|
||||
length="200"/>
|
||||
<property name="price" length="3"/>
|
||||
<property name="numberAvailable"/>
|
||||
|
||||
<property name="numberOrdered">
|
||||
<formula>
|
||||
( select sum(li.quantity)
|
||||
from LineItem li
|
||||
where li.productId = productId )
|
||||
</formula>
|
||||
</property>
|
||||
|
||||
</class>]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="example-mappings-composite-key-manytomany">
|
||||
<title>Muchos-a-muchos con atributo de clave compuesta compartido</title>
|
||||
<programlisting><![CDATA[<class name="User" table="`User`">
|
||||
<composite-id>
|
||||
<key-property name="name"/>
|
||||
<key-property name="org"/>
|
||||
</composite-id>
|
||||
<set name="groups" table="UserGroup">
|
||||
<key>
|
||||
<column name="userName"/>
|
||||
<column name="org"/>
|
||||
</key>
|
||||
<many-to-many class="Group">
|
||||
<column name="groupName"/>
|
||||
<formula>org</formula>
|
||||
</many-to-many>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Group" table="`Group`">
|
||||
<composite-id>
|
||||
<key-property name="name"/>
|
||||
<key-property name="org"/>
|
||||
</composite-id>
|
||||
<property name="description"/>
|
||||
<set name="users" table="UserGroup" inverse="true">
|
||||
<key>
|
||||
<column name="groupName"/>
|
||||
<column name="org"/>
|
||||
</key>
|
||||
<many-to-many class="User">
|
||||
<column name="userName"/>
|
||||
<formula>org</formula>
|
||||
</many-to-many>
|
||||
</set>
|
||||
</class>
|
||||
]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="example-mappings-content-discrimination">
|
||||
<title>Discriminación basada en contenido</title>
|
||||
<programlisting><![CDATA[<class name="Person"
|
||||
discriminator-value="P">
|
||||
|
||||
<id name="id"
|
||||
column="person_id"
|
||||
unsaved-value="0">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
|
||||
|
||||
<discriminator
|
||||
type="character">
|
||||
<formula>
|
||||
case
|
||||
when title is not null then 'E'
|
||||
when salesperson is not null then 'C'
|
||||
else 'P'
|
||||
end
|
||||
</formula>
|
||||
</discriminator>
|
||||
|
||||
<property name="name"
|
||||
not-null="true"
|
||||
length="80"/>
|
||||
|
||||
<property name="sex"
|
||||
not-null="true"
|
||||
update="false"/>
|
||||
|
||||
<component name="address">
|
||||
<property name="address"/>
|
||||
<property name="zip"/>
|
||||
<property name="country"/>
|
||||
</component>
|
||||
|
||||
<subclass name="Employee"
|
||||
discriminator-value="E">
|
||||
<property name="title"
|
||||
length="20"/>
|
||||
<property name="salary"/>
|
||||
<many-to-one name="manager"/>
|
||||
</subclass>
|
||||
|
||||
<subclass name="Customer"
|
||||
discriminator-value="C">
|
||||
<property name="comments"/>
|
||||
<many-to-one name="salesperson"/>
|
||||
</subclass>
|
||||
|
||||
</class>]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="example-mappings-association-alternatekeys" >
|
||||
<title>Asociaciones sobre claves alternativas</title>
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
|
||||
<id name="id">
|
||||
<generator class="hilo"/>
|
||||
</id>
|
||||
|
||||
<property name="name" length="100"/>
|
||||
|
||||
<one-to-one name="address"
|
||||
property-ref="person"
|
||||
cascade="all"
|
||||
fetch="join"/>
|
||||
|
||||
<set name="accounts"
|
||||
inverse="true">
|
||||
<key column="userId"
|
||||
property-ref="userId"/>
|
||||
<one-to-many class="Account"/>
|
||||
</set>
|
||||
|
||||
<property name="userId" length="8"/>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Address">
|
||||
|
||||
<id name="id">
|
||||
<generator class="hilo"/>
|
||||
</id>
|
||||
|
||||
<property name="address" length="300"/>
|
||||
<property name="zip" length="5"/>
|
||||
<property name="country" length="25"/>
|
||||
<many-to-one name="person" unique="true" not-null="true"/>
|
||||
|
||||
</class>
|
||||
|
||||
<class name="Account">
|
||||
<id name="accountId" length="32">
|
||||
<generator class="uuid.hex"/>
|
||||
</id>
|
||||
|
||||
<many-to-one name="user"
|
||||
column="userId"
|
||||
property-ref="userId"/>
|
||||
|
||||
<property name="type" not-null="true"/>
|
||||
|
||||
</class>]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
<chapter id="example-parentchild">
|
||||
<title>Ejemplo: Padre/Hijo</title>
|
||||
|
||||
<para>
|
||||
Una de las primerísimas cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relación de
|
||||
tipo padre / hijo. Para esto hay dos enfoques diferentes. Por varias razones, el enfoque más conveniente,
|
||||
especialmente para usuarios nuevos, es modelar tanto <literal>Parent</literal> como <literal>Child</literal>
|
||||
como clases de entidad con una asociación <literal><one-to-many></literal> desde <literal>Parent</literal>
|
||||
a <literal>Child</literal>. (El enfoque alternativo es declarar el <literal>Child</literal> como un
|
||||
<literal><composite-element></literal>.) Ahora, resulta que la semántica por defecto de una asociación
|
||||
uno a muchos (en Hibernate) es mucho menos cercana a la semántica usual de una relación padre / hijo que aquellas
|
||||
de un mapeo de elementos compuestos. Explicaremos cómo usar una <emphasis>asociación uno a muchos bidireccional
|
||||
con tratamiento en cascada</emphasis> para modelar una relación padre / hijo eficiente y elegantemente.
|
||||
¡No es para nada difícil!
|
||||
</para>
|
||||
|
||||
<sect1 id="example-parentchild-collections">
|
||||
<title>Una nota sobre las colecciones</title>
|
||||
|
||||
<para>
|
||||
Se considera que las colecciones de Hibernate son una parte lógica de la entidad que las posee; nunca de
|
||||
las entidades contenidas. ¡Esta es una distinción crucial! Esto tiene las siguientes consecuencias:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Cuando se quita / añade un objeto desde / a una colección, se incrementa el número de versión del
|
||||
dueño de la colección.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Si un objeto que fue quitado de una colección es una instancia de un tipo de valor (por ejemplo, un
|
||||
elemento compuesto), ese objeta cesará de ser persistente y su estado será completamente quitado de la
|
||||
base de datos. Asimismo, añadir una instancia de tipo de valor a la colección causará que su estado
|
||||
sea inmediatamente persistente.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Por otro lado, si se quita una entidad de una colección (una asociación uno-a-muchos o muchos-a-muchos),
|
||||
no será borrado, por defecto. Este comportamiento es completamente consistente. ¡Un cambio en el
|
||||
estado interno de otra entidad no hace desaparecer la entidad asociada! Asimismo, añadir una entidad a
|
||||
una colección no causa que la entidad se vuelva persistente, por defecto.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
En cambio, el comportamiento por defecto es que al añadir una entidad a una colección se crea meramente
|
||||
un enlace entre las dos entidades, mientras que al quitarla se quita el enlace. Esto es muy apropiado para
|
||||
todos los tipos de casos. Donde no es para nada apropiado es en el caso de una relación padre / hijo. donde
|
||||
la vida del hijo está ligada al ciclo de vida del padre.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-parentchild-bidir">
|
||||
<title>Uno-a-muchos bidirectional</title>
|
||||
|
||||
<para>
|
||||
Supón que empezamos con una asociación simple <literal><one-to-many></literal> desde
|
||||
<literal>Parent</literal> a <literal>Child</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="children">
|
||||
<key column="parent_id"/>
|
||||
<one-to-many class="Child"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Si ejecutásemos el siguiente código
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = .....;
|
||||
Child c = new Child();
|
||||
p.getChildren().add(c);
|
||||
session.save(c);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Hibernate publicaría dos sentencias SQL:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>un <literal>INSERT</literal> para crear el registro de <literal>c</literal></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
un <literal>UPDATE</literal> para crear el enlace desde <literal>p</literal> a
|
||||
<literal>c</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Esto no es sólo ineficiente, sino que además viola cualquier restricción <literal>NOT NULL</literal> en la
|
||||
columna <literal>parent_id</literal>. Podemos reparar la violación de restricción de nulabilidad
|
||||
especificando <literal>not-null="true"</literal> en el mapeo de la colección:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="children">
|
||||
<key column="parent_id" not-null="true"/>
|
||||
<one-to-many class="Child"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Sin embargo, esta no es la solución recomendada.
|
||||
</para>
|
||||
<para>
|
||||
El caso subyacente de este comportamiento es que el enlace (la clave foránea <literal>parent_id</literal>)
|
||||
de <literal>p</literal> a <literal>c</literal> no es considerado parte del estado del objeto
|
||||
<literal>Child</literal> y por lo tanto no es creada en el <literal>INSERT</literal>. De modo que la
|
||||
solución es hacer el enlace parte del mapeo del <literal>Child</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||||
|
||||
<para>
|
||||
(Necesitamos además añadir la propiedad <literal>parent</literal> a la clase <literal>Child</literal>.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ahora que la entidad <literal>Child</literal> está gestionando el estado del enlace, le decimos a la
|
||||
colección que no actualice el enlace. Usamos el atributo <literal>inverse</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="children" inverse="true">
|
||||
<key column="parent_id"/>
|
||||
<one-to-many class="Child"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
El siguiente código podría ser usado para añadir un nuevo <literal>Child</literal>
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
Child c = new Child();
|
||||
c.setParent(p);
|
||||
p.getChildren().add(c);
|
||||
session.save(c);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Y ahora, ¡Sólo se publicaría un <literal>INSERT</literal> de SQL!
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para ajustar un poco más las cosas, podríamos crear un método <literal>addChild()</literal> en
|
||||
<literal>Parent</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public void addChild(Child c) {
|
||||
c.setParent(this);
|
||||
children.add(c);
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ahora, el código para añadir un <literal>Child</literal> se ve así
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
Child c = new Child();
|
||||
p.addChild(c);
|
||||
session.save(c);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-parentchild-cascades">
|
||||
<title>Ciclo de vida en cascada</title>
|
||||
|
||||
<para>
|
||||
La llamada explícita a <literal>save()</literal> es aún molesta. Apuntaremos a esto usando tratamientos
|
||||
en cascada.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||||
<key column="parent_id"/>
|
||||
<one-to-many class="Child"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esto simplifica el código anterior a
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
Child c = new Child();
|
||||
p.addChild(c);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Similarmente, no necesitamos iterar los hijos al salvar o borrar un <literal>Parent</literal>.
|
||||
Lo siguiente quita <literal>p</literal> y todos sus hijos de la base de datos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
session.delete(p);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Sin embargo, este código
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
Child c = (Child) p.getChildren().iterator().next();
|
||||
p.getChildren().remove(c);
|
||||
c.setParent(null);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
no quitará <literal>c</literal> de la base de datos; sólo quitará el enlace a <literal>p</literal>
|
||||
(y causará una violación a una restricción <literal>NOT NULL</literal>). Necesitas borrar el hijo
|
||||
explícitamente llamando a <literal>delete()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||
Child c = (Child) p.getChildren().iterator().next();
|
||||
p.getChildren().remove(c);
|
||||
session.delete(c);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ahora, en nuestro caso, un <literal>Child</literal> no puede existir realmente sin su padre. De modo que
|
||||
si quitamos un <literal>Child</literal> de la colección, realmente queremos que sea borrado. Para esto,
|
||||
debemos usar <literal>cascade="all-delete-orphan"</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||||
<key column="parent_id"/>
|
||||
<one-to-many class="Child"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota: aunque el mapeo de la colección especifique <literal>inverse="true"</literal>, el tratamiento en
|
||||
cascada se procesa aún al iterar los elementos de colección. De modo que si requieres que un objeto sea
|
||||
salvado, borrado o actualizado en cascada, debes añadirlo a la colección. No es suficiente con simplemente
|
||||
llamar a <literal>setParent()</literal>.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-parentchild-update">
|
||||
<title>Tratamiento en cascada y <literal>unsaved-value</literal></title>
|
||||
|
||||
<para>
|
||||
Supón que hemos cargado un <literal>Parent</literal> en una <literal>Session</literal>, hemos hecho algunos
|
||||
cambios en una acción de UI y deseamos hacer persistentes estos cambios en una nueva sesión llamando a
|
||||
<literal>update()</literal>. El <literal>Parent</literal> contendrá una colección de hijos y, ya que
|
||||
está habilitado el tratamiento en cascada, Hibernate necesita saber qué hijos están recién instanciados
|
||||
y cuáles representan filas existentes en la base de datos. Asumamos que tanto <literal>Parent</literal> como
|
||||
<literal>Child</literal> tienen propiedades identificadoras generadas de tipo <literal>Long</literal>.
|
||||
Hibernate usará el identificador y el valor de la propiedad de versión/timestamp para determinar cuáles de
|
||||
los hijos son nuevos. (Ver <xref linkend="objectstate-saveorupdate"/>.) <emphasis>En Hibernate3, no es
|
||||
más necesario especificar un <literal>unsaved-value</literal> explícitamente.</emphasis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The following code will update <literal>parent</literal> and <literal>child</literal> and insert
|
||||
<literal>newChild</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[//parent and child were both loaded in a previous session
|
||||
parent.addChild(child);
|
||||
Child newChild = new Child();
|
||||
parent.addChild(newChild);
|
||||
session.update(parent);
|
||||
session.flush();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Bueno, todo eso está muy bien para el caso de un identificador generado, pero ¿qué de los
|
||||
identificadores asignados y de los identificadores compuestos? Esto es más difícil, ya que Hibernate
|
||||
no puede usar la propiedad identificadora para distinguir entre un objeto recién instanciado (con un
|
||||
identificador asignado por el usuario) y un objeto cargado en una sesión previa. En este caso, Hibernate
|
||||
bien usará la propiedad de versión o timestamp, o bien consultará realmente el caché de segundo nivel,
|
||||
o bien, en el peor de los casos, la base de datos, para ver si existe la fila.
|
||||
</para>
|
||||
|
||||
<!-- undocumenting
|
||||
<para>
|
||||
There is one further possibility. The <literal>Interceptor</literal> method named
|
||||
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
||||
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public class Persistent {
|
||||
private boolean _saved = false;
|
||||
public void onSave() {
|
||||
_saved=true;
|
||||
}
|
||||
public void onLoad() {
|
||||
_saved=true;
|
||||
}
|
||||
......
|
||||
public boolean isSaved() {
|
||||
return _saved;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
(The <literal>saved</literal> property is non-persistent.)
|
||||
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||||
and <literal>onSave()</literal> as follows.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||||
if (entity instanceof Persistent) {
|
||||
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onLoad(Object entity,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
|
||||
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onSave(Object entity,
|
||||
Serializable id,
|
||||
Object[] state,
|
||||
String[] propertyNames,
|
||||
Type[] types) {
|
||||
|
||||
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||||
return false;
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
||||
</para>
|
||||
-->
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-parentchild-conclusion">
|
||||
<title>Conclusión</title>
|
||||
|
||||
<para>
|
||||
Hay que resumir un poco aquí y podría parecer confuso a la primera vez. Sin embargo, en la práctica,
|
||||
todo funciona muy agradablemente. La mayoría de las aplicaciones de Hibernate usan el patrón
|
||||
padre / hijo en muchos sitios.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hemos mencionado una alternativa en el primer párrafo. Ninguno de los temas anteriores existe en el caso
|
||||
de los mapeos <literal><composite-element></literal>, que tienen exactamente la semántica de una
|
||||
relación padre / hijo. Desafortunadamente, hay dos grandes limitaciones para las clases de elementos
|
||||
compuestos: los elementos compuestos no pueden poseer sus propias colecciones, y no deben ser el hijo
|
||||
de cualquier otra entidad que no sea su padre único.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
|
@ -1,429 +0,0 @@
|
|||
<chapter id="example-weblog">
|
||||
<title>Ejemplo: Aplicación de Weblog</title>
|
||||
|
||||
<sect1 id="example-weblog-classes">
|
||||
<title>Clases Persistentes</title>
|
||||
|
||||
<para>
|
||||
Las clases persistentes representan un weblog, y un ítem enviado a un weblog. Van a ser modelados como una
|
||||
relación padre/hijo estñndar, pero usaremos un bag ordenado, en vez de un conjunto (set).
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package eg;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Blog {
|
||||
private Long _id;
|
||||
private String _name;
|
||||
private List _items;
|
||||
|
||||
public Long getId() {
|
||||
return _id;
|
||||
}
|
||||
public List getItems() {
|
||||
return _items;
|
||||
}
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
public void setId(Long long1) {
|
||||
_id = long1;
|
||||
}
|
||||
public void setItems(List list) {
|
||||
_items = list;
|
||||
}
|
||||
public void setName(String string) {
|
||||
_name = string;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[package eg;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class BlogItem {
|
||||
private Long _id;
|
||||
private Calendar _datetime;
|
||||
private String _text;
|
||||
private String _title;
|
||||
private Blog _blog;
|
||||
|
||||
public Blog getBlog() {
|
||||
return _blog;
|
||||
}
|
||||
public Calendar getDatetime() {
|
||||
return _datetime;
|
||||
}
|
||||
public Long getId() {
|
||||
return _id;
|
||||
}
|
||||
public String getText() {
|
||||
return _text;
|
||||
}
|
||||
public String getTitle() {
|
||||
return _title;
|
||||
}
|
||||
public void setBlog(Blog blog) {
|
||||
_blog = blog;
|
||||
}
|
||||
public void setDatetime(Calendar calendar) {
|
||||
_datetime = calendar;
|
||||
}
|
||||
public void setId(Long long1) {
|
||||
_id = long1;
|
||||
}
|
||||
public void setText(String string) {
|
||||
_text = string;
|
||||
}
|
||||
public void setTitle(String string) {
|
||||
_title = string;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-weblog-mappings">
|
||||
<title>Mapeos de Hibernate</title>
|
||||
|
||||
<para>
|
||||
Los mapeos XML ahora deben ser absolutamente directos.
|
||||
</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 package="eg">
|
||||
|
||||
<class
|
||||
name="Blog"
|
||||
table="BLOGS">
|
||||
|
||||
<id
|
||||
name="id"
|
||||
column="BLOG_ID">
|
||||
|
||||
<generator class="native"/>
|
||||
|
||||
</id>
|
||||
|
||||
<property
|
||||
name="name"
|
||||
column="NAME"
|
||||
not-null="true"
|
||||
unique="true"/>
|
||||
|
||||
<bag
|
||||
name="items"
|
||||
inverse="true"
|
||||
order-by="DATE_TIME"
|
||||
cascade="all">
|
||||
|
||||
<key column="BLOG_ID"/>
|
||||
<one-to-many class="BlogItem"/>
|
||||
|
||||
</bag>
|
||||
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<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 package="eg">
|
||||
|
||||
<class
|
||||
name="BlogItem"
|
||||
table="BLOG_ITEMS"
|
||||
dynamic-update="true">
|
||||
|
||||
<id
|
||||
name="id"
|
||||
column="BLOG_ITEM_ID">
|
||||
|
||||
<generator class="native"/>
|
||||
|
||||
</id>
|
||||
|
||||
<property
|
||||
name="title"
|
||||
column="TITLE"
|
||||
not-null="true"/>
|
||||
|
||||
<property
|
||||
name="text"
|
||||
column="TEXT"
|
||||
not-null="true"/>
|
||||
|
||||
<property
|
||||
name="datetime"
|
||||
column="DATE_TIME"
|
||||
not-null="true"/>
|
||||
|
||||
<many-to-one
|
||||
name="blog"
|
||||
column="BLOG_ID"
|
||||
not-null="true"/>
|
||||
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="example-weblog-code">
|
||||
<title>Código Hibernate</title>
|
||||
|
||||
<para>
|
||||
La siguiente clase demuestra algunos de los tipos de cosas que podemos haces con estas clases,
|
||||
usando Hibernate.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package eg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Query;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||
|
||||
public class BlogMain {
|
||||
|
||||
private SessionFactory _sessions;
|
||||
|
||||
public void configure() throws HibernateException {
|
||||
_sessions = new Configuration()
|
||||
.addClass(Blog.class)
|
||||
.addClass(BlogItem.class)
|
||||
.buildSessionFactory();
|
||||
}
|
||||
|
||||
public void exportTables() throws HibernateException {
|
||||
Configuration cfg = new Configuration()
|
||||
.addClass(Blog.class)
|
||||
.addClass(BlogItem.class);
|
||||
new SchemaExport(cfg).create(true, true);
|
||||
}
|
||||
|
||||
public Blog createBlog(String name) throws HibernateException {
|
||||
|
||||
Blog blog = new Blog();
|
||||
blog.setName(name);
|
||||
blog.setItems( new ArrayList() );
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
session.persist(blog);
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return blog;
|
||||
}
|
||||
|
||||
public BlogItem createBlogItem(Blog blog, String title, String text)
|
||||
throws HibernateException {
|
||||
|
||||
BlogItem item = new BlogItem();
|
||||
item.setTitle(title);
|
||||
item.setText(text);
|
||||
item.setBlog(blog);
|
||||
item.setDatetime( Calendar.getInstance() );
|
||||
blog.getItems().add(item);
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
session.update(blog);
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public BlogItem createBlogItem(Long blogid, String title, String text)
|
||||
throws HibernateException {
|
||||
|
||||
BlogItem item = new BlogItem();
|
||||
item.setTitle(title);
|
||||
item.setText(text);
|
||||
item.setDatetime( Calendar.getInstance() );
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
Blog blog = (Blog) session.load(Blog.class, blogid);
|
||||
item.setBlog(blog);
|
||||
blog.getItems().add(item);
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public void updateBlogItem(BlogItem item, String text)
|
||||
throws HibernateException {
|
||||
|
||||
item.setText(text);
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
session.update(item);
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateBlogItem(Long itemid, String text)
|
||||
throws HibernateException {
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
BlogItem item = (BlogItem) session.load(BlogItem.class, itemid);
|
||||
item.setText(text);
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List listAllBlogNamesAndItemCounts(int max)
|
||||
throws HibernateException {
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
List result = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
Query q = session.createQuery(
|
||||
"select blog.id, blog.name, count(blogItem) " +
|
||||
"from Blog as blog " +
|
||||
"left outer join blog.items as blogItem " +
|
||||
"group by blog.name, blog.id " +
|
||||
"order by max(blogItem.datetime)"
|
||||
);
|
||||
q.setMaxResults(max);
|
||||
result = q.list();
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Blog getBlogAndAllItems(Long blogid)
|
||||
throws HibernateException {
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
Blog blog = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
Query q = session.createQuery(
|
||||
"from Blog as blog " +
|
||||
"left outer join fetch blog.items " +
|
||||
"where blog.id = :blogid"
|
||||
);
|
||||
q.setParameter("blogid", blogid);
|
||||
blog = (Blog) q.uniqueResult();
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return blog;
|
||||
}
|
||||
|
||||
public List listBlogsAndRecentItems() throws HibernateException {
|
||||
|
||||
Session session = _sessions.openSession();
|
||||
Transaction tx = null;
|
||||
List result = null;
|
||||
try {
|
||||
tx = session.beginTransaction();
|
||||
Query q = session.createQuery(
|
||||
"from Blog as blog " +
|
||||
"inner join blog.items as blogItem " +
|
||||
"where blogItem.datetime > :minDate"
|
||||
);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.roll(Calendar.MONTH, false);
|
||||
q.setCalendar("minDate", cal);
|
||||
|
||||
result = q.list();
|
||||
tx.commit();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
if (tx!=null) tx.rollback();
|
||||
throw he;
|
||||
}
|
||||
finally {
|
||||
session.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
<chapter id="filters">
|
||||
<title>Filtrando datos</title>
|
||||
|
||||
<para>
|
||||
Hibernate3 provee un nuevo enfoque innovador para manejar datos con reglas de "visibilidad".
|
||||
Un <emphasis>filtro de Hibernate</emphasis> es un filtro global, con nombre y parametrizado
|
||||
que puede ser habilitado o deshabilitado para una sesión de Hibernate en particular.
|
||||
</para>
|
||||
|
||||
<sect1 id="objectstate-filters">
|
||||
<title>Filtros de Hibernate</title>
|
||||
|
||||
<para>
|
||||
Hibernate3 añade la habilidad de predefinir criterios de filtros y unir esos filtros tanto a
|
||||
nivel de una clase como de una colección. Un criterio de filtro es la habilidad de definir una
|
||||
cláusula de restricción muy similar al atributo existente "where" disponible en el elemento
|
||||
class y varios elementos de colección. Excepto en que estos filtros pueden ser parametrizados.
|
||||
La aplicación puede tomar la decisión en tiempo de ejecución de qué filtros deben estar
|
||||
habilitados y cuáles deben ser sus parámetros. Los filtros pueden ser usados como vistas de
|
||||
base de datos, pero parametrizados dentro de la aplicación.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para usar los filtros, éstos deben primero ser definidos y luego unidos a los elementos de mapeo
|
||||
apropiados. Para definir un filtro, usa el elemento <literal><filter-def/></literal> dentro
|
||||
de un elemento <literal><hibernate-mapping/></literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<filter-def name="myFilter">
|
||||
<filter-param name="myFilterParam" type="string"/>
|
||||
</filter-def>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Entonces este filtro puede ser unido a una clase:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="myClass" ...>
|
||||
...
|
||||
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
o a una colección:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set ...>
|
||||
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<para>
|
||||
o incluso a ambos (o muchos de cada uno) al mismo tiempo.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los métodos en <literal>Session</literal> son: <literal>enableFilter(String filterName)</literal>,
|
||||
<literal>getEnabledFilter(String filterName)</literal>, y <literal>disableFilter(String filterName)</literal>.
|
||||
Por defecto, los filtros <emphasis>no</emphasis> están habilitados para una sesión dada; deben ser
|
||||
habilitados explícitamente por medio del uso del método <literal>Session.enableFilter()</literal>,
|
||||
que devuelve una instancia de la interface <literal>Filter</literal>. Usando el filtro simple definido
|
||||
arriba, esto se vería así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que los métodos en la interface org.hibernate.Filter permiten el encadenamiento de métodos
|
||||
común en gran parte de Hibernate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Un ejemplo completo, usando datos temporales con un patrón efectivo de fechas de registro:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<filter-def name="effectiveDate">
|
||||
<filter-param name="asOfDate" type="date"/>
|
||||
</filter-def>
|
||||
|
||||
<class name="Employee" ...>
|
||||
...
|
||||
<many-to-one name="department" column="dept_id" class="Department"/>
|
||||
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
|
||||
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
|
||||
...
|
||||
<!--
|
||||
Note that this assumes non-terminal records have an eff_end_dt set to
|
||||
a max db date for simplicity-sake
|
||||
-->
|
||||
<filter name="effectiveDate"
|
||||
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||
</class>
|
||||
|
||||
<class name="Department" ...>
|
||||
...
|
||||
<set name="employees" lazy="true">
|
||||
<key column="dept_id"/>
|
||||
<one-to-many class="Employee"/>
|
||||
<filter name="effectiveDate"
|
||||
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||
</set>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Entonces, en orden de asegurar que siempre tendrás de vuelta registros actualmente efectivos,
|
||||
simplemente habilita el filtro en la sesión previo a recuperar los datos de empleados:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = ...;
|
||||
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
|
||||
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
|
||||
.setLong("targetSalary", new Long(1000000))
|
||||
.list();
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
En el HQL de arriba, aunque sólo hemos mencionado explícitamente una restricción de salario en
|
||||
los resultados, debido al filtro habilitado la consulta sólo devolverá empleados actualmente activos
|
||||
que tengan un salario mayor que un millón de dólares.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nota: si planeas usar filtros con unión externa (outer joining) (bien a través de HQL, o bien
|
||||
de recuperación de carga) sé cuidadoso en la dirección de expresión de la condición. Lo más seguro
|
||||
es establecer esto para unión externa izquierda (left outer joining). En general, coloca el primer
|
||||
parámetro seguido del nombre(s) de columna(s) después del operador.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,464 +0,0 @@
|
|||
<chapter id="inheritance">
|
||||
<title>Mapeo de Herencia</title>
|
||||
|
||||
<sect1 id="inheritance-strategies" revision="2">
|
||||
<title>Las Tres Estrategias</title>
|
||||
|
||||
<para>
|
||||
Hibernate soporta las tres estrategias básicas de mapeo de herencia:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
tabla por jerarquía de clases
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
tabla por subclase
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
tabla por clase concreta
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
En adición, Hibernate soporta un cuarto, ligeramente diferente tipo
|
||||
de polimorfismo:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
polimorfismo implícito
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Es posible usar estrategias de mapeo diferentes para diferentes
|
||||
ramificaciones de la misma jerarquía de herencia, y entonces usar
|
||||
polimorfismo implícito para conseguir polimorfismo a través de
|
||||
toda la jerarquía. Sin embargo, Hibernate no soporta la mezcla de
|
||||
mapeos <literal><subclass></literal>,
|
||||
y <literal><joined-subclass></literal>
|
||||
y <literal><union-subclass></literal> bajo el mismo elemento
|
||||
<literal><class></literal> raíz. Es posible mezclar juntas las
|
||||
estrategias de tabla por jerarquía y tabla por subclase, bajo el mismo
|
||||
elemento <literal><class></literal>, combinando los elementos
|
||||
<literal><subclass></literal> y <literal><join></literal>
|
||||
(ver debajo).
|
||||
</para>
|
||||
|
||||
<sect2 id="inheritance-tableperclass" >
|
||||
<title>Tabla por jerarquía de clases</title>
|
||||
|
||||
<para>
|
||||
Supón que tenemos una interface <literal>Payment</literal>, con
|
||||
los implementadores <literal>CreditCardPayment</literal>,
|
||||
<literal>CashPayment</literal>, <literal>ChequePayment</literal>.
|
||||
El mapeo de tabla por jerarquía se vería así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||
<id name="id" type="long" column="PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||
<property name="amount" column="AMOUNT"/>
|
||||
...
|
||||
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||
<property name="creditCardType" column="CCTYPE"/>
|
||||
...
|
||||
</subclass>
|
||||
<subclass name="CashPayment" discriminator-value="CASH">
|
||||
...
|
||||
</subclass>
|
||||
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||
...
|
||||
</subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Se requiere exactamente una tabla. Hay una gran limitación de esta estrategia de mapeo:
|
||||
las columnas declaradas por las subclases, como <literal>CCTYPE</literal>, no pueden
|
||||
tener restricciones <literal>NOT NULL</literal>.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritance-tablepersubclass">
|
||||
<title>Tabla por subclase</title>
|
||||
|
||||
<para>
|
||||
Un mapeo de tabla por sublclase se vería así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||
<id name="id" type="long" column="PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="amount" column="AMOUNT"/>
|
||||
...
|
||||
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
<property name="creditCardType" column="CCTYPE"/>
|
||||
...
|
||||
</joined-subclass>
|
||||
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
...
|
||||
</joined-subclass>
|
||||
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
...
|
||||
</joined-subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Se requieren cuatro tablas. Las tres tablas de subclase tienen
|
||||
asociaciones de clave primaria a la tabla de superclase (de modo
|
||||
que en el modelo relacional es realmente una asociación uno-a-uno).
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
||||
<title>Tabla por subclase, usando un discriminador</title>
|
||||
|
||||
<para>
|
||||
Observa que la implementación de Hibernate de tabla por subclase
|
||||
no requiere ninguna columna discriminadora. Otros mapeadores
|
||||
objeto/relacional usan una implementación diferente de tabla por
|
||||
subclase que requiere una columna discriminadora de tipo en la tabla
|
||||
de superclase. Este enfoque es mucho más difícil de implementar
|
||||
pero discutiblemente más correcto desde un punto de vista relacional.
|
||||
Si quisieras usar una columna discriminadora con la estrategia de
|
||||
tabla por subclase, puedes combinar el uso de <literal><subclass></literal>
|
||||
y <literal><join></literal>, como sigue:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||
<id name="id" type="long" column="PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||
<property name="amount" column="AMOUNT"/>
|
||||
...
|
||||
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||
<join table="CREDIT_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
<property name="creditCardType" column="CCTYPE"/>
|
||||
...
|
||||
</join>
|
||||
</subclass>
|
||||
<subclass name="CashPayment" discriminator-value="CASH">
|
||||
<join table="CASH_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
...
|
||||
</join>
|
||||
</subclass>
|
||||
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||
<join table="CHEQUE_PAYMENT" fetch="select">
|
||||
<key column="PAYMENT_ID"/>
|
||||
...
|
||||
</join>
|
||||
</subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
la declaración opcional <literal>fetch="select"</literal> dice a Hibernate
|
||||
que no recupere los datos de la subclase <literal>ChequePayment</literal>
|
||||
usando una unión externa (outer join) al consultar la superclase.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
||||
<title>Mezclando tabla por jerarquía de clases con tabla por subclase</title>
|
||||
|
||||
<para>
|
||||
Puedes incluso mezclar las estrategias de tabla po jerarquía y tabla por
|
||||
subclase usando este enfoque:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||
<id name="id" type="long" column="PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||
<property name="amount" column="AMOUNT"/>
|
||||
...
|
||||
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||
<join table="CREDIT_PAYMENT">
|
||||
<property name="creditCardType" column="CCTYPE"/>
|
||||
...
|
||||
</join>
|
||||
</subclass>
|
||||
<subclass name="CashPayment" discriminator-value="CASH">
|
||||
...
|
||||
</subclass>
|
||||
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||
...
|
||||
</subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Para cualquiera de estas estrategias de mapeo, una asociación polimórfica
|
||||
a la clase raíz <literal>Payment</literal> es mapeada usando <literal><many-to-one></literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritance-tableperconcrete" revision="1">
|
||||
<title>Tabla por clase concreta</title>
|
||||
|
||||
<para>
|
||||
Podríamos ir de dos maneras a la estrategia de mapeo de tabla por clase
|
||||
concreta. La primera es usar <literal><union-subclass></literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Payment">
|
||||
<id name="id" type="long" column="PAYMENT_ID">
|
||||
<generator class="sequence"/>
|
||||
</id>
|
||||
<property name="amount" column="AMOUNT"/>
|
||||
...
|
||||
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||
<property name="creditCardType" column="CCTYPE"/>
|
||||
...
|
||||
</union-subclass>
|
||||
<union-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||
...
|
||||
</union-subclass>
|
||||
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||
...
|
||||
</union-subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Están implicadas tres tablas. Cada tabla define columnas para todas las
|
||||
propiedades de la clase, inccluyendo las propiedades heredadas.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La limitación de este enfoque es que si una propiedad es mapeada en la
|
||||
superclase, el nombre de columna debe ser el mismo en todas las tablas
|
||||
de subclase. (Podríamos relajar esto en un lanzamiento futuro de Hibernate.)
|
||||
La estrategia de generador de indentidad no está permitida en la herencia
|
||||
de unión de subclase, de hecho la semilla de clave primaria tiene que ser
|
||||
compartida a través de todas las subclases unidas de una jerarquía.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
||||
<title>Tabla por clase concreta, usando polimorfismo implícito</title>
|
||||
|
||||
<para>
|
||||
Un enfoque alternativo es hacer uso de polimorfismo implícito:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||
...
|
||||
</class>
|
||||
|
||||
<class name="CashPayment" table="CASH_PAYMENT">
|
||||
<id name="id" type="long" column="CASH_PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="amount" column="CASH_AMOUNT"/>
|
||||
...
|
||||
</class>
|
||||
|
||||
<class name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||
...
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que en ningún sitio mencionamos la interface <literal>Payment</literal>
|
||||
explícitamente. Nota además que las propiedades de <literal>Payment</literal>
|
||||
son mapeadas en cada una de las subclases. Si quieres evitar duplicación,
|
||||
considera usar entidades XML. (por ejemplo,
|
||||
<literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
||||
en la declaración <literal>DOCTYPE</literal> y <literal>&allproperties;</literal>
|
||||
en el mapeo).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La desventaja de este enfoque es que Hibernate no genera <literal>UNION</literal>s
|
||||
de SQL al realizar consultas polimórficas.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para esta estrategia de mapeo, una asociación polimórfica a <literal>Payment</literal>
|
||||
es mapeada generalmente usando <literal><any></literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<any name="payment" meta-type="string" id-type="long">
|
||||
<meta-value value="CREDIT" class="CreditCardPayment"/>
|
||||
<meta-value value="CASH" class="CashPayment"/>
|
||||
<meta-value value="CHEQUE" class="ChequePayment"/>
|
||||
<column name="PAYMENT_CLASS"/>
|
||||
<column name="PAYMENT_ID"/>
|
||||
</any>]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="inheritace-mixingpolymorphism">
|
||||
<title>Mezclando polimorfismo implícito con otros mapeos de herencia</title>
|
||||
|
||||
<para>
|
||||
Hay una cosa más por notar acerca de este mapeo. Ya que las subclases se mapean
|
||||
cada una en su propio elemento <literal><class></literal> (y ya que
|
||||
<literal>Payment</literal> es sólo una interface), cada una de las subclases
|
||||
podría ser parte de otra jerarquía de herencia! (Y todavía puedes seguir usando
|
||||
consultas polimórficas contra la interface <literal>Payment</literal>.)
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<discriminator column="CREDIT_CARD" type="string"/>
|
||||
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||
...
|
||||
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
|
||||
<subclass name="VisaPayment" discriminator-value="VISA"/>
|
||||
</class>
|
||||
|
||||
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
|
||||
<id name="id" type="long" column="TXN_ID">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
...
|
||||
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
<property name="amount" column="CASH_AMOUNT"/>
|
||||
...
|
||||
</joined-subclass>
|
||||
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||
<key column="PAYMENT_ID"/>
|
||||
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||
...
|
||||
</joined-subclass>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Una vez más, no mencionamos a <literal>Payment</literal> explícitamente.
|
||||
Si ejecutamos una consulta contra la interface <literal>Payment</literal>
|
||||
- por ejemplo, <literal>from Payment</literal> - Hibernate devuelve
|
||||
automáticamente instancias de <literal>CreditCardPayment</literal>
|
||||
(y sus subclases, ya que ellas también implementan <literal>Payment</literal>),
|
||||
<literal>CashPayment</literal> y <literal>ChequePayment</literal> pero
|
||||
no instancias de <literal>NonelectronicTransaction</literal>.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="inheritance-limitations">
|
||||
<title>Limitaciones</title>
|
||||
|
||||
<para>
|
||||
Existen ciertas limitaciones al enfoque de "polimorfismo implícito" en
|
||||
la estrategia de mapeo de tabla por clase concreta. Existen limitaciones
|
||||
algo menos restrictivas a los mapeos <literal><union-subclass></literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La siguiente tabla muestra las limitaciones de mapeos de tabla por
|
||||
clase concreta, y de polmorfismo implícito, en Hibernate.
|
||||
</para>
|
||||
|
||||
<table frame="topbot">
|
||||
<title>Funcionalidades de mapeo de herencia</title>
|
||||
<tgroup cols='8' align='left' colsep='1' rowsep='1'>
|
||||
<colspec colname='c1' colwidth="1*"/>
|
||||
<colspec colname='c2' colwidth="1*"/>
|
||||
<colspec colname='c3' colwidth="1*"/>
|
||||
<colspec colname='c4' colwidth="1*"/>
|
||||
<colspec colname='c5' colwidth="1*"/>
|
||||
<colspec colname='c6' colwidth="1*"/>
|
||||
<colspec colname='c7' colwidth="1*"/>
|
||||
<colspec colname='c8' colwidth="1*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Estrategia de herencia</entry>
|
||||
<entry>muchos-a-uno polimórfica</entry>
|
||||
<entry>uno-a-uno polimórfica</entry>
|
||||
<entry>uno-a-muchos polimórfica</entry>
|
||||
<entry>mushos-a-muchos polimórfica</entry>
|
||||
<entry><literal>load()/get()</literal> polimórficos</entry>
|
||||
<entry>Consultas polimórficas</entry>
|
||||
<entry>Uniones polimórficas</entry>
|
||||
<entry>Recuperación por unión externa (outer join)</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>tabla por jerarquía de clases</entry>
|
||||
<entry><literal><many-to-one></literal></entry>
|
||||
<entry><literal><one-to-one></literal></entry>
|
||||
<entry><literal><one-to-many></literal></entry>
|
||||
<entry><literal><many-to-many></literal></entry>
|
||||
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||
<entry><literal>from Payment p</literal></entry>
|
||||
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||
<entry><emphasis>soportada</emphasis></entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>tabla por subclase</entry>
|
||||
<entry><literal><many-to-one></literal></entry>
|
||||
<entry><literal><one-to-one></literal></entry>
|
||||
<entry><literal><one-to-many></literal></entry>
|
||||
<entry><literal><many-to-many></literal></entry>
|
||||
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||
<entry><literal>from Payment p</literal></entry>
|
||||
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||
<entry><emphasis>soportada</emphasis></entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>tabla por clase concreta (union-subclass)</entry>
|
||||
<entry><literal><many-to-one></literal></entry>
|
||||
<entry><literal><one-to-one></literal></entry>
|
||||
<entry><literal><one-to-many></literal> (para <literal>inverse="true"</literal> solamente)</entry>
|
||||
<entry><literal><many-to-many></literal></entry>
|
||||
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||
<entry><literal>from Payment p</literal></entry>
|
||||
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||
<entry><emphasis>soportada</emphasis></entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>tabla por clase concreta (polimorfismo implícito)</entry>
|
||||
<entry><literal><any></literal></entry>
|
||||
<entry><emphasis>no soportada</emphasis></entry>
|
||||
<entry><emphasis>no soportada</emphasis></entry>
|
||||
<entry><literal><many-to-any></literal></entry>
|
||||
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
|
||||
<entry><literal>from Payment p</literal></entry>
|
||||
<entry><emphasis>no suportadas</emphasis></entry>
|
||||
<entry><emphasis>no soportada</emphasis></entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,478 +0,0 @@
|
|||
<chapter id="persistent-classes" revision="2">
|
||||
<title>Clases Persistentes</title>
|
||||
|
||||
<para>
|
||||
Clases presistentes son clases en una aplicación que implementan las
|
||||
entidades del problema de negocio (por ejemplo, Customer y Order en una
|
||||
aplicación de comercio electrónico). No todas las instancias de una
|
||||
clase persistente se considera que estén en el estado persistente,
|
||||
una instancia puede en cambio ser transitoria o estar separada.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hibernate funciona mejor si las clases siguen algunas simples reglas, también
|
||||
conocidas como el modelo de programación de Viejas Clases Java Planas
|
||||
(Plain Old Java Object o POJO). Sin embargo, ninguna de estas reglas son
|
||||
requerimientos rígidos. En cambio, Hibernate3 asume muy poco acerca de
|
||||
la naturaleza de tus objetos persistentes. Puedes expresar un modelo de dominio en
|
||||
otras formas: usando árboles de instancias de <literal>Map</literal>,
|
||||
por ejemplo.
|
||||
</para>
|
||||
|
||||
<sect1 id="persistent-classes-pojo">
|
||||
<title>Un ejemplo simple de POJO</title>
|
||||
|
||||
<para>
|
||||
La mayoría de aplicaciones Java requieren una clase
|
||||
representando felinos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package eg;
|
||||
import java.util.Set;
|
||||
import java.util.Date;
|
||||
|
||||
public class Cat {
|
||||
private Long id; // identifier
|
||||
|
||||
private Date birthdate;
|
||||
private Color color;
|
||||
private char sex;
|
||||
private float weight;
|
||||
private int litterId;
|
||||
|
||||
private Cat mother;
|
||||
private Set kittens = new HashSet();
|
||||
|
||||
private void setId(Long id) {
|
||||
this.id=id;
|
||||
}
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void setBirthdate(Date date) {
|
||||
birthdate = date;
|
||||
}
|
||||
public Date getBirthdate() {
|
||||
return birthdate;
|
||||
}
|
||||
|
||||
void setWeight(float weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
public float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
void setSex(char sex) {
|
||||
this.sex=sex;
|
||||
}
|
||||
public char getSex() {
|
||||
return sex;
|
||||
}
|
||||
|
||||
void setLitterId(int id) {
|
||||
this.litterId = id;
|
||||
}
|
||||
public int getLitterId() {
|
||||
return litterId;
|
||||
}
|
||||
|
||||
void setMother(Cat mother) {
|
||||
this.mother = mother;
|
||||
}
|
||||
public Cat getMother() {
|
||||
return mother;
|
||||
}
|
||||
void setKittens(Set kittens) {
|
||||
this.kittens = kittens;
|
||||
}
|
||||
public Set getKittens() {
|
||||
return kittens;
|
||||
}
|
||||
|
||||
// addKitten not needed by Hibernate
|
||||
public void addKitten(Cat kitten) {
|
||||
kitten.setMother(this);
|
||||
kitten.setLitterId( kittens.size() );
|
||||
kittens.add(kitten);
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Aquí hay cuatro reglas principales a seguir:
|
||||
</para>
|
||||
|
||||
<sect2 id="persistent-classes-pojo-constructor" revision="1">
|
||||
<title>Implementa un constructor sin argumentos</title>
|
||||
|
||||
<para>
|
||||
<literal>Cat</literal> tiene un contructor sin argumentos. Todas las clases persistentes
|
||||
deben tener un constructor por defecto (que puede no ser público) de modo que Hibernate
|
||||
pueda instanciarlas usando <literal>Constructor.newInstance()</literal>. Recomendamos fuertemente tener
|
||||
un constructor por defecto con al menos visibilidad de <emphasis>package</emphasis> para la
|
||||
generación de proxies en tiempo de ejecución en Hibernate.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="persistent-classes-pojo-identifier" revision="2">
|
||||
<title>Provee una propiedad identificadora (opcional)</title>
|
||||
|
||||
<para>
|
||||
<literal>Cat</literal> tiene una propiedad llamada <literal>id</literal>. Esta
|
||||
propiedad mapea a la columna clave primaria de la tabla de base de datos. La propiedad
|
||||
podría llamarse cualquierCosa, y su tipo podría haber sido cualquier tipo
|
||||
primitivo, cualquier tipo de "envoltura" primitivo, <literal>java.lang.String</literal>
|
||||
o <literal>java.util.Date</literal>. (Si tu tabla de base de datos heredada tiene claves
|
||||
compuestas, puedes incluso usar una clase definida por el usuario con propiedades de
|
||||
estos tipos, ver la sección sobre identificadores compuestos luego.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La propiedad identificadora es estrictamente opcional. Puedes olvidarla y dejar que Hibernate
|
||||
siga internamente la pista de los identificadores del objeto. Sin embargo, no recomendamos esto.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
De hecho, alguna funcionalidad está disponible sólo para clases que
|
||||
declaran una propiedad identificadora:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
Reasociación transitiva de objetos separados (actualizaciones o
|
||||
fusiones en cascada) - ver <xref linkend="objectstate-transitive"/>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>Session.saveOrUpdate()</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>Session.merge()</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Recomendamos que declares propiedades identificadoras nombradas-consistentemente
|
||||
en clases persistentes. Mas aún, recomendamos que uses un tipo nulable
|
||||
(es decir, no primitivo).
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="persistent-classes-pojo-final">
|
||||
<title>Prefiere las clases no finales (opcional)</title>
|
||||
<para>
|
||||
Un aspecto central de Hibernate, <emphasis>proxies</emphasis>, depende de que
|
||||
las clases persistentes sean ya no finales, o sean ya la implementación
|
||||
de una interface que declare todos los métodos públicos.
|
||||
</para>
|
||||
<para>
|
||||
Puedes persistir con Hibernate clases <literal>final</literal> que no implementen una
|
||||
interface, pero no serás capaz de usar proxies para recuperación perezosa
|
||||
de asociaciones, lo que limitará tus opciones para afinar el rendimiento.
|
||||
</para>
|
||||
<para>
|
||||
Debes también evitar declarar métodos <literal>public final</literal>
|
||||
en clases non-final. Si quieres usar una clase con un método <literal>public
|
||||
final</literal>, debes deshabilitar explícitamente el uso de proxies estableciendo
|
||||
<literal>lazy="false"</literal>.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="persistent-classes-pojo-accessors" revision="2">
|
||||
<title>Declara métodos de acceso y modificación para los campos persistentes (opcional)</title>
|
||||
<para>
|
||||
<literal>Cat</literal> declara métodos de acceso para todos sus campos persistente.
|
||||
Muchas otras herramientas ORM persisten directamente variables de instancia. Creemos que
|
||||
es mejor proveer una indirección entre el esquema relacional y las estructuras internas de la clase.
|
||||
Por defecto, Hibernate persiste propiedades del estilo JavaBeans, y reconoce nombres de método
|
||||
de la forma <literal>getFoo</literal>, <literal>isFoo</literal> y <literal>setFoo</literal>.
|
||||
Puedes cambiar a acceso directo a campos para propiedades en particular, de ser necesario.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las propiedades <emphasis>no</emphasis> necesitan ser declaradas públicas. Hibernate puede
|
||||
persistir una propiedad con un par get / set <literal>protected</literal> o <literal>private</literal>.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="persistent-classes-inheritance">
|
||||
<title>Implementando herencia</title>
|
||||
|
||||
<para>
|
||||
Una subclase puede a su vez observar la primera y segunda regla. Hereda su
|
||||
propiedad identificadora de la superclase, <literal>Cat</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package eg;
|
||||
|
||||
public class DomesticCat extends Cat {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
protected void setName(String name) {
|
||||
this.name=name;
|
||||
}
|
||||
}]]></programlisting>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="persistent-classes-equalshashcode" revision="1">
|
||||
<title>Implementando <literal>equals()</literal> y <literal>hashCode()</literal></title>
|
||||
|
||||
|
||||
<para>
|
||||
Tienes que sobrescribir los métodos <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||
si :
|
||||
</para>
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
piensas poner instancias de clases persistentes en un <literal>Set</literal>
|
||||
(la forma recomendada de representar asociaciones multivaluadas)
|
||||
<emphasis>y</emphasis>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
piensas usar reasociación de instancias separadas.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos) y
|
||||
identidad Java sólo dentro del ámbito de una sesión en particular.
|
||||
De modo que en el momento que mezclamos instancias recuperadas en sesiones diferentes,
|
||||
debemos implementar <literal>equals()</literal> y <literal>hashCode()</literal> si
|
||||
deseamos tener una semántica significativa de <literal>Set</literal>s.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La forma más obvia es implementar <literal>equals()</literal>/<literal>hashCode()</literal>
|
||||
comparando el valor identificador de ambos objetos. Si el valor es el mismo, ambos deben ser
|
||||
la misma fila de base de datos, por lo tanto son iguales (si ambos son agregados a un
|
||||
<literal>Set</literal>, sólo tendremos un elemento en el <literal>Set</literal>).
|
||||
Desafortunadamente, no podemos usar este enfoque con identificadores generados! Hibernate sólo
|
||||
asignará valores identificadores a objetos que son persistentes, una instancia recién
|
||||
creada no tendrá ningún valor identificador! Además, si una instancia no está
|
||||
salvada y está actualmente en un <literal>Set</literal>, salvarla asignará un
|
||||
valor identificador al objeto. Si <literal>equals()</literal> and <literal>hashCode()</literal>
|
||||
están basados en el valor identificador, el código hash podría cambiar,
|
||||
rompiendo el contrato de <literal>Set</literal>. Ver el sitio web de Hibernate para una
|
||||
discusión completa de este problema. Observa que esto no es una incidencia de Hibernate,
|
||||
sino la semántica normal de Java de identidad de objeto e igualdad.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Recomendamos implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||
usando <emphasis>igualdad de clave de negocio (Business key equality)</emphasis>.
|
||||
Igualdad de clave de negocio significa que el método <literal>equals()</literal>
|
||||
compara sólo las propiedades que forman la clave de negocio, una clave que podría
|
||||
identificar nuestra instancia en el mundo real (una clave candidata
|
||||
<emphasis>natural</emphasis>):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[public class Cat {
|
||||
|
||||
...
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if ( !(other instanceof Cat) ) return false;
|
||||
|
||||
final Cat cat = (Cat) other;
|
||||
|
||||
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
|
||||
if ( !cat.getMother().equals( getMother() ) ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result;
|
||||
result = getMother().hashCode();
|
||||
result = 29 * result + getLitterId();
|
||||
return result;
|
||||
}
|
||||
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que una clave de negocio no tiene que ser tan sólida como
|
||||
una clave primaria candidata de base de datos (ver
|
||||
<xref linkend="transactions-basics-identity"/>). Las propiedades inmutables o
|
||||
únicas son usualmente buenas candidatas para una clave de negocio.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="persistent-classes-dynamicmodels">
|
||||
<title>Modelos dinámicos</title>
|
||||
|
||||
<para>
|
||||
<emphasis>Ten en cuenta que las siguientes funcionalidades están
|
||||
consideradas actualmente experimentales y pueden cambiar en el futuro
|
||||
cercano.</emphasis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las entidades persistentes no necesariamente tienen que estar representadas
|
||||
como clases POJO o como objetos JavaBean en tiempo de ejecución. Hibernate
|
||||
soporta además modelos dinámicos (usando <literal>Map</literal>s de
|
||||
<literal>Map</literal>s en tiempo de ejecución) y la representación
|
||||
de entidades como árboles de DOM4J. Con este enfoque no escribes clases
|
||||
persistentes, sólo ficheros de mapeo.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Por defecto, Hibernate funciona en modo POJO normal. Puedes establecer una
|
||||
representación de entidad por defecto para una <literal>SessionFactory</literal>
|
||||
en particular usando la opción de configuración
|
||||
<literal>default_entity_mode</literal>
|
||||
(ver <xref linkend="configuration-optional-properties"/>).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los siguientes ejemplos demuestran la representación usando
|
||||
<literal>Map</literal>s. Primero, en el fichero de mapeo,
|
||||
tiene que declararse un <literal>entity-name</literal> en vez de
|
||||
(o como agregado a) un nombre de clase:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<hibernate-mapping>
|
||||
|
||||
<class entity-name="Customer">
|
||||
|
||||
<id name="id"
|
||||
type="long"
|
||||
column="ID">
|
||||
<generator class="sequence"/>
|
||||
</id>
|
||||
|
||||
<property name="name"
|
||||
column="NAME"
|
||||
type="string"/>
|
||||
|
||||
<property name="address"
|
||||
column="ADDRESS"
|
||||
type="string"/>
|
||||
|
||||
<many-to-one name="organization"
|
||||
column="ORGANIZATION_ID"
|
||||
class="Organization"/>
|
||||
|
||||
<bag name="orders"
|
||||
inverse="true"
|
||||
lazy="false"
|
||||
cascade="all">
|
||||
<key column="CUSTOMER_ID"/>
|
||||
<one-to-many class="Order"/>
|
||||
</bag>
|
||||
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ten en cuenta que aunque las asociaciones se declaran usando nombres
|
||||
de clase objetivo, el tipo objetivo de una asociación puede
|
||||
ser además una entidad dinámica en vez de un POJO.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Después de establecer el modo de entidad por defecto a
|
||||
<literal>dynamic-map</literal> para la <literal>SessionFactory</literal>,
|
||||
podemos trabajar en tiempo de ejecución con <literal>Map</literal>s
|
||||
de <literal>Map</literal>s:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
Session s = openSession();
|
||||
|
||||
// Create a customer
|
||||
Map david = new HashMap();
|
||||
david.put("name", "David");
|
||||
|
||||
// Create an organization
|
||||
Map foobar = new HashMap();
|
||||
foobar.put("name", "Foobar Inc.");
|
||||
|
||||
// Link both
|
||||
david.put("organization", foobar);
|
||||
|
||||
// Save both
|
||||
s.save("Customer", david);
|
||||
s.save("Organization", foobar);
|
||||
|
||||
tx.commit();
|
||||
s.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Las ventajas de un mapeo dinámico es rápido tiempo de ciclo
|
||||
de prototipado sin la necesidad de implementación de clases de entidad.
|
||||
Sin embargo, pierdes chequeo de tipos en tiempo de compilación y
|
||||
muy probablemente tratarás con muchas excepciones en tiempo de ejecución.
|
||||
Gracias al mapeo de Hibernate, el esquema de base de datos puede estar facilmente
|
||||
sano y normalizado, permitiendo agregar una implementación apropiada del
|
||||
modelo de dominio más tarde.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los modos de representación de entidad pueden ser establecidos
|
||||
por <literal>Session</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
|
||||
|
||||
// Create a customer
|
||||
Map david = new HashMap();
|
||||
david.put("name", "David");
|
||||
dynamicSession.save("Customer", david);
|
||||
...
|
||||
dynamicSession.flush();
|
||||
dynamicSession.close()
|
||||
...
|
||||
// Continue on pojoSession
|
||||
]]></programlisting>
|
||||
|
||||
|
||||
<para>
|
||||
Por favor, ten en cuenta que la llamada a <literal>getSession()</literal>
|
||||
usando un <literal>EntityMode</literal> está en la API de
|
||||
<literal>Session</literal>, no en la de <literal>SessionFactory</literal>.
|
||||
De esta forma, la nueva <literal>Session</literal> comparte la conexión
|
||||
JDBC, transacción y otra información de contexto. Esto significa
|
||||
que no tienes que llamar a <literal>flush()</literal> ni a <literal>close()</literal>
|
||||
en la <literal>Session</literal> secundaria, y tembién dejar el manejo
|
||||
de la transacción y de la conexión a la unidad de trabajo primaria.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puede encontrarse más información sobre las capacidades de
|
||||
representación XML en <xref linkend="xml"/>.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<para>
|
||||
PORHACER: Documentar el framework de extensiones del usuario en los paquetes
|
||||
de propiedad y proxies.
|
||||
</para>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,431 +0,0 @@
|
|||
<chapter id="querycriteria">
|
||||
<title>Consultas por Criterios</title>
|
||||
|
||||
<para>
|
||||
Acompaña a Hibernate una API de consultas por criterios intuitiva y extensible.
|
||||
</para>
|
||||
|
||||
<sect1 id="querycriteria-creating">
|
||||
<title>Creando una instancia de <literal>Criteria</literal></title>
|
||||
|
||||
<para>
|
||||
La interface <literal>org.hibernate.Criteria</literal> representa una consulta contra
|
||||
una clase persistente en particular. La <literal>Session</literal> es una fábrica de instancias
|
||||
de <literal>Criteria</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Criteria crit = sess.createCriteria(Cat.class);
|
||||
crit.setMaxResults(50);
|
||||
List cats = crit.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-narrowing">
|
||||
<title>Estrechando el conjunto resultado</title>
|
||||
|
||||
<para>
|
||||
Un criterio individual de consulta es una instancia de la interface
|
||||
<literal>org.hibernate.criterion.Criterion</literal>. La clase
|
||||
<literal>org.hibernate.criterion.Restrictions</literal> define métodos de fábrica para obtener ciertos tipos
|
||||
prefabricados de <literal>Criterion</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.like("name", "Fritz%") )
|
||||
.add( Restrictions.between("weight", minWeight, maxWeight) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Las restricciones pueden ser agrupadas lógicamente.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.like("name", "Fritz%") )
|
||||
.add( Restrictions.or(
|
||||
Restrictions.eq( "age", new Integer(0) ),
|
||||
Restrictions.isNull("age")
|
||||
) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||
.add( Restrictions.disjunction()
|
||||
.add( Restrictions.isNull("age") )
|
||||
.add( Restrictions.eq("age", new Integer(0) ) )
|
||||
.add( Restrictions.eq("age", new Integer(1) ) )
|
||||
.add( Restrictions.eq("age", new Integer(2) ) )
|
||||
) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Hay un gran rango de tipos de criterio prefabricados (subclases de <literal>Restrictions</literal>),
|
||||
pero uno que es especialmente útil te deja especificar SQL directamente.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.sql("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
El sitio <literal>{alias}</literal> será remplazado por el alias de fila de la entidad consultada.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Un enfoque alternativo para obtener un criterio es tomarlo de una instancia de
|
||||
<literal>Property</literal>. Puedes crear una <literal>Property</literal> llamando a
|
||||
<literal>Property.forName()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[
|
||||
Property age = Property.forName("age");
|
||||
List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.disjunction()
|
||||
.add( age.isNull() )
|
||||
.add( age.eq( new Integer(0) ) )
|
||||
.add( age.eq( new Integer(1) ) )
|
||||
.add( age.eq( new Integer(2) ) )
|
||||
) )
|
||||
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-ordering">
|
||||
<title>Ordenando los resultados</title>
|
||||
|
||||
<para>
|
||||
Puedes ordenar los resultados usando <literal>org.hibernate.criterion.Order</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.like("name", "F%")
|
||||
.addOrder( Order.asc("name") )
|
||||
.addOrder( Order.desc("age") )
|
||||
.setMaxResults(50)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Property.forName("name").like("F%") )
|
||||
.addOrder( Property.forName("name").asc() )
|
||||
.addOrder( Property.forName("age").desc() )
|
||||
.setMaxResults(50)
|
||||
.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-associations">
|
||||
<title>Asociaciones</title>
|
||||
|
||||
<para>
|
||||
Puedes especificar fácilmente restricciones sobre las entidades relacionadas al navegar asociaciones
|
||||
usando <literal>createCriteria()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.like("name", "F%")
|
||||
.createCriteria("kittens")
|
||||
.add( Restrictions.like("name", "F%")
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
nota que el segundo <literal>createCriteria()</literal> devuelve una nueva instancia de
|
||||
<literal>Criteria</literal>, que hace referencia a los elementos de la colección
|
||||
<literal>kittens</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La siguiente forma alternativa es útil en ciertas circunstancias.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.createAlias("kittens", "kt")
|
||||
.createAlias("mate", "mt")
|
||||
.add( Restrictions.eqProperty("kt.name", "mt.name") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
(<literal>createAlias()</literal> no crea una nueva instancia de
|
||||
<literal>Criteria</literal>.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
¡Observa que las colecciones de gatitos tenidas por las instancias de <literal>Cat</literal> devueltas
|
||||
por las dos consultas previas <emphasis>no</emphasis> están prefiltradas por los criterios! Si deseas
|
||||
recuperar sólo los gatitos que emparejen los criterios, debes usar <literal>returnMaps()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.createCriteria("kittens", "kt")
|
||||
.add( Restrictions.eq("name", "F%") )
|
||||
.returnMaps()
|
||||
.list();
|
||||
Iterator iter = cats.iterator();
|
||||
while ( iter.hasNext() ) {
|
||||
Map map = (Map) iter.next();
|
||||
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
|
||||
Cat kitten = (Cat) map.get("kt");
|
||||
}]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-dynamicfetching" revision="1">
|
||||
<title>Recuperación dinámica de asociaciones</title>
|
||||
|
||||
<para>
|
||||
Puedes especificar la semántica de recuperación de asociaciones en tiempo de ejecución usando
|
||||
<literal>setFetchMode()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||
.add( Restrictions.like("name", "Fritz%") )
|
||||
.setFetchMode("mate", FetchMode.EAGER)
|
||||
.setFetchMode("kittens", FetchMode.EAGER)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esta consulta recuperará tanto <literal>mate</literal> como <literal>kittens</literal> por
|
||||
unión exterior (outer join). Ver <xref linkend="performance-fetching"/> para más información.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-examples">
|
||||
<title>Consultas por ejemplos</title>
|
||||
|
||||
<para>
|
||||
La clase <literal>org.hibernate.criterion.Example</literal> te permite construir un criterio de consulta
|
||||
a partir de una instancia dada.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Cat cat = new Cat();
|
||||
cat.setSex('F');
|
||||
cat.setColor(Color.BLACK);
|
||||
List results = session.createCriteria(Cat.class)
|
||||
.add( Example.create(cat) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Las propiedades de versión, los identificadores y las asociaciones son ignorados. Por defecto,
|
||||
las propiedades valuadas a nulo son excluídas.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puedes ajustar cómo se aplica el <literal>Example</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Example example = Example.create(cat)
|
||||
.excludeZeroes() //exclude zero valued properties
|
||||
.excludeProperty("color") //exclude the property named "color"
|
||||
.ignoreCase() //perform case insensitive string comparisons
|
||||
.enableLike(); //use like for string comparisons
|
||||
List results = session.createCriteria(Cat.class)
|
||||
.add(example)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Puedes incluso usar ejemplos para colocar criterios sobre objetos asociados.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.add( Example.create(cat) )
|
||||
.createCriteria("mate")
|
||||
.add( Example.create( cat.getMate() ) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-projection">
|
||||
<title>Proyecciones, agregación y agrupamiento</title>
|
||||
<para>
|
||||
La clase <literal>org.hibernate.criterion.Projections</literal> es una fábrica de instancias de
|
||||
<literal>Projection</literal>. Aplicamos una proyección a una consulta llamando a
|
||||
<literal>setProjection()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.rowCount() )
|
||||
.add( Restrictions.eq("color", Color.BLACK) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.projectionList()
|
||||
.add( Projections.rowCount() )
|
||||
.add( Projections.avg("weight") )
|
||||
.add( Projections.max("weight") )
|
||||
.add( Projections.groupProperty("color") )
|
||||
)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
No es necesario ningún "group by" explícito en una consulta por criterios.
|
||||
Ciertos tipos de proyecciones son definidos para ser <emphasis>proyecciones agrupadas</emphasis>,
|
||||
que además aparecen en la cláusula SQL <literal>group by</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puede opcionalmente asignarse un alias a una proyección, de modo que el valor proyectado pueda
|
||||
ser referido en restricciones u ordenamientos. Aquí hay dos formas diferentes de hacer esto:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
|
||||
.addOrder( Order.asc("colr") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.groupProperty("color").as("colr") )
|
||||
.addOrder( Order.asc("colr") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Los métodos <literal>alias()</literal> y <literal>as()</literal> simplemente envuelven una instancia
|
||||
de proyección en otra instancia de <literal>Projection</literal> con alias. Como un atajo, puedes asignar
|
||||
un alias cuando agregas la proyección a una lista de proyecciones:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.projectionList()
|
||||
.add( Projections.rowCount(), "catCountByColor" )
|
||||
.add( Projections.avg("weight"), "avgWeight" )
|
||||
.add( Projections.max("weight"), "maxWeight" )
|
||||
.add( Projections.groupProperty("color"), "color" )
|
||||
)
|
||||
.addOrder( Order.desc("catCountByColor") )
|
||||
.addOrder( Order.desc("avgWeight") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Domestic.class, "cat")
|
||||
.createAlias("kittens", "kit")
|
||||
.setProjection( Projections.projectionList()
|
||||
.add( Projections.property("cat.name"), "catName" )
|
||||
.add( Projections.property("kit.name"), "kitName" )
|
||||
)
|
||||
.addOrder( Order.asc("catName") )
|
||||
.addOrder( Order.asc("kitName") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Puedes también usar <literal>Property.forName()</literal> para expresar proyecciones:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Property.forName("name") )
|
||||
.add( Property.forName("color").eq(Color.BLACK) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||
.setProjection( Projections.projectionList()
|
||||
.add( Projections.rowCount().as("catCountByColor") )
|
||||
.add( Property.forName("weight").avg().as("avgWeight") )
|
||||
.add( Property.forName("weight").max().as("maxWeight") )
|
||||
.add( Property.forName("color").group().as("color" )
|
||||
)
|
||||
.addOrder( Order.desc("catCountByColor") )
|
||||
.addOrder( Order.desc("avgWeight") )
|
||||
.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querycriteria-detachedqueries">
|
||||
<title>Consultas y subconsultas separadas</title>
|
||||
<para>
|
||||
La clase <literal>DetachedCriteria</literal> te deja crear una consulta fuera del ámbito de una sesión,
|
||||
y entonces ejecutarla luego usando alguna <literal>Session</literal> arbitraria.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
|
||||
.add( Property.forName("sex").eq('F') );
|
||||
|
||||
Session session = ....;
|
||||
Transaction txn = session.beginTransaction();
|
||||
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
|
||||
txn.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
También una <literal>DetachedCriteria</literal> puede usarse para expresar una subconsulta.
|
||||
Las instancias de Criterion implicando subconsultas pueden obtenerse vía <literal>Subqueries</literal> o
|
||||
<literal>Property</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
|
||||
.setProjection( Property.forName("weight").avg() );
|
||||
session.createCriteria(Cat.class)
|
||||
.add( Property.forName("weight").gt(avgWeight) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
|
||||
.setProjection( Property.forName("weight") );
|
||||
session.createCriteria(Cat.class)
|
||||
.add( Subqueries.geAll("weight", weights) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Incluso son posibles las subconsultas correlacionadas:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
|
||||
.setProjection( Property.forName("weight").avg() )
|
||||
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
|
||||
session.createCriteria(Cat.class, "cat")
|
||||
.add( Property.forName("weight").gt(avgWeightForSex) )
|
||||
.list();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
<!--TODO: ResultSetTransformer + aliasing. AliasToBeanTransformer allow returning arbitrary
|
||||
user objects - similar to setResultClass in JDO2. General use of ResultTransformer
|
||||
could also be explained. -->
|
||||
|
||||
<sect1 id="query-criteria-naturalid">
|
||||
<title>Consultas por identificador natural</title>
|
||||
|
||||
<para>
|
||||
Para la mayoría de consultas, incluyendo las consultas por criterios, el caché de consulta no es
|
||||
muy eficiente, debido a que la invalidación del caché de consulta ocurre demasiado frecuentemente.
|
||||
Sin embargo, hay un tipo especial de consulta donde podemos optimizar el algoritmo de invalidación
|
||||
de caché: búsquedas por una clave natural constante. En algunas aplicaciones, este tipo de consulta,
|
||||
ocurre frecuentemente. La API de criterios brinda especial provisión para este caso de uso.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Primero, debes mapear la clave natural de tu entidad usando
|
||||
<literal><natural-id></literal>, y habilitar el uso del caché de segundo nivel.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="User">
|
||||
<cache usage="read-write"/>
|
||||
<id name="id">
|
||||
<generator class="increment"/>
|
||||
</id>
|
||||
<natural-id>
|
||||
<property name="name"/>
|
||||
<property name="org"/>
|
||||
</natural-id>
|
||||
<property name="password"/>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que esta funcionalidad no está pensada para uso con entidades con claves naturales
|
||||
<emphasis>mutable</emphasis>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Seguido, habilita el caché de consulta de Hibernate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ahora, <literal>Restrictions.naturalId()</literal> nos permite hacer uso de el algoritmo de caché
|
||||
más eficiente.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[session.createCriteria(User.class)
|
||||
.add( Restrictions.naturalId()
|
||||
.set("name", "gavin")
|
||||
.set("org", "hb")
|
||||
).setCacheable(true)
|
||||
.uniqueResult();]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,477 +0,0 @@
|
|||
<chapter id="querysql" revision="2">
|
||||
<title>SQL Nativo</title>
|
||||
|
||||
<para>
|
||||
Puedes también expresar consultas en el dialecto SQL nativo de tu base de datos. Esto es útil si quieres
|
||||
utilizar aspectos específicos de base de datos tal como consejos (hints) de consulta o la palabra clave
|
||||
<literal>CONNECT</literal> en Oracle. Provee además una clara ruta de migración desde una aplicación
|
||||
basada en SQL/JDBC directo a Hibernate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hibernate3 te permite especificar SQL escrito a mano (incluyendo procedimientos almacenados) para todas
|
||||
las operaciones de creación, actualización, borrado y carga.
|
||||
</para>
|
||||
|
||||
<sect1 id="querysql-creating">
|
||||
<title>Creando una <literal>Query</literal> de SQL nativo</title>
|
||||
|
||||
<para>
|
||||
Las consultas SQL se controlan por medio de la interface <literal>SQLQuery</literal>, que se obtiene
|
||||
llamando a <literal>Session.createSQLQuery()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createSQLQuery("select {cat.*} from cats cat")
|
||||
.addEntity("cat", Cat.class)
|
||||
.setMaxResults(50)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esta consulta especificada:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
la cadena de consulta SQL, con un lugar para que Hibernate inyecte los alias de columnas
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
la entidad devuelta por la consulta, y sus alias de tablas SQL
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
El método <literal>addEntity()</literal> asocia alias de tablas SQL con clases de entidad,
|
||||
y determina la forma del conjunto resultado de la consulta.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El método <literal>addJoin()</literal> puede ser usado para cargar asociaciones a otras entidades y
|
||||
colecciones.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[List cats = sess.createSQLQuery(
|
||||
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
|
||||
)
|
||||
.addEntity("cat", Cat.class)
|
||||
.addJoin("kitten", "cat.kittens")
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Una consulta SQL nativa podría devolver un valor escalar simple o una combinación de escalares y entidades.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
|
||||
.addScalar("maxWeight", Hibernate.DOUBLE);
|
||||
.uniqueResult();]]></programlisting>
|
||||
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querysql-aliasreferences">
|
||||
<title>Alias y referencias de propiedad</title>
|
||||
|
||||
<para>
|
||||
La notación <literal>{cat.*}</literal> usada arriba es un atajo para "todas las propiedades".
|
||||
Alternativamente, puedes listar las columnas explícitamente, pero incluso en este caso dejamos
|
||||
que Hibernate inyecte los alias de columnas SQL para cada propiedad. El lugar para un alias de columna
|
||||
es sólo el nombre de propiedad cualificado por el alias de la tabla. En el siguiente ejemplo,
|
||||
recuperamos <literal>Cat</literal>s de una tabla diferente (<literal>cat_log</literal>) a una
|
||||
declarada en los metadatos de mapeo. Nota que podríamos incluso usar los alias de propiedad en la
|
||||
cláusula where si quisieramos.
|
||||
</para>
|
||||
<para>
|
||||
La sintáxis <literal>{}</literal> <emphasis>no</emphasis> es requerida para consultas con nombre.
|
||||
Ver <xref linkend="querysql-namedqueries"/>
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[String sql = "select cat.originalId as {cat.id}, " +
|
||||
"cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +
|
||||
"cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +
|
||||
"from cat_log cat where {cat.mate} = :catId"
|
||||
|
||||
List loggedCats = sess.createSQLQuery(sql)
|
||||
.addEntity("cat", Cat.class)
|
||||
.setLong("catId", catId)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
<emphasis>Nota:</emphasis> si listas cada propiedad explícitamente, ¡debes incluir todas las
|
||||
propiedades de la clase <emphasis>y sus subclases</emphasis>!
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querysql-namedqueries" revision="2">
|
||||
<title>Consultas SQL con nombre</title>
|
||||
|
||||
<para>
|
||||
Las consultas SQL con nombre pueden definirse en el documento de mapeo y llamadas exactamente
|
||||
en la misma forma en que a una consulta HQL con nombre. En este caso, <emphasis>no</emphasis>
|
||||
necesitamos llamar a <literal>addEntity()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="persons">
|
||||
<return alias="person" class="eg.Person"/>
|
||||
SELECT person.NAME AS {person.name},
|
||||
person.AGE AS {person.age},
|
||||
person.SEX AS {person.sex}
|
||||
FROM PERSON person
|
||||
WHERE person.NAME LIKE :namePattern
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[List people = sess.getNamedQuery("persons")
|
||||
.setString("namePattern", namePattern)
|
||||
.setMaxResults(50)
|
||||
.list();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Los elementos <literal><return-join></literal> y <literal><load-collection></literal>
|
||||
se usan para unir asociaciones y definir consultas que inicialicen colecciones, respectivamente.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="personsWith">
|
||||
<return alias="person" class="eg.Person"/>
|
||||
<return-join alias="address" property="person.mailingAddress"/>
|
||||
SELECT person.NAME AS {person.name},
|
||||
person.AGE AS {person.age},
|
||||
person.SEX AS {person.sex},
|
||||
address.STREET AS {address.street},
|
||||
address.CITY AS {address.city},
|
||||
address.STATE AS {address.state},
|
||||
address.ZIP AS {address.zip}
|
||||
FROM PERSON person
|
||||
JOIN ADDRESS address
|
||||
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
|
||||
WHERE person.NAME LIKE :namePattern
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Una consulta SQL con nombre puede devolver un valor escalar. Debes especificar el alias de columna y
|
||||
tipo Hibernate usando el elementp <literal><return-scalar></literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||
<return-scalar column="name" type="string"/>
|
||||
<return-scalar column="age" type="long"/>
|
||||
SELECT p.NAME AS name,
|
||||
p.AGE AS age,
|
||||
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<sect2 id="propertyresults">
|
||||
<title>Usando return-property para especificar explícitamente nombres de columna/alias</title>
|
||||
|
||||
<para>
|
||||
Con <literal><return-property></literal> puedes decirle explícitamente a Hibernate qué
|
||||
alias de columna usar, en vez de usar la sintáxis <literal>{}</literal> para dejar que Hibernate
|
||||
inyecte sus propios alias.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||
<return alias="person" class="eg.Person">
|
||||
<return-property name="name" column="myName"/>
|
||||
<return-property name="age" column="myAge"/>
|
||||
<return-property name="sex" column="mySex"/>
|
||||
</return>
|
||||
SELECT person.NAME AS myName,
|
||||
person.AGE AS myAge,
|
||||
person.SEX AS mySex,
|
||||
FROM PERSON person WHERE person.NAME LIKE :name
|
||||
</sql-query>
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
<literal><return-property></literal> también trabaja con múltiples columnas. Esto resuelve una
|
||||
limitación de la sintáxis <literal>{}</literal>, la cual no puede permitir un control fino de propiedades
|
||||
multi-columna.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="organizationCurrentEmployments">
|
||||
<return alias="emp" class="Employment">
|
||||
<return-property name="salary">
|
||||
<return-column name="VALUE"/>
|
||||
<return-column name="CURRENCY"/>
|
||||
</return-property>
|
||||
<return-property name="endDate" column="myEndDate"/>
|
||||
</return>
|
||||
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
|
||||
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
|
||||
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
|
||||
FROM EMPLOYMENT
|
||||
WHERE EMPLOYER = :id AND ENDDATE IS NULL
|
||||
ORDER BY STARTDATE ASC
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que en este ejemplo hemos usado <literal><return-property></literal> en combinación con
|
||||
la sintáxis <literal>{}</literal> para inyección, permitiendo a los usuarios elejir cómo quieren
|
||||
referirse a las columnas y propiedades.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si tu mapeo tiene un discriminador debes usar <literal><return-discriminator></literal>
|
||||
para especificar la columna discriminadora.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="sp_query">
|
||||
<title>Usando procedimientos almacenados para consultar</title>
|
||||
|
||||
<para>
|
||||
Hibernate3 introduce soporte para consultas vía procedimientos almacenados. Los procedimientos
|
||||
almacenados deben devolver un conjunto resultado como el primer parámetro de salida para ser
|
||||
capaces de funcionar con Hibernate. Un ejemplo de uno procedimiento almacenado en Oracle 9
|
||||
o superior es así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION selectAllEmployments
|
||||
RETURN SYS_REFCURSOR
|
||||
AS
|
||||
st_cursor SYS_REFCURSOR;
|
||||
BEGIN
|
||||
OPEN st_cursor FOR
|
||||
SELECT EMPLOYEE, EMPLOYER,
|
||||
STARTDATE, ENDDATE,
|
||||
REGIONCODE, EID, VALUE, CURRENCY
|
||||
FROM EMPLOYMENT;
|
||||
RETURN st_cursor;
|
||||
END;]]></programlisting>
|
||||
|
||||
<para>
|
||||
Para usar esta consulta en Hibernate necesitas mapearla por medio de una consulta con nombre.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="selectAllEmployees_SP" callable="true">
|
||||
<return alias="emp" class="Employment">
|
||||
<return-property name="employee" column="EMPLOYEE"/>
|
||||
<return-property name="employer" column="EMPLOYER"/>
|
||||
<return-property name="startDate" column="STARTDATE"/>
|
||||
<return-property name="endDate" column="ENDDATE"/>
|
||||
<return-property name="regionCode" column="REGIONCODE"/>
|
||||
<return-property name="id" column="EID"/>
|
||||
<return-property name="salary">
|
||||
<return-column name="VALUE"/>
|
||||
<return-column name="CURRENCY"/>
|
||||
</return-property>
|
||||
</return>
|
||||
{ ? = call selectAllEmployments() }
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Nota que los procedimientos almacenados sólo devuelven escalares y entidades.
|
||||
No están soportados <literal><return-join></literal> y <literal><load-collection></literal>.
|
||||
</para>
|
||||
|
||||
<sect3 id="querysql-limits-storedprocedures">
|
||||
<title>Reglas/limitaciones para usar procedimientos almacenados</title>
|
||||
|
||||
<para>
|
||||
Para usar procedimientos almacenados con Hibernate los procedimientos tienen que seguir algunas reglas.
|
||||
Si no siguen esas reglas no son usables por Hibernate. Si aún quisieras usar estos procedimientos
|
||||
tendrías que ejecutarlos por medio de <literal>session.connection()</literal>. Las reglas son
|
||||
diferentes para cada base de datos, ya que los vendedores de base de datos tienen diferentes
|
||||
semánticas/sintáxis de procedimientos almacenados.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las consultas de procedimientos almacenados no pueden ser paginadas con
|
||||
<literal>setFirstResult()/setMaxResults()</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para Oracle se aplican las siguientes reglas:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
El procedimiento debe devolver un conjunto resultado. Esto se hace devolviendo un
|
||||
<literal>SYS_REFCURSOR</literal> en Oracle 9 o 10. En Oracle necesitas definir un
|
||||
tipo <literal>REF CURSOR</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
La forma recomendada es <literal>{ ? = call procName(<parameters>) }</literal> o
|
||||
<literal>{ ? = call procName }</literal> (esto es más una regla de Oracle que una regla de Hibernate).
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Para Sybase o MS SQL server se aplican las siguientes reglas:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
El procedimiento debe devolver un conjunto resultado. Nota que ya que estos servidores pueden
|
||||
y devolverán múltiples conjuntos resultados y cuentas de actualización, Hibernate iterará
|
||||
los resultados y tomará el primer resultado que sea un conjunto resultado como su valor
|
||||
a devolver. Todo lo demás será descartado.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Si habilitas <literal>SET NOCOUNT ON</literal> en tu procedimiento será probablemente más
|
||||
eficiente, pero esto no es un requerimiento.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</sect3>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querysql-cud">
|
||||
<title>SQL personalizado para crear, actualizar y borrar</title>
|
||||
|
||||
<para>
|
||||
Hibernate3 puede usar sentencias SQL personalizadas para las operaciones de
|
||||
crear, actualizar y borrar. Los persistidores de clases y colecciones en Hibernate
|
||||
ya contienen un conjunto de cadenas generadas en tiempo de configuración (insertsql,
|
||||
deletesql, updatesql, etc.). Las etiquetas de mapeo <literal><sql-insert></literal>,
|
||||
<literal><sql-delete></literal>, y <literal><sql-update></literal> sobrescriben
|
||||
estas cadenas:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id">
|
||||
<generator class="increment"/>
|
||||
</id>
|
||||
<property name="name" not-null="true"/>
|
||||
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
|
||||
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
|
||||
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
El SQL se ejecuta directamente en tu base de datos, de modo que eres libre de usar cualquier
|
||||
dialecto que quieras. Esto reducirá, por supuesto, la portabilidad de tu mapeo si usas SQL
|
||||
específico de la base de datos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los procedimientos almacenados son soportados si está establecido el atributo
|
||||
<literal>callable</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id">
|
||||
<generator class="increment"/>
|
||||
</id>
|
||||
<property name="name" not-null="true"/>
|
||||
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
|
||||
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
|
||||
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
El orden de los parámetros posicionales son actualmente vitales, ya que deben estar en la
|
||||
misma secuencia en que las espera Hibernate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puedes ver el orden esperado habilitando el registro de depuración para el nivel
|
||||
<literal>org.hibernate.persister.entity</literal>. Con este nivel habilitado, Hibernate
|
||||
imprimirá el SQL estático que se usa para crear, actualizar, borrar, etc. las entidades.
|
||||
(Para ver la secuencia esperada, recuerda no incluir tu SQL personalizado en los ficheros
|
||||
de mapeo ya que sobrescribirán el sql estático generado por Hibernate.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los procedimientos almacenados son, en la mayoría de los casos (léase, mejor hacerlo que no hacerlo),
|
||||
obligados a devolver el número de filas insertadas/actualizadas/borradas, ya que Hibernate tiene algunas
|
||||
comprobaciones en tiempo de ejecución del éxito de la sentencia. Hibernate siempre registra el primer
|
||||
parámetro de la sentencia como un parámetro de salida numérico para las operaciones CUD:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
|
||||
RETURN NUMBER IS
|
||||
BEGIN
|
||||
|
||||
update PERSON
|
||||
set
|
||||
NAME = uname,
|
||||
where
|
||||
ID = uid;
|
||||
|
||||
return SQL%ROWCOUNT;
|
||||
|
||||
END updatePerson;]]></programlisting>
|
||||
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="querysql-load">
|
||||
<title>SQL personalizado para carga</title>
|
||||
|
||||
<para>
|
||||
Puedes también declarar tu propias consultas SQL (o HQL) para cargar entidades:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="person">
|
||||
<return alias="pers" class="Person" lock-mode="upgrade"/>
|
||||
SELECT NAME AS {pers.name}, ID AS {pers.id}
|
||||
FROM PERSON
|
||||
WHERE ID=?
|
||||
FOR UPDATE
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esto es sólo una declaración de consulta con nombrem como se ha discutido anteriormente.
|
||||
Puedes hacer referencia a esta consulta con nombre en un mapeo de clase:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Person">
|
||||
<id name="id">
|
||||
<generator class="increment"/>
|
||||
</id>
|
||||
<property name="name" not-null="true"/>
|
||||
<loader query-ref="person"/>
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esto incluso funciona con procedimientos almacenados.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puedes incluso definit una consulta para la carga de colecciones:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<set name="employments" inverse="true">
|
||||
<key/>
|
||||
<one-to-many class="Employment"/>
|
||||
<loader query-ref="employments"/>
|
||||
</set>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="employments">
|
||||
<load-collection alias="emp" role="Person.employments"/>
|
||||
SELECT {emp.*}
|
||||
FROM EMPLOYMENT emp
|
||||
WHERE EMPLOYER = :id
|
||||
ORDER BY STARTDATE ASC, EMPLOYEE ASC
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Podrías incluso definir un cargador de entidades que cargue una colección por
|
||||
recuperación por unión (join fetching):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<sql-query name="person">
|
||||
<return alias="pers" class="Person"/>
|
||||
<return-join alias="emp" property="pers.employments"/>
|
||||
SELECT NAME AS {pers.*}, {emp.*}
|
||||
FROM PERSON pers
|
||||
LEFT OUTER JOIN EMPLOYMENT emp
|
||||
ON pers.ID = emp.PERSON_ID
|
||||
WHERE ID=?
|
||||
</sql-query>]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
|
@ -1,666 +0,0 @@
|
|||
<chapter id="quickstart">
|
||||
<title>Comienzo rápido con Tomcat</title>
|
||||
|
||||
<sect1 id="quickstart-intro" revision="2">
|
||||
<title>Empezando con Hibernate</title>
|
||||
|
||||
<para>
|
||||
Este tutorial explica una instalación de Hibernate con el
|
||||
contenedor de servlets Apache Tomcat (hemos usado la versión 4.1,
|
||||
las diferencias con la 5.0 deben ser mínimas) para una aplicación
|
||||
basada en web. Hibernate trabaja bien en un entorno manejado con
|
||||
todos los servidores de aplicaciones J2EE importantes, o incluso en aplicaciones
|
||||
Java independientes. El sistema de base de datos es sólo una cuestión
|
||||
de cambiar la configuración del dialecto SQL de Hibernate y las
|
||||
propiedades de conexión.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Primero, tenemos que copiar todas las bibliotecas requeridas a la instalación
|
||||
de Tomcat. Usamos un contexto web separado (<literal>webapps/quickstart</literal>)
|
||||
para este tutorial, de modo que tenemos que considerar tanto la ruta de búsqueda
|
||||
de bibliotecas global (<literal>TOMCAT/common/lib</literal>) como también
|
||||
el cargador de clases a nivel de contexto en <literal>webapps/quickstart/WEB-INF/lib</literal>
|
||||
(para ficheros JAR) y <literal>webapps/quickstart/WEB-INF/classes</literal>.
|
||||
Nos referiremos a ambos niveles de cargador de clases como el classpath global y el classpath
|
||||
de contexto, respectivamente.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ahora, copia las bibliotecas a los dos classpaths:
|
||||
</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Copia el driver JDBC para la base de datos al classpath global. Esto se
|
||||
requiere para el software de pool de conexiones DBCP que se distribuye
|
||||
con Tomcat. Hibernate usa conexiones JDBC para ejecutar SQL sobre la base de
|
||||
datos, de modo que, o bien tienes que proveer conexiones JDBC en pool,
|
||||
o bien configurar Hibernate para que use uno de los pools soportados
|
||||
directamente (C3P0, Proxool). Para este tutorial, copia la biblioteca
|
||||
<literal>pg74jdbc3.jar</literal> (para PostgreSQL 7.4 y JDK 1.4) al
|
||||
classpath del cargador global. Si quisieras usar una base de datos diferente,
|
||||
simplemente copia su apropiado driver JDBC.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Nunca copies nada más dentro de la ruta del cargador de clases global
|
||||
en Tomcat, o tendrás problemas con varias herramientas, incluyendo
|
||||
Log4J, commons-logging y otras. Siempre usa el classpath de contexto para
|
||||
cada aplicación web, esto es, copia las bibliotecas a
|
||||
<literal>WEB-INF/lib</literal> y tus propias clases y ficheros de
|
||||
configuración/propiedades a <literal>WEB-INF/classes</literal>.
|
||||
Ambos directorios están a nivel del classpath de contexto por defecto.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Hibernate está empaquetado como una biblioteca JAR. El fichero
|
||||
<literal>hibernate3.jar</literal> debe ser copiado en el classpath de contexto
|
||||
junto a las otras clases de la aplicación. Hibernate requiere algunas
|
||||
bibliotecas de terceros en tiempo de ejecución; éstas vienen
|
||||
incluídas con la distribución de Hibernate en el directorio
|
||||
<literal>lib/</literal>. Ver <xref linkend="3rdpartylibs"/>. Copia las
|
||||
bibliotecas de terceros requeridas al classpath de contexto.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<table frame="topbot" id="3rdpartylibs">
|
||||
<title>
|
||||
Bibliotecas de terceros de Hibernate
|
||||
</title>
|
||||
<tgroup cols="2" rowsep="1" colsep="1">
|
||||
<colspec colname="c1" colwidth="1*"/>
|
||||
<colspec colname="c2" colwidth="2*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry align="center">
|
||||
Biblioteca
|
||||
</entry>
|
||||
<entry align="center">
|
||||
Descripción
|
||||
</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
antlr (requerida)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate usa ANTLR para producir analizadores de consultas,
|
||||
esta biblioteca también se necesita en tiempo de ejecución.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
dom4j (requerida)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate usa dom4j para analizar ficheros de configuración
|
||||
XML y ficheros de metadatos de mapeo XML.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
CGLIB, asm (requerida)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate usa la biblioteca de generación de código
|
||||
para aumentar las clases en tiempo de ejecución
|
||||
(en combinación con reflección Java).
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
Commons Collections, Commons Logging (requeridas)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate usa varias bibliotecas de utilidad del proyecto
|
||||
Jakarta Commons de Apache.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
EHCache (requerida)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate puede usar varios provedores de caché para
|
||||
el caché de segundo nivel. EHCache es el provedor de
|
||||
caché por defecto si no se cambia en la configuración.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
Log4j (opcional)
|
||||
</entry>
|
||||
<entry>
|
||||
Hibernate usa la API de Commons Logging, que a su vez puede
|
||||
usar Log4J como el mecanismo de logging subyacente. Si la
|
||||
biblioteca Log4J está disponible en el directorio de
|
||||
bibliotecas del contexto, Commons Logging usará Log4J
|
||||
y la configuración <literal>log4j.properties</literal>
|
||||
en el classpath de contexto. Un fichero de propiedades de ejemplo
|
||||
para Log4J se incluye con la distribución de Hibernate.
|
||||
Así que copia log4j.jar y el fichero de configuración
|
||||
(de <literal>src/</literal>) a tu classpath de contexto si quieres
|
||||
ver que ocurre tras escénas.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
¿Requerida o no?
|
||||
</entry>
|
||||
<entry>
|
||||
Echa una mirada al fichero <literal>lib/README.txt</literal> en la
|
||||
distribución de Hibernate. Esta es una lista actualizada
|
||||
de bibliotecas de terceros distribuídas con Hibernate.
|
||||
Encontrarás listadas ahí todas las bibliotecas
|
||||
requeridas y opcionales (Observa que "buildtame required" significa
|
||||
aquí para la construcción de Hibernate, no de tu
|
||||
aplicación).
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
Ahora instalamos el pooling y modo compartido de conexiones de base de datos
|
||||
tanto en Tomcat como Hibernate. Esto significa que Tomcat proveerá
|
||||
conexiones JDBC en pool (usando su funcionalidad prefabricada de pooling DBCP).
|
||||
Hibernate pide esas conexiones a través de JNDI. Alternativamente,
|
||||
puedes dejar que Hibernate maneje el pool de conexiones. Tomcat liga su pool
|
||||
de conexiones a JNDI; agregamos una declaración de recurso al fichero
|
||||
de configuración principal de Tomcat, <literal>TOMCAT/conf/server.xml</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<Context path="/quickstart" docBase="quickstart">
|
||||
<Resource name="jdbc/quickstart" scope="Shareable" type="javax.sql.DataSource"/>
|
||||
<ResourceParams name="jdbc/quickstart">
|
||||
<parameter>
|
||||
<name>factory</name>
|
||||
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
|
||||
</parameter>
|
||||
|
||||
<!-- DBCP database connection settings -->
|
||||
<parameter>
|
||||
<name>url</name>
|
||||
<value>jdbc:postgresql://localhost/quickstart</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>driverClassName</name><value>org.postgresql.Driver</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>username</name>
|
||||
<value>quickstart</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>password</name>
|
||||
<value>secret</value>
|
||||
</parameter>
|
||||
|
||||
<!-- DBCP connection pooling options -->
|
||||
<parameter>
|
||||
<name>maxWait</name>
|
||||
<value>3000</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>maxIdle</name>
|
||||
<value>100</value>
|
||||
</parameter>
|
||||
<parameter>
|
||||
<name>maxActive</name>
|
||||
<value>10</value>
|
||||
</parameter>
|
||||
</ResourceParams>
|
||||
</Context>]]></programlisting>
|
||||
|
||||
<para>
|
||||
El contexto que configuramos en este ejemplo se llama <literal>quickstart</literal>,
|
||||
su base es el directorio <literal>TOMCAT/webapp/quickstart</literal>. Para acceder
|
||||
a cualquier servlet, llama a la ruta <literal>http://localhost:8080/quickstart</literal>
|
||||
en tu navegador (por supuesto, agregando el nombre del servlet como se mapee en tu
|
||||
<literal>web.xml</literal>). Puedes también ir más allá y crear
|
||||
ahora un servlet simple que tenga un método <literal>process()</literal>
|
||||
vacío.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Tomcat provee ahora conexiones a través de JNDI en
|
||||
<literal>java:comp/env/jdbc/quickstart</literal>. Si tienes problemas obteniendo
|
||||
el pool de conexiones en ejecución, refiérete a la documentación
|
||||
de Tomcat. Si obtienes mensajes de excepción del driver JDBC, intenta instalar
|
||||
primero el pool de conexiones JDBC sin Hibernate. Hay disponibles en la Web
|
||||
tutoriales de Tomcat y JDBC.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Tu próximo paso es configurar Hibernate. Hibernate tiene que saber cómo
|
||||
debe obtener conexiones JDBC. Usamos la configuración de Hibernate basada en XML.
|
||||
El otro enfoque, usando un ficheros de propiedad, es casi equivalente pero pierde unas
|
||||
pocas funcionalidades que sí permite la sintaxis XML. El fichero de configuración
|
||||
XML se ubica en el classpath de contexto (<literal>WEB-INF/classes</literal>), como
|
||||
<literal>hibernate.cfg.xml</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<?xml version='1.0' encoding='utf-8'?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
|
||||
<session-factory>
|
||||
|
||||
<property name="connection.datasource">java:comp/env/jdbc/quickstart</property>
|
||||
<property name="show_sql">false</property>
|
||||
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
|
||||
|
||||
<!-- Mapping files -->
|
||||
<mapping resource="Cat.hbm.xml"/>
|
||||
|
||||
</session-factory>
|
||||
|
||||
</hibernate-configuration>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Desactivamos el registro (logging) de comandos SQL y decimos a Hibernate
|
||||
qué dialecto SQL de base de datos se usa y dónde obtener
|
||||
conexiones JDBC (declarando la dirección JNDI del pool ligado a
|
||||
Tomcat). El dialecto es una configuración requerida, las bases de
|
||||
datos difieren en su interpretación del "estándar" de SQL.
|
||||
Hibernate cuidará de las diferencias y viene con dialectos incluídos
|
||||
para todas las principales bases de datos comerciales y de código
|
||||
abierto.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una <literal>SessionFactory</literal> es el concepto de Hibernate
|
||||
de un almacén de datos solo. Pueden usarse múltiples
|
||||
bases de datos creando múltiples ficheros de configuración
|
||||
XML y creando múltiples objetos <literal>Configuration</literal>
|
||||
y <literal>SessionFactory</literal> en tu aplicación.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El último elemento del <literal>hibernate.cfg.xml</literal>
|
||||
declara <literal>Cat.hbm.xml</literal> como el nombre de un fichero
|
||||
de mapeo XML para la clase persistente <literal>Cat</literal>. Este
|
||||
fichero contiene los metadatos para el mapeo de la clase POJO
|
||||
<literal>Cat</literal> a una tabla (o tablas) de base de datos.
|
||||
Volveremos a este fichero pronto. Escribamos primero la clase POJO
|
||||
y luego declaremos los metadatos de mapeo para ella.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="quickstart-persistentclass" revision="1">
|
||||
<title>Primera clase persistente</title>
|
||||
|
||||
<para>
|
||||
Hibernate trabaja mejor con el modelo de programación de los
|
||||
Viejos Objetos Planos de Java (POJOs, a veces llamados Ordinarios Objetos Planos de Java)
|
||||
para clases persistentes. Un POJO es como un JavaBean, con las propiedades
|
||||
de la clase accesible vía métodos getter y setter,
|
||||
encapsulando la representación interna de la interfaz publicamente
|
||||
visible (Hibernate puede también acceder a los campos directamente, si se
|
||||
necesita):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[package org.hibernate.examples.quickstart;
|
||||
|
||||
public class Cat {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private char sex;
|
||||
private float weight;
|
||||
|
||||
public Cat() {
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public char getSex() {
|
||||
return sex;
|
||||
}
|
||||
|
||||
public void setSex(char sex) {
|
||||
this.sex = sex;
|
||||
}
|
||||
|
||||
public float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(float weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Hibernate no está restringido en su uso de tipos de propiedad, todos
|
||||
los tipos y tipos primitivos del JDK de Java (como <literal>String</literal>,
|
||||
<literal>char</literal> y <literal>Date</literal>) pueden ser mapeados, incluyendo
|
||||
clases del framework de colecciones de Java. Puedes mapearlos como valores,
|
||||
colecciones de valores, o asociaciones a otras entidades. El <literal>id</literal>
|
||||
es una propiedad especial que representa el identificador de base de datos (clave
|
||||
primaria) de la clase. Es altamente recomendado para entidades como un
|
||||
<literal>Cat</literal>. Hibernate puede usar identificadores sólo
|
||||
internamente, pero perderíamos algo de la flexibilidad en nuestra
|
||||
arquitectura de aplicación.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
No tiene que implementarse ninguna interface especial para las clases persistentes
|
||||
ni tienes que subclasear de una clase persistente raíz en especial. Hibernate
|
||||
tampoco requiere ningún procesamiento en tiempo de construcción,
|
||||
como manipulación del byte-code. Se basa solamente en reflección de Java
|
||||
y aumentación de clases en tiempo de ejecución (a través de CGLIB).
|
||||
De modo que, sin ninguna dependencia de la clase POJO en Hibernate, podemos mapearla
|
||||
a una tabla de base de datos.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="quickstart-mapping" revision="1">
|
||||
<title>Mapeando el gato</title>
|
||||
|
||||
<para>
|
||||
El fichero de mapeo <literal>Cat.hbm.xml</literal> contiene los
|
||||
metadatos requeridos para el mapeo objeto/relacional. Los metadatos
|
||||
incluyen la declaración de clases persistentes y el mapeo de
|
||||
propiedades (a columnas y relaciones de claves foráneas a otras
|
||||
entidades) a tablas de base de datos.
|
||||
</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>
|
||||
|
||||
<class name="org.hibernate.examples.quickstart.Cat" table="CAT">
|
||||
|
||||
<!-- A 32 hex character is our surrogate key. It's automatically
|
||||
generated by Hibernate with the UUID pattern. -->
|
||||
<id name="id" type="string" unsaved-value="null" >
|
||||
<column name="CAT_ID" sql-type="char(32)" not-null="true"/>
|
||||
<generator class="uuid.hex"/>
|
||||
</id>
|
||||
|
||||
<!-- A cat has to have a name, but it shouldn' be too long. -->
|
||||
<property name="name">
|
||||
<column name="NAME" length="16" not-null="true"/>
|
||||
</property>
|
||||
|
||||
<property name="sex"/>
|
||||
|
||||
<property name="weight"/>
|
||||
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Cada clase persistente debe tener un atributo identificador (realmente,
|
||||
sólo las clases que representen entidades, no las clases dependientes
|
||||
de tipo-valor, que son mapeadas como componentes de una entidad). Esta propiedad
|
||||
es usada para distinguir los objetos persistentes: Dos gatos son iguales si
|
||||
<literal>catA.getId().equals(catB.getId())</literal> es verdadero. Este concepto
|
||||
se llama <emphasis>identidad de base de datos (database identity)</emphasis>.
|
||||
Hibernate viene empaquetado con varios generadores de identificador para diferentes
|
||||
escenarios (incluyendo generadores nativos para secuencias de base de datos, tablas
|
||||
de identificadores alto/bajo, e identificadores asignados por aplicación).
|
||||
Usamos el generador UUID (recomendado sólo para pruebas, pues deben
|
||||
preferirse las claves enteras delegadas generadas por la base de datos) y
|
||||
también especificamos la columna <literal>CAT_ID</literal> de la tabla
|
||||
<literal>CAT</literal> para el valor identificador generado por Hibernate
|
||||
(como una clave primaria de la tabla).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Todas las demás propiedades de <literal>Cat</literal> son mapeadas a la
|
||||
misma tabla. En el caso de la propiedad <literal>name</literal>, la hemos mapeado
|
||||
con una declaración explícita de columna de base de datos. Esto es
|
||||
especialmente útil cuando el esquema de base de datos es generado
|
||||
automáticamente (como sentencias DDL de SQL) desde la declaración
|
||||
de mapeo con la herramienta <emphasis>SchemaExport</emphasis> de Hibernate.
|
||||
Todas las demás propiedades son mapeadas usando la configuración
|
||||
por defecto de Hibernate, que es lo que necesitas la mayoría del tiempo.
|
||||
La tabla <literal>CAT</literal> en la base de datos se ve así como:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[ Columna | Tipo | Modificadores
|
||||
--------+-----------------------+-----------
|
||||
cat_id | character(32) | not null
|
||||
name | character varying(16) | not null
|
||||
sex | character(1) |
|
||||
weight | real |
|
||||
Indexes: cat_pkey primary key btree (cat_id)]]></programlisting>
|
||||
|
||||
<para>
|
||||
Ahora debes crear esta tabla manualmente en tu base de datos, y luego leer el
|
||||
<xref linkend="toolsetguide"/> si quieres automatizar este paso con la
|
||||
herramienta <literal>hbm2ddl</literal>. Esta herramienta puede crear un
|
||||
DDL SQL completo, incluyendo definición de tablas, restricciones
|
||||
personalizadas de tipo de columnas, restricciones de unicidad e índices.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="quickstart-playingwithcats" revision="2">
|
||||
<title>Jugando con gatos</title>
|
||||
|
||||
<para>
|
||||
Ahora estamos listos para comenzar la <literal>Session</literal> de Hibernate.
|
||||
Es el <emphasis>manejador de persistencia</emphasis> que usamos para almacenar
|
||||
y traer <literal>Cat</literal>s hacia y desde la base de datos. Pero primero,
|
||||
tenemos que obtener una <literal>Session</literal> (unidad de trabajo de Hibernate)
|
||||
de la <literal>SessionFactory</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[SessionFactory sessionFactory =
|
||||
new Configuration().configure().buildSessionFactory();]]></programlisting>
|
||||
|
||||
<para>
|
||||
La llamada a <literal>configure()</literal> carga el fichero de
|
||||
configuración <literal>hibernate.cfg.xml</literal> e
|
||||
inicializa la instancia de <literal>Configuration</literal>.
|
||||
Puedes establecer otras propiedades (e incluso cambiar los metadatos de mapeo)
|
||||
accediendo a la <literal>Configuration</literal> <emphasis>antes</emphasis>
|
||||
que construyas la <literal>SessionFactory</literal> (que es inmutable).
|
||||
¿Dónde creamos la <literal>SessionFactory</literal> y cómo
|
||||
accedemos a ella en nuestra aplicación?
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una <literal>SessionFactory</literal> usualmente se construye una vez,
|
||||
por ejemplo, al arrancar con un servlet <emphasis>load-on-startup</emphasis>.
|
||||
Esto significa también que no debes mantenerla en una variable de instancia
|
||||
en tus servlets, sino en alguna otro sitio. Además, necesitamos algún
|
||||
tipo de <emphasis>Singleton</emphasis>, de modo que podamos acceder a la
|
||||
<literal>SessionFactory</literal> fácilmente en el código de
|
||||
aplicación. El siguiente enfoque mostrado resuelve ambos problemas:
|
||||
configuración de arranque y fácil acceso a una
|
||||
<literal>SessionFactory</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Implementamos una clase de ayuda <literal>HibernateUtil</literal>:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[import org.hibernate.*;
|
||||
import org.hibernate.cfg.*;
|
||||
|
||||
public class HibernateUtil {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(HibernateUtil.class);
|
||||
|
||||
private static final SessionFactory sessionFactory;
|
||||
|
||||
static {
|
||||
try {
|
||||
// Create the SessionFactory
|
||||
sessionFactory = new Configuration().configure().buildSessionFactory();
|
||||
} catch (Throwable ex) {
|
||||
// Make sure you log the exception, as it might be swallowed
|
||||
log.error("Initial SessionFactory creation failed.", ex);
|
||||
throw new ExceptionInInitializerError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static final ThreadLocal session = new ThreadLocal();
|
||||
|
||||
public static Session currentSession() {
|
||||
Session s = (Session) session.get();
|
||||
// Open a new Session, if this Thread has none yet
|
||||
if (s == null) {
|
||||
s = sessionFactory.openSession();
|
||||
session.set(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static void closeSession() {
|
||||
Session s = (Session) session.get();
|
||||
if (s != null)
|
||||
s.close();
|
||||
session.set(null);
|
||||
}
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esta clase no sólo cuida de la <literal>SessionFactory</literal>
|
||||
con su inicializador static, sino que además tiene una variable
|
||||
<literal>ThreadLocal</literal> que tiene la <literal>Session</literal>
|
||||
para la hebra actual. Asegúrate de entender el concepto Java de una
|
||||
variable local a una hebra antes de intentar usar esta ayuda. Una clase
|
||||
<literal>HibernateUtil</literal> más compleja y potente puede
|
||||
encontrarse en <literal>CaveatEmptor</literal>, http://caveatemptor.hibernate.org/
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una <literal>SessionFactory</literal> es segura entre hebras, muchas hebras pueden
|
||||
acceder a ella concurrentemente y pedirle <literal>Session</literal>s. Una
|
||||
<literal>Session</literal> no es un objeto seguro entre hebras que representa
|
||||
una sola unidad-de-trabajo con la base de datos. Las <literal>Session</literal>s
|
||||
se abren desde una <literal>SessionFactory</literal> y son cerradas cuando
|
||||
todo el trabajo está completo. Un ejemplo en el método
|
||||
<literal>process()</literal> de tu servlet podría parecerse a esto
|
||||
(sin manejo de excepciones):
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Session session = HibernateUtil.currentSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
Cat princess = new Cat();
|
||||
princess.setName("Princess");
|
||||
princess.setSex('F');
|
||||
princess.setWeight(7.4f);
|
||||
|
||||
session.save(princess);
|
||||
|
||||
tx.commit();
|
||||
HibernateUtil.closeSession();]]></programlisting>
|
||||
|
||||
<para>
|
||||
En una <literal>Session</literal>, cada operación de base de datos
|
||||
ocurre dentro de una transacción que aísla las operaciones
|
||||
de base de datos (incluso operaciones de sólo lectura).
|
||||
Usamos la API de <literal>Transaction</literal> de Hibernate para
|
||||
abstraer de la estrategia de transacciones subyacente (en nuestro caso,
|
||||
transacciones JDBC). Esto permite que nuestro código sea desplegado
|
||||
con transacciones manejadas por contenedor (usando JTA) sin cambio alguno.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Observa que puedes llamar <literal>HibernateUtil.currentSession();</literal>
|
||||
tantas veces como quieras, siempre obtendrás la <literal>Session</literal>
|
||||
actual de esta hebra. Tienes que asegurarte que la <literal>Session</literal>
|
||||
sea cerrada después que se complete tu unidad-de-trabajo, ya sea en
|
||||
código de tu servlet o en un filtro de servlet antes que la respuesta HTTP
|
||||
sea enviada. El bonito efecto colateral de la segunda opción es la
|
||||
fácil inicialización perezosa: la <literal>Session</literal> todavía
|
||||
está abierta cuando se dibuja la vista, de modo que Hibernate puede cargar
|
||||
objetos no inicializados mientras navegas tu actual grafo de objetos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hibernate tiene varios métodos que pueden ser usados para traer
|
||||
objetos desde la base de datos. La forma más flexible es usando
|
||||
el Lenguaje de Consulta de Hibernate (Hibernate Query Language o HQL),
|
||||
que es una extensión orientada a objetos de SQL fácil de
|
||||
aprender:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Transaction tx = session.beginTransaction();
|
||||
|
||||
Query query = session.createQuery("select c from Cat as c where c.sex = :sex");
|
||||
query.setCharacter("sex", 'F');
|
||||
for (Iterator it = query.iterate(); it.hasNext();) {
|
||||
Cat cat = (Cat) it.next();
|
||||
out.println("Female Cat: " + cat.getName() );
|
||||
}
|
||||
|
||||
tx.commit();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Hibernate también ofrece una API <emphasis>consulta por criterios</emphasis>
|
||||
orientada a objetos que puede ser usada para formular consultas de tipo seguro.
|
||||
Por supuesto, Hibernate usa <literal>PreparedStatement</literal>s y ligado de
|
||||
parámetros para toda la comunicación SQL con la base de datos.
|
||||
También puedes usar la funcionalidad de consulta SQL directa de Hibernate
|
||||
u obtener una conexión plana de JDBC de una <literal>Session</literal>
|
||||
en casos raros.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="quickstart-summary" revision="1">
|
||||
<title>Finalmente</title>
|
||||
|
||||
<para>
|
||||
Rasguñamos solamente la superficie de Hibernate en este pequeño
|
||||
tutorial. Por favor, observa que no incluimos ningún código
|
||||
específico de servlet en nuestros ejemplos. Tienes que crear un servlet
|
||||
por tí mismo e insertar el código de Hibernate como lo veas
|
||||
ubicado.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ten en mente que Hibernate, como capa de acceso a datos, está firmemente
|
||||
integrado dentro de tu aplicación. Usualmente, todas las otras capas dependen
|
||||
del mecanismo de persistencia. Asegúrate de entender las implicaciones
|
||||
de este diseño.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para un ejemplo de aplicación más compleja, ver
|
||||
http://caveatemptor.hibernate.org/ y echa una mirada a los
|
||||
otros tutoriales con links en http://www.hibernate.org/Documentation
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,459 +0,0 @@
|
|||
<chapter id="toolsetguide" revision="2">
|
||||
<title>Guía del Conjunto de Herramientas</title>
|
||||
|
||||
<para>
|
||||
La ingeniería de ida y vuelta con Hibernate es posible usando un conjunto de plugins de Eclipse,
|
||||
herramientas de línea de comandos, así como tareas de Ant.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Las <emphasis>Herramientas de Hibernate</emphasis> actualmente incluyen plugins para la IDE de
|
||||
Eclipse así como tareas de Ant para la ingeniería inversa de bases de datos existentes:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>
|
||||
<emphasis>Editor de Mapeo:</emphasis> Un editor de ficheros de mapeo XML, que soporta autocompleción
|
||||
y resaltado de sintáxis. Soporta también autocompleción semántica de nombres de clases y nombres de
|
||||
campos/propiedades, haciéndolo mucho más versátil que un editor de XML normal.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<emphasis>Consola:</emphasis> La consola es una nueva vista en Eclipse. Además de la vista de
|
||||
árbol de tus configuraciones de consola, tienes también una vista interactiva de tus clases
|
||||
persistentes y sus relaciones. La console te permite ejecutar consultas HQL contra tu base de datos y
|
||||
navegar el resultado directamente en Eclipse.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<emphasis>Asistentes de Desarrollo:</emphasis> Se proveen muchos asistentes con las herramientas
|
||||
de Eclipse. Puedes usar un asistente para generar rápidamente ficheros de configuración de Hibernate
|
||||
(cfg.xml), o incluso puedes haceruna ingeniería inversa completa de un esquema de base de datos existente
|
||||
en ficheros de código de POJO y ficheros de mapeo de Hibernate. El asistente de ingeniería inversa soporta
|
||||
plantillas personalizables.
|
||||
</para></listitem>
|
||||
<listitem><para>
|
||||
<emphasis>Tareas de Ant:</emphasis>
|
||||
</para></listitem>
|
||||
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Por favor refiérete al paquete <emphasis>Herramientas de Hibernate</emphasis> y su documentación para
|
||||
más información.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Sin embargo, el paquete principal de Hibernate viene incluyendo una herramienta integrada
|
||||
(puede ser usada incluso "dentro" de Hibernate on-the-fly): <emphasis>SchemaExport</emphasis>
|
||||
también conocido como <literal>hbm2ddl</literal>.
|
||||
</para>
|
||||
|
||||
<sect1 id="toolsetguide-s1" revision="2">
|
||||
<title>Generación automática de esquemas</title>
|
||||
|
||||
<para>
|
||||
Una utilidad de Hibernate puede generar DDL desde tus ficheros de mapeo. El esquema generado incluye
|
||||
restricciones de integridad referencial (claves primarias y foráneas) para las tablas de entidades y
|
||||
colecciones. Las tablas y secuencias también son creadas para los generadores de identificadores mapeados.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<emphasis>Debes</emphasis> especificar un <literal>Dialecto</literal> SQL vía la propiedad
|
||||
<literal>hibernate.dialect</literal> al usar esta herramienta, ya que el DDL es altamente específico del
|
||||
vendedor.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
First, customize your mapping files to improve the generated schema.
|
||||
</para>
|
||||
|
||||
<sect2 id="toolsetguide-s1-2" revision="1">
|
||||
<title>Personalizando el esquema</title>
|
||||
|
||||
<para>
|
||||
Muchos elementos de mapeo de Hibernate definen un atributo opcional llamado <literal>length</literal>.
|
||||
Con este atributo puedes establecer el tamaño de una columna. (O, para tipos de datos
|
||||
numéricos/decimales, la precisión.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Algunas etiquetas también aceptan un atributo <literal>not-null</literal> (para generar una restricción
|
||||
<literal>NOT NULL</literal> en columnas de tablas) y y un atributo <literal>unique</literal> (para generar
|
||||
restricciones <literal>UNIQUE</literal> en columnas de tablas).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Algunas etiquetas aceptan un atributo <literal>index</literal> para especificar el nombre de un índice
|
||||
para esa columna. Se puede usar un atributo <literal>unique-key</literal> para agrupar columnas en una
|
||||
restricción de clave de una sola unidad. Actualmente, el valor especificado del atributo
|
||||
<literal>unique-key</literal> <emphasis>no</emphasis> es usado para nombrar la restricción, sólo para
|
||||
agrupar las columnas en el fichero de mapeo.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Ejemplos:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<property name="foo" type="string" length="64" not-null="true"/>
|
||||
|
||||
<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>
|
||||
|
||||
<element column="serial_number" type="long" not-null="true" unique="true"/>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Alternativamente, estos elementos aceptan tambíen un elemento hijo <literal><column></literal>.
|
||||
Esto es particularmente útil para tipos multicolumnas:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<property name="foo" type="string">
|
||||
<column name="foo" length="64" not-null="true" sql-type="text"/>
|
||||
</property>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[<property name="bar" type="my.customtypes.MultiColumnType"/>
|
||||
<column name="fee" not-null="true" index="bar_idx"/>
|
||||
<column name="fi" not-null="true" index="bar_idx"/>
|
||||
<column name="fo" not-null="true" index="bar_idx"/>
|
||||
</property>]]></programlisting>
|
||||
|
||||
<para>
|
||||
El atributo <literal>sql-type</literal> permite al usuario sobrescribir el mapeo por defecto de
|
||||
tipo Hibernate a tipo de datos SQL.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El atributo <literal>check</literal> te permite especificar una comprobación de restricción.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<property name="foo" type="integer">
|
||||
<column name="foo" check="foo > 10"/>
|
||||
</property>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[<class name="Foo" table="foos" check="bar < 100.0">
|
||||
...
|
||||
<property name="bar" type="float"/>
|
||||
</class>]]></programlisting>
|
||||
|
||||
|
||||
<table frame="topbot" id="schemattributes-summary" revision="2">
|
||||
<title>Resumen</title>
|
||||
<tgroup cols="3">
|
||||
<colspec colwidth="1*"/>
|
||||
<colspec colwidth="1*"/>
|
||||
<colspec colwidth="2.5*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Atributo</entry>
|
||||
<entry>Valores</entry>
|
||||
<entry>Interpretación</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>length</literal></entry>
|
||||
<entry>number</entry>
|
||||
<entry>largo de columna/precisión decimal</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>not-null</literal></entry>
|
||||
<entry><literal>true|false</literal></entry>
|
||||
<entry>especifica que la columna debe ser no nulable</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>unique</literal></entry>
|
||||
<entry><literal>true|false</literal></entry>
|
||||
<entry>especifica que la columna debe tener una restricción de unicidad</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>index</literal></entry>
|
||||
<entry><literal>index_name</literal></entry>
|
||||
<entry>especifica el nombre de un índice (multicolumna)</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>unique-key</literal></entry>
|
||||
<entry><literal>unique_key_name</literal></entry>
|
||||
<entry>especifica el nombre de una restricción de unicidad multicolumna</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>foreign-key</literal></entry>
|
||||
<entry><literal>foreign_key_name</literal></entry>
|
||||
<entry>
|
||||
especifica el nombre de la restricción de clave foránea generada por una
|
||||
asociación, úsalo en los elementos de mapeo <one-to-one>, <many-to-one>,
|
||||
<key>, y <many-to-many>. Nota que los lados
|
||||
<literal>inverse="true"</literal> no serán considerados por
|
||||
<literal>SchemaExport</literal>.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>sql-type</literal></entry>
|
||||
<entry><literal>column_type</literal></entry>
|
||||
<entry>
|
||||
sobrescribe el tipo de columna por defecto (sólo atributo del elemento
|
||||
<literal><column></literal>)
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>check</literal></entry>
|
||||
<entry>expresión SQL</entry>
|
||||
<entry>
|
||||
crea una restricción de comprobación SQL en columna o tabla
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
El elemento <literal><comment></literal> te permite especificar un comentario para el esquema
|
||||
generado.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Customer" table="CurCust">
|
||||
<comment>Current customers only</comment>
|
||||
...
|
||||
</class>]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[<property name="balance">
|
||||
<column name="bal">
|
||||
<comment>Balance in USD</comment>
|
||||
</column>
|
||||
</property>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Esto resulta en una sentencia <literal>comment on table</literal> o <literal>comment on column</literal>
|
||||
en el DDL generado (donde esté soportado).
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="toolsetguide-s1-3">
|
||||
<title>Ejecutando la herramienta</title>
|
||||
|
||||
<para>
|
||||
La herramienta <literal>SchemaExport</literal> escribe un guión DDL a la salida estándar y/o
|
||||
ejecuta las sentencias DDL.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>java -cp </literal><emphasis>classpaths_de_hibernate</emphasis>
|
||||
<literal>org.hibernate.tool.hbm2ddl.SchemaExport</literal> <emphasis>opciones ficheros_de_mapeo</emphasis>
|
||||
</para>
|
||||
|
||||
<table frame="topbot">
|
||||
<title>Opciones de Línea de Comandos de <literal>SchemaExport</literal></title>
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="1.5*"/>
|
||||
<colspec colwidth="2*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Opción</entry>
|
||||
<entry>Descripción</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>--quiet</literal></entry>
|
||||
<entry>no enviar a salida estándar el guión</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--drop</literal></entry>
|
||||
<entry>sólo desechar las tablas</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--text</literal></entry>
|
||||
<entry>no exportar a la base de datos</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--output=my_schema.ddl</literal></entry>
|
||||
<entry>enviar la salida del guión ddl a un fichero</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
|
||||
<entry>lee la configuración de Hibernate de un fichero XML</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||
<entry>lee las propiedades de base de datos de un fichero</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--format</literal></entry>
|
||||
<entry>formatea agradablemente el SQL generado en el guión</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--delimiter=x</literal></entry>
|
||||
<entry>establece un delimitador de fin de línea para el guión</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
Puedes incluso encajar <literal>SchemaExport</literal> en tu aplicación:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Configuration cfg = ....;
|
||||
new SchemaExport(cfg).create(false, true);]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="toolsetguide-s1-4">
|
||||
<title>Propiedades</title>
|
||||
|
||||
<para>
|
||||
Las propiedades de base de datos pueden especificarse
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>como propiedades de sistema con <literal>-D</literal><emphasis><property></emphasis></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>en <literal>hibernate.properties</literal></para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>en un fichero de propiedades mencionado con <literal>--properties</literal></para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Las propiedades necesarias son:
|
||||
</para>
|
||||
|
||||
<table frame="topbot">
|
||||
<title>Propiedades de Conexión de SchemaExport</title>
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="1.5*"/>
|
||||
<colspec colwidth="2*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Nombre de Propiedad</entry>
|
||||
<entry>Descripción</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>hibernate.connection.driver_class</literal></entry>
|
||||
<entry>clase del driver jdbc</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>hibernate.connection.url</literal></entry>
|
||||
<entry>url de jdbc</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>hibernate.connection.username</literal></entry>
|
||||
<entry>usuario de base de datos</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>hibernate.connection.password</literal></entry>
|
||||
<entry>contraseña de usuario</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>hibernate.dialect</literal></entry>
|
||||
<entry>dialecto</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="toolsetguide-s1-5">
|
||||
<title>Usando Ant</title>
|
||||
|
||||
<para>
|
||||
Puedes llamar a <literal>SchemaExport</literal> desde tu guión de construcción de Ant:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<target name="schemaexport">
|
||||
<taskdef name="schemaexport"
|
||||
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
|
||||
classpathref="class.path"/>
|
||||
|
||||
<schemaexport
|
||||
properties="hibernate.properties"
|
||||
quiet="no"
|
||||
text="no"
|
||||
drop="no"
|
||||
delimiter=";"
|
||||
output="schema-export.sql">
|
||||
<fileset dir="src">
|
||||
<include name="**/*.hbm.xml"/>
|
||||
</fileset>
|
||||
</schemaexport>
|
||||
</target>]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="toolsetguide-s1-6">
|
||||
<title>Actualizaciones incrementales de esquema</title>
|
||||
|
||||
<para>
|
||||
La herramienta <literal>SchemaUpdate</literal> actualizará un esquema existente con cambios
|
||||
"incrementales". Nota que <literal>SchemaUpdate</literal> depende fuertemente de la API de metadatos
|
||||
de JDBC, de modo que no funcionará con todos los drivers JDBC.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>java -cp </literal><emphasis>classpaths_de_hibernate</emphasis>
|
||||
<literal>org.hibernate.tool.hbm2ddl.SchemaUpdate</literal> <emphasis>opciones ficheros_de_mapeo</emphasis>
|
||||
</para>
|
||||
|
||||
<table frame="topbot">
|
||||
<title>Opciones de Línea de Comandos de <literal>SchemaUpdate</literal></title>
|
||||
<tgroup cols="2">
|
||||
<colspec colwidth="1.5*"/>
|
||||
<colspec colwidth="2*"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Opción</entry>
|
||||
<entry>Descripción</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>--quiet</literal></entry>
|
||||
<entry>no enviar a salida estándar el guión</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||
<entry>lee las propiedades de base de datos de un fichero</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>
|
||||
Puedes encajar <literal>SchemaUpdate</literal> en tu aplicación:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Configuration cfg = ....;
|
||||
new SchemaUpdate(cfg).execute(false);]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="toolsetguide-s1-7">
|
||||
<title>Usando Ant para actualizaciones incrementales de esquema</title>
|
||||
|
||||
<para>
|
||||
Puedes llamar a <literal>SchemaUpdate</literal> desde el guión de Ant:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<target name="schemaupdate">
|
||||
<taskdef name="schemaupdate"
|
||||
classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask"
|
||||
classpathref="class.path"/>
|
||||
|
||||
<schemaupdate
|
||||
properties="hibernate.properties"
|
||||
quiet="no">
|
||||
<fileset dir="src">
|
||||
<include name="**/*.hbm.xml"/>
|
||||
</fileset>
|
||||
</schemaupdate>
|
||||
</target>]]></programlisting>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
@ -1,925 +0,0 @@
|
|||
<chapter id="transactions" revision="1">
|
||||
<title>Transacciones y Concurrencia</title>
|
||||
|
||||
<para>
|
||||
El punto más importante sobre Hibernate y el control de concurrencia es que muy fácil
|
||||
de comprender. Hibernate usa directamente conexiones JDBC y recursos JTA sin agregar
|
||||
ningún comportamiento de bloqueo adicional. Recomendamos altamente que gastes algo de
|
||||
tiempo con la especificación de JDBC, ANSI, y el aislamiento de transacciones de tu sistema
|
||||
de gestión de base de datos. Hibernate sólo añade versionado automático pero no bloquea
|
||||
objetos en memoria ni cambia el nivel de aislamiento de tus transacciones de base de datos.
|
||||
Básicamente, usa Hibernate como usarías JDBC directo (o JTA/CMT) con tus recursos de base de
|
||||
datos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Sin embargo, además del versionado automático, Hibernate ofrece una API (menor) para
|
||||
bloqueo pesimista de filas, usando la sintáxis <literal>SELECT FOR UPDATE</literal>.
|
||||
Esta API se discute más adelante en este capítulo:
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Comenzamos la discusión del control de concurrencia en Hibernate con la granularidad
|
||||
de <literal>Configuration</literal>, <literal>SessionFactory</literal>, y
|
||||
<literal>Session</literal>, así como la base de datos y las transacciones de aplicación
|
||||
largas.
|
||||
</para>
|
||||
|
||||
<sect1 id="transactions-basics">
|
||||
<title>Ámbitos de sesión y de transacción</title>
|
||||
|
||||
<para>
|
||||
Una <literal>SessionFactory</literal> es un objeto seguro entre hebras caro-de-crear
|
||||
pensado para ser compartido por todas las hebras de la aplicación. Es creado una sola vez,
|
||||
usualmente en el arranque de la aplicación, a partir de una instancia de <literal>Configuration</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una <literal>Session</literal> es un objeto barato, inseguro entre hebras que debe
|
||||
ser usado una sola vez, para un solo proceso de negocio, una sola unidad de trabajo,
|
||||
y luego descartado. Una <literal>Session</literal> no obtendrá una <literal>Connection</literal>
|
||||
JDBC (o un <literal>Datasource</literal>) a menos que sea necesario, de modo que puedas
|
||||
abrir y cerrar seguramente una <literal>Session</literal> incluso si no estás seguro
|
||||
que se necesitará acceso a los datos para servir una petición en particular. (Esto se
|
||||
vuelve importante en cuanto estés implementando alguno de los siguientes patrones usando
|
||||
intercepción de peticiones).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Para completar este cuadro tienes que pensar también en las transacciones de base de
|
||||
datos. Una transacción de base de datos tiene que ser tan corta como sea posible, para
|
||||
reducir la contención de bloqueos en la base de datos. Las transacciones largas de base de
|
||||
datos prevendrán a tu aplicación de escalar a una carga altamente concurrente.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
¿Qué es el ámbito de una unidad de trabajo? ¿Puede una sola <literal>Session</literal> de Hibernate
|
||||
extenderse a través de varias transacciones de base de datos o es ésta una relación uno-a-uno
|
||||
de ámbitos? ¿Cuándo debes abrir y cerrar una <literal>Session</literal> y cómo demarcas los
|
||||
límites de la transacción de base de datos?
|
||||
</para>
|
||||
|
||||
<sect2 id="transactions-basics-uow">
|
||||
<title>Unidad de trabajo</title>
|
||||
|
||||
<para>
|
||||
Primero, no uses el antipatrón <emphasis>sesión-por-operación</emphasis>, esto es,
|
||||
¡no abras y cierres una <literal>Session</literal> para cada simple llamada a la base
|
||||
de datos en una sola hebra! Por supuesto, lo mismo es verdad para transacciones de base de
|
||||
datos. Las llamadas a base de datos en una aplicación se hacen usando una secuencia
|
||||
prevista, que están agrupadas dentro de unidades de trabajo atómicas. (Nota que esto
|
||||
también significa que el auto-commit después de cada una de las sentencias SQL es inútil
|
||||
en una aplicación, este modo está pensado para trabajo ad-hoc de consola SQL.
|
||||
Hibernate deshabilita, o espera que el servidor de aplicaciones lo haga, el modo
|
||||
auto-commit inmediatamente.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El patrón más común en una aplicación mutiusuario cliente/servidor es
|
||||
<emphasis>sesión-por-petición</emphasis>. En este modelo, una petición del cliente
|
||||
es enviada al servidor (en donde se ejecuta la capa de persistencia de Hibernate),
|
||||
se abre una nueva <literal>Session</literal> de Hibernate, y todas las operaciones
|
||||
de base de datos se ejecutan en esta unidad de trabajo. Una vez completado el trabajo
|
||||
(y se ha preparado la respuesta para el cliente) la sesión es limpiada y cerrada.
|
||||
Podrías usar una sola transacción de base de datos para servir a petición del cliente,
|
||||
comenzándola y comprometiéndola cuando abres y cierras la <literal>Session</literal>.
|
||||
La relación entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas
|
||||
aplicaciones.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
El desafío yace en la implementación: no sólo tienen que comenzarse y terminarse correctamente
|
||||
la <literal>Session</literal> y la transacción, sino que además tienen que estar accesibles
|
||||
para las operaciones de acceso a datos. La demarcación de una unidad de trabajo se implementa
|
||||
idealmente usando un interceptor que se ejecuta cuando una petición llama al servidor y anter que
|
||||
la respuesta sea enviada (es decir, un <literal>ServletFilter</literal>). Recomendamos ligar la
|
||||
<literal>Session</literal> a la hebra que atiende la petición, usando una variable
|
||||
<literal>ThreadLocal</literal>. Esto permite un fácil acceso (como acceder a una variable static)
|
||||
en tódo el código que se ejecuta en esta hebra. Dependiendo del mecanismo de demarcación de
|
||||
transacciones de base de datos que elijas, podrías mantener también el contexto de la transacción
|
||||
en una variable <literal>ThreadLocal</literal>. Los patrones de implementación para esto son
|
||||
conocidos como <emphasis>Sesión Local de Hebra (ThreadLocal Session)</emphasis> y
|
||||
<emphasis>Sesión Abierta en Vista (Open Session in View)</emphasis>. Puedes extender fácilmente
|
||||
la clase de ayuda <literal>HibernateUtil</literal> mostrada anteriormente para encontrar
|
||||
una forma de implementar un interceptor e instalarlo en tu entorno. Ver el sitio web de Hibernate
|
||||
para consejos y ejemplos.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-basics-apptx">
|
||||
<title>Transacciones de aplicación</title>
|
||||
|
||||
<para>
|
||||
El patrón sesión-por-petición no es el único concepto útil que puedes usar para diseñar unidades
|
||||
de trabajo. Muchos procesos de negocio requiere una serie completa de interacciones con el
|
||||
usuario intercaladas con accesos a base de datos. En aplicaciones web y de empresa no es aceptable
|
||||
que una transacción de base de datos se extienda a través de la interacción de un usuario.
|
||||
Considera el siguiente ejemplo:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Se abre la primera pantalla de un diálogo, los datos vistos por el usuario han sido
|
||||
cargados en una <literal>Session</literal> y transacción de base de datos particular.
|
||||
El usuario es libre de modificar los objetos.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
El usuario hace click en "Salvar" después de 5 minutos y espera que sus modificaciones
|
||||
sean hechas persistentes. También espera que él sea la única persona editando esta
|
||||
información y que no puede ocurrir ninguna modificación en conflicto.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Llamamos a esto unidad de trabajo, desde el punto de vista del usuario, una larga
|
||||
<emphasis>transacción de aplicación</emphasis> ejecutándose. Hay muchas formas en
|
||||
que puedes implementar esto en tu aplicación.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una primera implementación ingenua podría mantener abierta la <literal>Session</literal>
|
||||
y la transacción de base de datos durante el tiempo de pensar del usuario, con bloqueos
|
||||
tomados en la base de datos para prevenir la modificación concurrente, y para garantizar
|
||||
aislamiento y atomicidad. Esto es, por supuesto, un antipatrón, ya que la contención de
|
||||
bloqueo no permitiría a la aplicación escalar con el número de usuarios concurrentes.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Claramente, tenemos que usar muchas transacciones de base de datos para implementar la transacción
|
||||
de aplicación. En este caso, mantener el aislamiento de los procesos de negocio se vuelve una
|
||||
responsabilidad parcial de la capa de aplicación. Una sola transacción de aplicación usualmente
|
||||
abarca varias transacciones de base de datos. Será atómica si sólo una de estas transacciones de
|
||||
base de datos (la última) almacena los datos actualizados, todas las otras simplemente leen datos
|
||||
(por ejemplo, en un diálogo estilo-asistente abarcando muchos ciclos petición/respuesta).
|
||||
Esto es más fácil de implementar de lo que suena, especialmente si usas las funcionalidades de
|
||||
Hibernate:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis>Versionado Automático</emphasis> - Hibernate puede llevar un control automático de
|
||||
concurrencia optimista por ti, puede detectar automáticamente si una modificación concurrente
|
||||
ha ocurrido durante el tiempo de pensar del usuario.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis>Objetos Separados</emphasis> - Si decides usar el ya discutido patrón
|
||||
de <emphasis>sesión-por-petición</emphasis>, todas las instancias cargadas estarán
|
||||
en estado separado durante el tiempo de pensar del usuario. Hibernate te permite
|
||||
volver a unir los objetos y hacer persistentes las modificaciones. El patrón se
|
||||
llama <emphasis>sesión-por-petición-con-objetos-separados</emphasis>. Se usa
|
||||
versionado automático para aislar las modificaciones concurrentes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis>Sesión Larga</emphasis> - La <literal>Session</literal> de Hibernate puede ser
|
||||
desconectada de la conexión JDBC subyacente después que se haya sido comprometida la
|
||||
transacción de base de datos, y reconectada cuando ocurra una nueva petición del cliente.
|
||||
Este patrón es conocido como <emphasis>sesión-por-transacción-de-aplicación</emphasis>
|
||||
y hace la re-unión innecesaria. Para aislar las modificaciones concurrentes se usa el
|
||||
versionado automático.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Tanto <emphasis>sesión-por-petición-con-objetos-separados</emphasis> como
|
||||
<emphasis>sesión-por-transacción-de-aplicación</emphasis>, ambas tienen
|
||||
ventajas y desventajas, las discutimos más adelante en este capítulo en el contexto
|
||||
del control optimista de concurrencia.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-basics-identity">
|
||||
<title>Considerando la identidad del objeto</title>
|
||||
|
||||
<para>
|
||||
Una aplicación puede acceder concurrentemente a el mismo estado persistente en dos
|
||||
<literal>Session</literal>s diferentes. Sin embargo, una instancia de una clase
|
||||
persistente nunca se comparte entre dos instancias de <literal>Session</literal>.
|
||||
Por lo tanto existen dos nociones diferentes de identidad:
|
||||
</para>
|
||||
|
||||
<variablelist spacing="compact">
|
||||
<varlistentry>
|
||||
<term>Identidad de Base de Datos</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>foo.getId().equals( bar.getId() )</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Identidad JVM</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>foo==bar</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>
|
||||
Entonces para objetos unidos a una <literal>Session</literal> <emphasis>en particular</emphasis>
|
||||
(es decir en el ámbito de una <literal>Session</literal>) las dos nociones son equivalentes, y
|
||||
la identidad JVM para la identidad de base de datos está garantizada por Hibernate. Sin embargo,
|
||||
mientras la aplicación acceda concurrentemente al "mismo" (identidad persistente) objeto de negocio
|
||||
en dos sesiones diferentes, las dos instancias serán realmente "diferentes" (identidad JVM).
|
||||
Los conflictos se resuelven (con versionado automático) en tiempo de limpieza (flush) usando un
|
||||
enfoque optimista.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Este enfoque deja que Hibernate y la base de datos se preocupen sobre la concurrencia. Además
|
||||
provee la mejor escalabilidad, ya que garantizando la identidad un unidades de trabajo monohebra
|
||||
no se necesitan bloqueos caros u otros medios de sincronización. La aplicación nunca necesita
|
||||
sincronizar sobre ningún objeto de negocio, siempre que se apegue a una sola hebra por
|
||||
<literal>Session</literal>. Dentro de una <literal>Session</literal> la aplicación puede usar
|
||||
con seguridad <literal>==</literal> para comparar objetos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Sin embargo, una aplicación que usa <literal>==</literal> fuera de una <literal>Session</literal>,
|
||||
podría ver resultados inesperados. Esto podría ocurrir incluso en sitios algo inesperados,
|
||||
por ejemplo, si pones dos instancias separadas dentro del mismo <literal>Set</literal>.
|
||||
Ambas podrían tener la misma identidad de base de datos (es decir, representar la misma fila),
|
||||
pero la identidad JVM, por definición, no está garantizada para las instancias en estado separado.
|
||||
El desarrollador tiene que sobrescribir los métodos <literal>equals()</literal> y
|
||||
<literal>hashCode()</literal> en las clases persistentes e implementar su propia noción de igualdad
|
||||
de objetos. Hay una advertencia: Nunca uses el identificador de base de datos para implementar
|
||||
la igualdad, usa una clave de negocio, una combinación de atributos únicos, usualmente inmutables.
|
||||
El identificador de base de datos cambiará si un objeto transitorio es hecho persistente.
|
||||
Si la instancia transitoria (usualmente junta a instancias separadas) es mantenida en un
|
||||
<literal>Set</literal>, cambiar el código hash rompe el contrato del <literal>Set</literal>.
|
||||
Los atributos para las claves de negocio no tienen que ser tan estables como las claves primarias
|
||||
de base de datos, sólo tienes que garantizar estabilidad en tanto los objetos estén en el mismo
|
||||
<literal>Set</literal>. Mira el sitio web de Hibernate para una discusión más cuidadosa de este
|
||||
tema. Nota también que éste no es un tema de Hibernate, sino simplemente cómo la identidad y la igualdad
|
||||
de los objetos Java tiene que ser implementada.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-basics-issues">
|
||||
<title>Temas comunes</title>
|
||||
|
||||
<para>
|
||||
Nunca uses los antipatrones <emphasis>sesión-por-sesión-de-usuario</emphasis> o
|
||||
<emphasis>sesión-por-aplicación</emphasis> (por supuesto, hay raras excepciones a esta
|
||||
regla). Nota que algunis de los siguientes temas podrían también aparecer con los patrones
|
||||
recomendados. Asegúrate que entiendes las implicaciones antes de tomar una decisión de
|
||||
diseño:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Una <literal>Session</literal> no es segura entre hebras. Las cosas que se suponen
|
||||
que funcionan concurrentemente, como peticiones HTTP, beans de sesión, o workers de
|
||||
Swing, provocarán condiciones de competencia si una instancia de <literal>Session</literal>
|
||||
fuese compartida. Si guardas tu <literal>Session</literal> de Hibernate en tu
|
||||
<literal>HttpSession</literal> (discutido más adelante), debes considerar sincronizar
|
||||
el acceso a tu sesión HTTP. De otro modo, un usuario que hace click lo suficientemente
|
||||
rápido puede llegar a usar la misma <literal>Session</literal> en dos hebras ejecutándose
|
||||
concurrentemente.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Una excepción lanzada por Hibernate significa que tienes que deshacer (rollback) tu
|
||||
transacción de base de datos y cerrar la <literal>Session</literal> inmediatamente
|
||||
(discutido en más detalle luego). Si tu <literal>Session</literal> está ligada a la
|
||||
aplicación, tienes que parar la aplicación. Deshacer (rollback) la transacción de base
|
||||
de datos no pone a tus objetos de vuelta al estado en que estaban al comienzo de la
|
||||
transacción. Esto significa que el estado de la base de datos y los objetos de negocio
|
||||
quedan fuera de sincronía. Usualmente esto no es un problema, pues las excepciones no
|
||||
son recuperables y tienes que volver a comenzar después del rollback de todos modos.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
La <literal>Session</literal> pone en caché todo objeto que esté en estado persistente
|
||||
(vigilado y chequeado por estado sucio por Hibernate). Esto significa que crece sin
|
||||
fin hasta que obtienes una OutOfMemoryException, si la mantienes abierta por un largo
|
||||
tiempo o simplemente cargas demasiados datos. Una solución para esto es llamar a
|
||||
<literal>clear()</literal> y <literal>evict()</literal> para gestionar el caché de la
|
||||
<literal>Session</literal>, pero probalemente debas considerar un procedimiento almacenado
|
||||
si necesitas operaciones de datos masivas. Se muestran algunas soluciones en
|
||||
<xref linkend="batch"/>. Mantener una <literal>Session</literal> abierta por la duración
|
||||
de una sesión de usuario significa también una alta probabilidad de datos añejos.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="transactions-demarcation">
|
||||
<title>Demarcación de la transacción de base de datos</title>
|
||||
|
||||
<para>
|
||||
Los límites de las transacciones de base de datos (o sistema) son siempre necesarios. Ninguna comunicación
|
||||
con la base de datos puede darse fuera de una transacción de base de datos (esto parece confundir muchos
|
||||
desarrolladores acostumbrados al modo auto-commit). Siempre usa límites de transacción claros, incluso
|
||||
para las operaciones de sólo lectura. Dependiendo del nivel de aislamiento y las capacidades de base de
|
||||
datos, esto podría o no ser requerido, pero no hay un merma si siempre demarcas explícitamente
|
||||
las transacciones.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una aplicación Hibernate puede ejecutarse en entornos no manejados (es decir, como independiente,
|
||||
Web simple, o aplicaciones Swing) y entornos manejados J2EE. En un entorno no manejado, Hibernate es
|
||||
usualmente responsable de su propio pool de conexiones de base de datos. El desarrollador de aplicaciones
|
||||
tiene que establecer manualmente los límites de transacción, en otras palabras, hacer begin, commit, o
|
||||
rollback las transacciones de base de datos por sí mismo. Un entorno manejado usualmente provee transacciones
|
||||
gestionadas por contenedor, con el ensamble de transacción definido declarativamente en descriptores de
|
||||
despliegue de beans de sesión EJB, por ejemplo. La demarcación programática de transacciones no es más
|
||||
necesario, incluso limpiar (flush) la <literal>Session</literal> es hecho automáticamente.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Sin embargo, frecuentemente es deseable mantener portable tu capa de persistencia. Hibernate ofrece
|
||||
una API de envoltura llamada <literal>Transaction</literal> que se traduce al sistema de transacciones
|
||||
nativo de tu entorno de despliegue. Esta API es realmente opcional, pero recomendamos fuertemente su uso
|
||||
salvo que estés en un bean de sesión CMT.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Usualmente, finalizar una <literal>Session</literal> implica cuatro fases distintas:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
limpiar (flush) la sesión
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
comprometer la transacción
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
cerrar la sesión
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
manejar excepciones
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Limpiar la sesión ha sido discutido anteriormente, tendremos ahora una mirada más de cerca
|
||||
a la demarcación de transacciones y manejo de excepciones en sendos entornos manejado y no manejados.
|
||||
</para>
|
||||
|
||||
|
||||
<sect2 id="transactions-demarcation-nonmanaged">
|
||||
<title>Entorno no manejado</title>
|
||||
|
||||
<para>
|
||||
Si una capa de persistencia Hibernate se ejecuta en un entorno no manejado, las conexiones
|
||||
de base de datos son manejadas usualmente por el mecanismo de pooling de Hibernate. El idioma
|
||||
manejo de sesión/transacción se ve así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[//Non-managed environment idiom
|
||||
Session sess = factory.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = sess.beginTransaction();
|
||||
|
||||
// do some work
|
||||
...
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (tx != null) tx.rollback();
|
||||
throw e; // or display error message
|
||||
}
|
||||
finally {
|
||||
sess.close();
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
No tienes que limpiar con <literal>flush()</literal> la <literal>Session</literal> explícitamente -
|
||||
la llamada a <literal>commit()</literal> automáticamente dispara la sincronización.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Una llamada a <literal>close()</literal> marca el fin de una sesión. La principal implicación
|
||||
de <literal>close()</literal> es que la conexión JDBC será abandonada por la sesión.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Este código Java es portable y se ejecuta tanto en entornos no manejados como en entornos JTA.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Muy probablemente nunca veas este idioma en código de negocio en una aplicación normal;
|
||||
las excepciones fatales (sistema) deben siempre ser capturadas en la "cima". En otras palabras,
|
||||
el código que ejecuta las llamadas de Hibernate (en la capa de persistencia) y el código que
|
||||
maneja <literal>RuntimeException</literal> (y usualmente sólo puede limpiar y salir) están en
|
||||
capas diferentes. Esto puede ser un desafío de diseñarlo tú mismo y debes usar los servicios
|
||||
de contenedor J2EE/EJB en cuanto estuviesen disponibles. El manejo de excepciones se dicute
|
||||
más adelante en este capítulo.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nota que debes seleccionar <literal>org.hibernate.transaction.JDBCTransactionFactory</literal>
|
||||
(que es el por defecto).
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-demarcation-jta">
|
||||
<title>Usando JTA</title>
|
||||
|
||||
<para>
|
||||
Si tu capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás
|
||||
de beans de sesión EJB), cada conexión de datasource obtenida por Hibernate será parte
|
||||
automáticamente de la transacción JTA global. Hibernate ofrece dos estrategias para esta
|
||||
integración.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si usas transacciones gestionadas-por-bean (BMT) Hibernate le dirá al servidor de aplicaciones
|
||||
que comience y finalice una transacción BMT si usas la API de <literal>Transaction</literal>.
|
||||
De modo que, el código de gestión de la transacción es idéntico al de un entorno no manejado.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[// BMT idiom
|
||||
Session sess = factory.openSession();
|
||||
Transaction tx = null;
|
||||
try {
|
||||
tx = sess.beginTransaction();
|
||||
|
||||
// do some work
|
||||
...
|
||||
|
||||
tx.commit();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (tx != null) tx.rollback();
|
||||
throw e; // or display error message
|
||||
}
|
||||
finally {
|
||||
sess.close();
|
||||
}]]></programlisting>
|
||||
|
||||
<para>
|
||||
Con CMT, la demarcación de la transacción se hace en descriptores de despliegue de beans de sesión,
|
||||
no programáticamente. Si no quieres limpiar (flush) y cerrar manualmente la <literal>Session</literal>
|
||||
por ti mismo, solamente establece <literal>hibernate.transaction.flush_before_completion</literal> a
|
||||
<literal>true</literal>, <literal>hibernate.connection.release_mode</literal> a
|
||||
<literal>after_statement</literal> o <literal>auto</literal> y
|
||||
<literal>hibernate.transaction.auto_close_session</literal> a <literal>true</literal>. Hibernate
|
||||
limpiará y cerrará entonces automáticamente la <literal>Session</literal> para ti. Lo único que resta
|
||||
es deshacer (rollback) la transacción cuando ocurra una excepción. Afortunadamente, en un bean CMT,
|
||||
incluso esto ocurre automáticamente, ya que una <literal>RuntimeException</literal> no manejada
|
||||
disparada por un método de un bean de sesión le dice al contenedor que ponga a deshacer la transacción
|
||||
global. <emphasis>Esto significa que, en CMT, no necesitas usar en absoluto la API de
|
||||
<literal>Transaction</literal> de Hibernate.</emphasis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nota que debes elegir <literal>org.hibernate.transaction.JTATransactionFactory</literal> en un
|
||||
bean de sesión BMT, y <literal>org.hibernate.transaction.CMTTransactionFactory</literal> en un
|
||||
bean de sesión CMT, cuando configures la fábrica de transacciones de Hibernate. Recuerda además
|
||||
establecer <literal>org.hibernate.transaction.manager_lookup_class</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si trabajas en un entorno CMT, y usas limpieza (flushing) y cierre automáticos de la sesión,
|
||||
podrías querer también usar la misma sesión en diferentes partes de tu código. Típicamente,
|
||||
en un entorno no manejado, usarías una variable <literal>ThreadLocal</literal> para tener la sesión,
|
||||
pero una sola petición de EJB puede ejecutarse en diferentes hebras (por ejemplo, un bean de sesión
|
||||
llamando a otro bean de sesión). Si no quieres molestarte en pasar tu <literal>Session</literal>
|
||||
por alrededor, la <literal>SessionFactory</literal> provee el método
|
||||
<literal>getCurrentSession()</literal>, que devuelve una sesión que está pegada al contexto de
|
||||
transacción JTA. ¡Esta es la forma más fácil de integrar Hibernate en una aplicación!
|
||||
La sesión "actual" siempre tiene habilitados limpieza, cierre y liberación de conexión automáticos
|
||||
(sin importar la configuración de las propiedades anteriores). Nuestra idioma de gestión de
|
||||
sesión/transacción se reduce a:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[// CMT idiom
|
||||
Session sess = factory.getCurrentSession();
|
||||
|
||||
// do some work
|
||||
...
|
||||
|
||||
]]></programlisting>
|
||||
|
||||
<para>
|
||||
En otras palabras, todo lo que tienes que hacer en un entorno manejado, es llamar a
|
||||
<literal>SessionFactory.getCurrentSession()</literal>, hacer tu trabajo de acceso a datos,
|
||||
y dejar el resto al contenedor. Los límites de transacción se establecen declarativamente
|
||||
en los descriptores de despliegue de tu bean de sesión. El ciclo de vida de la sesión es
|
||||
manejado completamente por Hibernate.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Existe una advertencia al uso del modo de liberación de conexión <literal>after_statement</literal>.
|
||||
Debido a una limitación tonta de la especificación de JTA, no es posible para Hibernate
|
||||
limpiar automáticamente ningún <literal>ScrollableResults</literal> no cerrado ni
|
||||
instancias de <literal>Iterator</literal> devueltas por <literal>scroll()</literal> o
|
||||
<literal>iterate()</literal>. <emphasis>Debes</emphasis> liberar el cursor de base de datos
|
||||
subyacente llamando a <literal>ScrollableResults.close()</literal> o
|
||||
<literal>Hibernate.close(Iterator)</literal> explícitamente desde un bloque <literal>finally</literal>.
|
||||
(Por supuesto, la mayoría de las aplicaciones pueden evitarlo fácilmente no usando en absoluto ningún
|
||||
<literal>scroll()</literal> o <literal>iterate()</literal> desde el código CMT.)
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-demarcation-exceptions">
|
||||
<title>Manejo de excepciones</title>
|
||||
|
||||
<para>
|
||||
Si la <literal>Session</literal> lanza una excepción (incluyendo cualquier
|
||||
<literal>SQLException</literal>), debes inmediatamente deshacer (rollback) la
|
||||
transacción de base de datos, llamar a <literal>Session.close()</literal> y
|
||||
descartar la instancia de <literal>Session</literal>. Ciertos métodos de
|
||||
<literal>Session</literal> <emphasis>no</emphasis> dejarán la sesión en un
|
||||
estado consistente. Ninguna excepción lanzada por Hibernate puede ser tratada
|
||||
como recuperable. Asegúrate que la <literal>Session</literal> sea cerrada llamando
|
||||
a <literal>close()</literal> en un bloque <literal>finally</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La <literal>HibernateException</literal>, que envuelve la mayoría de los errores que
|
||||
pueden ocurrir en la capa de persistencia de Hibernate, en una excepción no chequeada
|
||||
(no lo era en versiones anteriores de Hibernate). En nuestra opinión, no debemos forzar
|
||||
al desarrollador de aplicaciones a capturar una excepción irrecuperable en una capa baja.
|
||||
En la mayoría de los sistemas, las excepciones no chequeadas y fatales son manejadas
|
||||
en uno de los primeros cuadros de la pila de llamadas a métodos (es decir, en las capas
|
||||
más altas) y se presenta un mensaje de error al usuario de la aplicación (o se toma alguna
|
||||
otra acción apropiada). Nota que Hibernate podría también lanzar otras excepciones no chequeadas
|
||||
que no sean una <literal>HibernateException</literal>. Una vez más, no son recuperables y debe
|
||||
tomarse una acción apropiada.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hibernate envuelve <literal>SQLException</literal>s lanzadas mientras se interactúa con la base
|
||||
de datos en una <literal>JDBCException</literal>. De hecho, Hibernate intentará convertir la excepción
|
||||
en una subclase de <literal>JDBCException</literal> más significativa. La <literal>SQLException</literal>
|
||||
está siempre disponible vía <literal>JDBCException.getCause()</literal>. Hibernate convierte la
|
||||
<literal>SQLException</literal> en una subclase de <literal>JDBCException</literal> apropiada usando
|
||||
el <literal>SQLExceptionConverter</literal> adjunto a la <literal>SessionFactory</literal>. Por defecto,
|
||||
el <literal>SQLExceptionConverter</literal> está definido para el dialecto configurado; sin embargo,
|
||||
es también posible enchufar una implementación personalizada (ver los javadocs de la clase
|
||||
<literal>SQLExceptionConverterFactory</literal> para los detalles). Los subtipos estándar de
|
||||
<literal>JDBCException</literal> son:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>JDBCConnectionException</literal> - indica un error con la comunicación JDBC subyacente.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>SQLGrammarException</literal> - indica un problema de gramática o sintáxis con el
|
||||
SQL publicado.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>ConstraintViolationException</literal> - indica alguna forma de violación de restricción
|
||||
de integridad.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockAcquisitionException</literal> - indica un error adquiriendo un nivel de bloqueo
|
||||
necesario para realizar una operación solicitada.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>GenericJDBCException</literal> - una excepción genérica que no cayó en ninguna de las
|
||||
otras categorías.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="transactions-optimistic">
|
||||
<title>Control optimista de concurrencia</title>
|
||||
|
||||
<para>
|
||||
El único enfoque que es consistente con alta concurrencia y alta escalabilidad es el control
|
||||
optimista de concurrencia con versionamiento. El chuequeo de versión usa números de versión,
|
||||
o timestamps, para detectar actualizaciones en conflicto (y para prevenir actualizaciones perdidas).
|
||||
Hibernate provee para tres enfoques posibles de escribir código de aplicación que use concurrencia
|
||||
optimista. Los casos de uso que hemos mostrado están en el contexto de transacciones de aplicación
|
||||
largas pero el chequeo de versiones tiene además el beneficio de prevenir actualizaciones perdidas
|
||||
en transacciones de base de datos solas.
|
||||
</para>
|
||||
|
||||
<sect2 id="transactions-optimistic-manual">
|
||||
<title>Chequeo de versiones de aplicación</title>
|
||||
|
||||
<para>
|
||||
En una implementación sin mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una
|
||||
nueva <literal>Session</literal> y el desarrollador es responsable de recargar todas las intancias
|
||||
persistentes desde la base de datos antes de manipularlas. Este enfoque fuerza a la aplicación a
|
||||
realizar su propio chequeo de versiones para asegurar el aislamiento de transacciones de base de datos.
|
||||
Es el enfoque más similar a los EJBs de entidad.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
||||
session = factory.openSession();
|
||||
Transaction t = session.beginTransaction();
|
||||
int oldVersion = foo.getVersion();
|
||||
session.load( foo, foo.getKey() ); // load the current state
|
||||
if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException();
|
||||
foo.setProperty("bar");
|
||||
t.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
La propiedad <literal>version</literal> se mapea usando <literal><version></literal>,
|
||||
e Hibernate la incrementará automáticamente durante la limpieza si la entidad está sucia.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Por supuesto, si estás operando un entorno de baja-concurrencia-de-datos y no requieres
|
||||
chequeo de versiones, puedes usar este enfoque y simplemente saltar el chequeo de versiones.
|
||||
En ese caso, <emphasis>el último compromiso (commit) gana</emphasis> será la estrategia por
|
||||
defecto para tus transacciones de aplicación largas. Ten en mente que esto podría confundir
|
||||
a los usuarios de la aplicación, pues podrían experimentar actualizaciones perdidas sin
|
||||
mensajes de error ni chance de fusionar los cambios conflictivos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Claramente, el chequeo manual de versiones es factible solamente en circunstancias muy triviales,
|
||||
y no es práctico para la mayoría de aplicaciones. Frecuentemente, no sólo intancias solas, sino grafos
|
||||
completos de objetos modificados tienen que ser chequeados. Hibernate ofrece chequeo de versiones
|
||||
automático con el paradigma de diseño de <literal>Session</literal> larga o de instancias separadas.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-optimistic-longsession">
|
||||
<title>Sesión larga y versionado automático</title>
|
||||
|
||||
<para>
|
||||
Una sola instancia de <literal>Session</literal> y sus instancias persistentes
|
||||
son usadas para toda la transacción de aplicación. Hibernate chequea las versiones
|
||||
de instancia en el momento de limpieza (flush), lanzando una excepción si se detecta
|
||||
una modificación concurrente. Concierne al desarrollador capturar y manejar esta excepción
|
||||
(las opciones comunes son la oportunidad del usuario de fusionar los cambios, o recomenzar el
|
||||
proceso de negocio sin datos añejos).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La <literal>Session</literal> se desconecta de cualquier conexión JDBC subyacente
|
||||
al esperar por una interacción del usuario. Este enfoque es el más eficiente en términos
|
||||
de acceso a base de datos. La aplicación no necesita tratar por sí misma con el chequeo de
|
||||
versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cada
|
||||
transacción de base de datos.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session
|
||||
session.reconnect(); // Obtain a new JDBC connection
|
||||
Transaction t = session.beginTransaction();
|
||||
foo.setProperty("bar");
|
||||
t.commit(); // End database transaction, flushing the change and checking the version
|
||||
session.disconnect(); // Return JDBC connection ]]></programlisting>
|
||||
|
||||
<para>
|
||||
El objeto <literal>foo</literal> todavía conoce en qué <literal>Session</literal> fue cargado.
|
||||
<literal>Session.reconnect()</literal> obtiene una nueva conexión (o puedes proveer una) y
|
||||
reasume la sesión. El método <literal>Session.disconnect()</literal> desconectará la sesión
|
||||
de la conexión JDBC y la devolverá la conexión al pool (a menos que hayas provisto la conexión).
|
||||
Después de la reconexión, para forzar un chequeo de versión en datos que no estés actualizando,
|
||||
puedes llamar a <literal>Session.lock()</literal> con <literal>LockMode.READ</literal> sobre
|
||||
cualquier objeto que pudiese haber sido actualizado por otra transacción. No necesitas bloquear
|
||||
ningún dato que <emphasis>sí estés</emphasis> actualizando.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si las llamadas explícitas a <literal>disconnect()</literal> y <literal>reconnect()</literal>
|
||||
son muy onerosas, puedes usar en cambio <literal>hibernate.connection.release_mode</literal>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Este patrón es problemático si la <literal>Session</literal> es demasiado grande para ser almacenada
|
||||
durante el tiempo de pensar del usuario, por ejemplo, una <literal>HttpSession</literal> debe
|
||||
mantenerse tan pequeña como sea posible. Ya que la <literal>Session</literal> es también el caché
|
||||
(obligatorio) de primer nivel y contiene todos los objetos cargados, podemos probablemente cargar
|
||||
esta estrategia sólo para unos pocos ciclos petición/respuesta. Esto está de hecho recomendado, ya que
|
||||
la <literal>Session</literal> tendrá pronto también datos añejos.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nota también que debes mantener la <literal>Session</literal> desconectada próxima a la capa
|
||||
de persistencia. En otras palabras, usa una sesión de EJB con estado para tener la
|
||||
<literal>Session</literal> y no transferirla a la capa web para almacenarla en la
|
||||
<literal>HttpSession</literal> (ni incluso serializarla a una capa separada).
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-optimistic-detached">
|
||||
<title>Objetos separados y versionado automático</title>
|
||||
|
||||
<para>
|
||||
Cada interacción con el almacén persistente ocurre en una nueva <literal>Session</literal>.
|
||||
Sin embargo, las mismas instancias persistentes son reusadas para cada interacción con la base de
|
||||
datos. La aplicación manipula el estado de las instancias separadas originalmente cargadas en otra
|
||||
<literal>Session</literal> y luego las readjunta usando <literal>Session.update()</literal>,
|
||||
<literal>Session.saveOrUpdate()</literal>, o <literal>Session.merge()</literal>.
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
||||
foo.setProperty("bar");
|
||||
session = factory.openSession();
|
||||
Transaction t = session.beginTransaction();
|
||||
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
|
||||
t.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
De nuevo, Hibernate chequeará las versiones de instancia durante la limpieza (flush),
|
||||
lanzando una excepción si ocurrieron actualizaciones en conflicto.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Puedes también llamar a <literal>lock()</literal> en vez de <literal>update()</literal>
|
||||
y usar <literal>LockMode.READ</literal> (realizando un chequeo de versión, puenteando
|
||||
todos los cachés) si estás seguro que el objeto no ha sido modificado.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="transactions-optimistic-customizing">
|
||||
<title>Personalizando el versionado automático</title>
|
||||
|
||||
<para>
|
||||
Puedes deshabilitar el incremento de versión automático de Hibernate para propiedades en particular
|
||||
y colecciones estableciendo el atributo de mapeo <literal>optimistic-lock</literal> a
|
||||
<literal>false</literal>. Hibernate entonces no incrementará ya más las versiones si la propiedad está
|
||||
sucia.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Los esquemas de base de datos heredados son frecuentemente estáticos y no pueden ser modificados.
|
||||
U otras aplicaciones podrían también acceder la misma base de datos y no saber cómo manejar los números
|
||||
de versión ni incluso timestamps. En ambos casos, el versionado no puede confiarse a una columna en
|
||||
particular en una tabla. Para forzar un chequeo de versiones sin un mapeo de propiedad de versión o
|
||||
timestamp, con una comparación del estado de todos los campos en una fila, activa
|
||||
<literal>optimistic-lock="all"</literal> en el mapeo de <literal><class></literal>.
|
||||
Nota que esto conceptualmente funciona solamente si Hibernate puede comparar el estado viejo y nuevo,
|
||||
es decir, si usas una sola <literal>Session</literal> larga y no
|
||||
sesión-por-petición-con-instancias-separadas.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A veces las modificaciones concurrentes pueden permitirse, en cuanto los cambios que hayan sido
|
||||
hechos no se traslapen. Si estableces <literal>optimistic-lock="dirty"</literal> al mapear la
|
||||
<literal><class></literal>, Hibernate sólo comparará los campos sucios durante la limpieza.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
En ambos casos, con columnas de versión/timestamp dedicadas o con comparación de campos
|
||||
completa/sucios, Hibernate usa una sola sentencia <literal>UPDATE</literal>
|
||||
(con una cláusula <literal>WHERE</literal> apropiada) por entidad para ejecutar el chequeo
|
||||
de versiones y actualizar la información. Si usas persistencia transitiva para la re-unión
|
||||
en cascada de entidades asociadas, Hibernate podría ejecutar actualizaciones innecesarias.
|
||||
Esto usualmente no es un problema, pero podrían ejecutarse disparadores (triggers)
|
||||
<emphasis>on update</emphasis> en la base de datos incluso cuando no se haya hecho ningún cambio
|
||||
a las instancias separadas. Puedes personalizar este comportamiento estableciendo
|
||||
<literal>select-before-update="true"</literal> en el mapeo de <literal><class></literal>,
|
||||
forzando a Hibernate a <literal>SELECT</literal> la instancia para asegurar que las actualizaciones
|
||||
realmente ocurran, antes de actualizar la fila.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="transactions-locking">
|
||||
<title>Bloqueo pesimista</title>
|
||||
|
||||
<para>
|
||||
No se pretende que los usuarios gasten mucho tiempo preocupándose de las estrategias de bloqueo.
|
||||
Usualmente es suficiente con especificar un nivel de aislamiento para las conexiones JDBC y entonces
|
||||
simplemente dejar que la base de datos haga todo el trabajo. Sin embargo, los usuarios avanzados pueden
|
||||
a veces obtener bloqueos exclusivos pesimistas, o reobtener bloqueos al comienzo de una nueva
|
||||
transacción.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
¡Hibernate siempre usará el mecanismo de bloqueo de la base de datos, nunca bloqueo
|
||||
de objetos en memoria!
|
||||
</para>
|
||||
|
||||
<para>
|
||||
La clase <literal>LockMode</literal> define los diferentes niveles de bloqueo que pueden ser adquiridos
|
||||
por Hibernate. Un bloqueo se obtiene por los siguientes mecanismos:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockMode.WRITE</literal> se adquiere automáticamente cuando Hibernate actualiza o
|
||||
inserta una fila.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockMode.UPGRADE</literal> puede ser adquirido bajo petición explícita del usuario
|
||||
usando <literal>SELECT ... FOR UPDATE</literal> en base de datos que soporten esa sintáxis.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockMode.UPGRADE_NOWAIT</literal> puede ser adquirido bajo petición explícita del usuario
|
||||
usando un <literal>SELECT ... FOR UPDATE NOWAIT</literal> bajo Oracle.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockMode.READ</literal> es adquirido automáticamente cuando Hibernate lee datos
|
||||
bajo los niveles de aislamiento Repeatable Read o Serializable. Puede ser readquirido por
|
||||
pedido explícito del usuario.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>LockMode.NONE</literal> representa la ausencia de un bloqueo. Todos los objetos se pasan
|
||||
a este modo de bloqueo al final de una <literal>Transaction</literal>. Los objetos asociados con una
|
||||
sesión vía una llamada a <literal>update()</literal> o <literal>saveOrUpdate()</literal> también
|
||||
comienzan en este modo de bloqueo.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
La "petición explícita del usuario" se expresa en una de las siguientes formas:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para>
|
||||
Una llamada a <literal>Session.load()</literal>, especificando un <literal>LockMode</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Una llamada a <literal>Session.lock()</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Una llamada a <literal>Query.setLockMode()</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Si se llama a <literal>Session.load()</literal> con <literal>UPGRADE</literal> o
|
||||
<literal>UPGRADE_NOWAIT</literal>, y el objeto pedido no ha sido aún cargado por la sesión, el objeto es
|
||||
cargado usando <literal>SELECT ... FOR UPDATE</literal>. Si se llama a <literal>load()</literal> para
|
||||
un objeto que ya esté cargado con un bloqueo menos restrictivo que el pedido, Hibernate llama a
|
||||
<literal>lock()</literal> para ese objeto.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>Session.lock()</literal> realiza un chequeo de número de versión si el modo de bloqueo especificado
|
||||
es <literal>READ</literal>, <literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>. (En el caso de
|
||||
<literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>, se usa
|
||||
<literal>SELECT ... FOR UPDATE</literal>.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Si la base de datos no soporta el modo de bloqueo solicitado, Hibernate usará un modo alternativo
|
||||
apropiado (en vez de lanzar una excepción). Esto asegura que las aplicaciones serán portables.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,282 +0,0 @@
|
|||
<chapter id="xml">
|
||||
<title>Mapeo XML</title>
|
||||
|
||||
<para><emphasis>
|
||||
Nota que esta es una funcionalidad experimental en Hibernate 3.0 y está
|
||||
bajo un desarrollo extremadamente activo.
|
||||
</emphasis></para>
|
||||
|
||||
<sect1 id="xml-intro" revision="1">
|
||||
<title>Trabajando con datos XML</title>
|
||||
|
||||
<para>
|
||||
Hibernate te permite trabajar con datos XML persistentes en casi la misma forma
|
||||
que trabajas con POJOs persistentes. Un árbol XML analizado (parsed) puede ser
|
||||
pensado como sólo otra forma de representar los datos relacionales a nivel de objetos,
|
||||
en vez de POJOs.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Hibernate soporta dom4j como API para manipular árboles XML. Puedes escribir
|
||||
consultas que traigan árboles dom4j de la base de datos y tener cualquier modificación
|
||||
que hagas al árbol sincronizada automáticamente a la base de datos. Puedes incluso tomar
|
||||
un documento XML, analizarlo usando dom4j, y escribirlo a la base de datos con cualquiera
|
||||
de las operaciones básicas de Hibernate: <literal>persist(), saveOrUpdate(), merge(),
|
||||
delete(), replicate()</literal> (la fusión no está aún soportada).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Esta funcionalidad tiene muchas aplicaciones incluyendo la importación/exportación de datos,
|
||||
externalización de datos de entidad vía JMS o SOAP y reportes basados en XSLT.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Un solo mapeo puede ser usado para mapear simultáneamente las propiedades de una clase y los nodos de un
|
||||
documento XML a la base de datos, o, si no hay ninguna clase a mapear, puede ser usado para mapear sólo
|
||||
el XML.
|
||||
</para>
|
||||
|
||||
<sect2 id="xml-intro-mapping">
|
||||
<title>Especificando los mapeos de XML y de clase juntos</title>
|
||||
|
||||
<para>
|
||||
He aquí un ejemplo de mapear un POJO y XML simultáneamente:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Account"
|
||||
table="ACCOUNTS"
|
||||
node="account">
|
||||
|
||||
<id name="accountId"
|
||||
column="ACCOUNT_ID"
|
||||
node="@id"/>
|
||||
|
||||
<many-to-one name="customer"
|
||||
column="CUSTOMER_ID"
|
||||
node="customer/@id"
|
||||
embed-xml="false"/>
|
||||
|
||||
<property name="balance"
|
||||
column="BALANCE"
|
||||
node="balance"/>
|
||||
|
||||
...
|
||||
|
||||
</class>]]></programlisting>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="xml-onlyxml">
|
||||
<title>Especificando sólo un mapeo XML</title>
|
||||
|
||||
<para>
|
||||
He aquí un ejemplo donde no hay ninguna clase POJO:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class entity-name="Account"
|
||||
table="ACCOUNTS"
|
||||
node="account">
|
||||
|
||||
<id name="id"
|
||||
column="ACCOUNT_ID"
|
||||
node="@id"
|
||||
type="string"/>
|
||||
|
||||
<many-to-one name="customerId"
|
||||
column="CUSTOMER_ID"
|
||||
node="customer/@id"
|
||||
embed-xml="false"
|
||||
entity-name="Customer"/>
|
||||
|
||||
<property name="balance"
|
||||
column="BALANCE"
|
||||
node="balance"
|
||||
type="big_decimal"/>
|
||||
|
||||
...
|
||||
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Este mapeo te permite acceder a los datos como un árbol dom4j, o como un grafo de pares nombre/valor de
|
||||
propiedad (<literal>Map</literal>s de Java). Los nombres de propiedades son construcciones puramente
|
||||
lógicas a las que se puede hacer referencia en consultas HQL.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="xml-mapping" revision="1">
|
||||
<title>Mapeo de metadatos XML</title>
|
||||
|
||||
<para>
|
||||
Muchos elementos de mapeo de Hibernate aceptan el atributo <literal>node</literal>. Esto te permite espcificar
|
||||
el nombre de un atributo o elemento XML que contenga los datos de la propiedad o entidad. El formato del
|
||||
atributo <literal>node</literal> debe ser uno de los siguientes:
|
||||
</para>
|
||||
|
||||
<itemizedlist spacing="compact">
|
||||
<listitem>
|
||||
<para><literal>"element-name"</literal> - mapea al elemento XML mencionado</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><literal>"@attribute-name"</literal> - mapea al atributo XML mencionado</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para><literal>"."</literal> - mapea al elemento padre</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>"element-name/@attribute-name"</literal> -
|
||||
mapea al atributo mencionado del elemento mencionado
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Para las colecciones y asociaciones monovaluadas, existe un atributo adicional <literal>embed-xml</literal>.
|
||||
Si <literal>embed-xml="true"</literal>, que es el valor por defecto, el árbol XML para la entidad
|
||||
asociada (o colección de tipo de valor) será embebida directamente en el árbol XML para la entidad que
|
||||
posee la asociación. En otro caso, si <literal>embed-xml="false"</literal>, sólo el valor identificador
|
||||
referenciado aparecerá en el XML para asociaciones de punto único y para las colecciones simplemente
|
||||
no aparecerá en absoluto.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
¡Debes ser cuidadoso de no dejar <literal>embed-xml="true"</literal> para demasiadas asociaciones,
|
||||
ya que XML no trata bien la circularidad!
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<class name="Customer"
|
||||
table="CUSTOMER"
|
||||
node="customer">
|
||||
|
||||
<id name="id"
|
||||
column="CUST_ID"
|
||||
node="@id"/>
|
||||
|
||||
<map name="accounts"
|
||||
node="."
|
||||
embed-xml="true">
|
||||
<key column="CUSTOMER_ID"
|
||||
not-null="true"/>
|
||||
<map-key column="SHORT_DESC"
|
||||
node="@short-desc"
|
||||
type="string"/>
|
||||
<one-to-many entity-name="Account"
|
||||
embed-xml="false"
|
||||
node="account"/>
|
||||
</map>
|
||||
|
||||
<component name="name"
|
||||
node="name">
|
||||
<property name="firstName"
|
||||
node="first-name"/>
|
||||
<property name="initial"
|
||||
node="initial"/>
|
||||
<property name="lastName"
|
||||
node="last-name"/>
|
||||
</component>
|
||||
|
||||
...
|
||||
|
||||
</class>]]></programlisting>
|
||||
|
||||
<para>
|
||||
en este caso, hemos decidido embeber la colección de ids de cuenta, pero no los datos reales de cuenta.
|
||||
La siguiente consulta HQL:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[from Customer c left join fetch c.accounts where c.lastName like :lastName]]></programlisting>
|
||||
|
||||
<para>
|
||||
devolvería conjuntos de datos como estos:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<customer id="123456789">
|
||||
<account short-desc="Savings">987632567</account>
|
||||
<account short-desc="Credit Card">985612323</account>
|
||||
<name>
|
||||
<first-name>Gavin</first-name>
|
||||
<initial>A</initial>
|
||||
<last-name>King</last-name>
|
||||
</name>
|
||||
...
|
||||
</customer>]]></programlisting>
|
||||
|
||||
<para>
|
||||
Si estableces <literal>embed-xml="true"</literal> en el mapeo <literal><one-to-many></literal>, los datos
|
||||
podrían verse así:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[<customer id="123456789">
|
||||
<account id="987632567" short-desc="Savings">
|
||||
<customer id="123456789"/>
|
||||
<balance>100.29</balance>
|
||||
</account>
|
||||
<account id="985612323" short-desc="Credit Card">
|
||||
<customer id="123456789"/>
|
||||
<balance>-2370.34</balance>
|
||||
</account>
|
||||
<name>
|
||||
<first-name>Gavin</first-name>
|
||||
<initial>A</initial>
|
||||
<last-name>King</last-name>
|
||||
</name>
|
||||
...
|
||||
</customer>]]></programlisting>
|
||||
|
||||
</sect1>
|
||||
|
||||
|
||||
<sect1 id="xml-manipulation" revision="1">
|
||||
<title>Manipulando datos XML</title>
|
||||
|
||||
<para>
|
||||
Vamos a releer y actualizar documentos XML en la aplicación. Hacemos esto obteniendo una sesión dom4j:
|
||||
</para>
|
||||
|
||||
<programlisting><![CDATA[Document doc = ....;
|
||||
|
||||
Session session = factory.openSession();
|
||||
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
List results = dom4jSession
|
||||
.createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
|
||||
.list();
|
||||
for ( int i=0; i<results.size(); i++ ) {
|
||||
//add the customer data to the XML document
|
||||
Element customer = (Element) results.get(i);
|
||||
doc.getRootElement().add(customer);
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<programlisting><![CDATA[Session session = factory.openSession();
|
||||
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||
Transaction tx = session.beginTransaction();
|
||||
|
||||
Element cust = (Element) dom4jSession.get("Customer", customerId);
|
||||
for ( int i=0; i<results.size(); i++ ) {
|
||||
Element customer = (Element) results.get(i);
|
||||
//change the customer name in the XML and database
|
||||
Element name = customer.element("name");
|
||||
name.element("first-name").setText(firstName);
|
||||
name.element("initial").setText(initial);
|
||||
name.element("last-name").setText(lastName);
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
session.close();]]></programlisting>
|
||||
|
||||
<para>
|
||||
Es extremadamente útil combinar esta funcionalidad con la operación <literal>replicate()</literal>
|
||||
de Hibernate para implementar la importación/exportación de datos basada en XML.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
Loading…
Reference in New Issue