new docbook layout (prep for translations migration to PO)
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14075 1b8cb986-b30d-0410-93ca-fae66ebed9b2
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
|
||||||
|
~
|
||||||
|
~ This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
~ copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
~ Lesser General Public License, v. 2.1. This program is distributed in the
|
||||||
|
~ hope that it will be useful, but WITHOUT A WARRANTY; without even the implied
|
||||||
|
~ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
~ Lesser General Public License for more details. You should have received a
|
||||||
|
~ copy of the GNU Lesser General Public License, v.2.1 along with this
|
||||||
|
~ distribution; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
~
|
||||||
|
~ Red Hat Author(s): Steve Ebersole
|
||||||
|
-->
|
||||||
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
|
||||||
|
<!ENTITY versionNumber "3.3.0.alpha1">
|
||||||
|
<!ENTITY copyrightYear "2004">
|
||||||
|
<!ENTITY copyrightHolder "Red Hat Middleware, LLC.">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<book>
|
||||||
|
|
||||||
|
<bookinfo>
|
||||||
|
<title>HIBERNATE - Persistencia Relacional para Java Idiomático</title>
|
||||||
|
<subtitle>Documentación de Referencia de Hibernate</subtitle>
|
||||||
|
<releaseinfo>&versionNumber;</releaseinfo>
|
||||||
|
<productnumber>&versionNumber;</productnumber>
|
||||||
|
<issuenum>1</issuenum>
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/hibernate_logo_a.png" align="center" />
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="images/hibernate_logo_a.png" depth="3cm" />
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
<copyright>
|
||||||
|
<year>©rightYear;</year>
|
||||||
|
<holder>©rightHolder;</holder>
|
||||||
|
</copyright>
|
||||||
|
<xi:include href="translators.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="legal_notice.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="legal_notice2.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
</bookinfo>
|
||||||
|
|
||||||
|
<toc/>
|
||||||
|
|
||||||
|
<xi:include href="content/preface.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/tutorial.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/architecture.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/configuration.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/persistent_classes.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/basic_mapping.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/collection_mapping.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/association_mapping.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/component_mapping.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/inheritance_mapping.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/session_api.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/transactions.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/events.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/batch.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/query_hql.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/query_criteria.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/query_sql.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/filters.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/xml.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/performance.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/toolset_guide.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/example_parentchild.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/example_weblog.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
<xi:include href="content/example_mappings.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
<xi:include href="content/best_practices.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||||
|
|
||||||
|
</book>
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
<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="../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="../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="../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>
|
||||||
|
|
|
@ -0,0 +1,527 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,229 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,403 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,233 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,654 @@
|
||||||
|
<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="../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="../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="../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>
|
||||||
|
|
|
@ -0,0 +1,362 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,429 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,464 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,478 @@
|
||||||
|
<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,63 +1,10 @@
|
||||||
<?xml version='1.0' encoding="iso-8859-1"?>
|
<?xml version='1.0'?>
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN"
|
|
||||||
"../support/docbook-dtd/docbookx.dtd"
|
|
||||||
[
|
|
||||||
<!ENTITY quickstart SYSTEM "modules/quickstart.xml">
|
|
||||||
<!ENTITY tutorial SYSTEM "modules/tutorial.xml">
|
|
||||||
<!ENTITY architecture SYSTEM "modules/architecture.xml">
|
|
||||||
<!ENTITY configuration SYSTEM "modules/configuration.xml">
|
|
||||||
<!ENTITY persistent-classes SYSTEM "modules/persistent_classes.xml">
|
|
||||||
<!ENTITY basic-mapping SYSTEM "modules/basic_mapping.xml">
|
|
||||||
<!ENTITY collection-mapping SYSTEM "modules/collection_mapping.xml">
|
|
||||||
<!ENTITY association-mapping SYSTEM "modules/association_mapping.xml">
|
|
||||||
<!ENTITY component-mapping SYSTEM "modules/component_mapping.xml">
|
|
||||||
<!ENTITY inheritance-mapping SYSTEM "modules/inheritance_mapping.xml">
|
|
||||||
<!ENTITY session-api SYSTEM "modules/session_api.xml">
|
|
||||||
<!ENTITY transactions SYSTEM "modules/transactions.xml">
|
|
||||||
<!ENTITY events SYSTEM "modules/events.xml">
|
|
||||||
<!ENTITY batch SYSTEM "modules/batch.xml">
|
|
||||||
<!ENTITY query-hql SYSTEM "modules/query_hql.xml">
|
|
||||||
<!ENTITY query-criteria SYSTEM "modules/query_criteria.xml">
|
|
||||||
<!ENTITY query-sql SYSTEM "modules/query_sql.xml">
|
|
||||||
<!ENTITY filters SYSTEM "modules/filters.xml">
|
|
||||||
<!ENTITY xml SYSTEM "modules/xml.xml">
|
|
||||||
<!ENTITY performance SYSTEM "modules/performance.xml">
|
|
||||||
<!ENTITY toolset-guide SYSTEM "modules/toolset_guide.xml">
|
|
||||||
<!ENTITY example-parentchild SYSTEM "modules/example_parentchild.xml">
|
|
||||||
<!ENTITY example-weblog SYSTEM "modules/example_weblog.xml">
|
|
||||||
<!ENTITY example-mappings SYSTEM "modules/example_mappings.xml">
|
|
||||||
<!ENTITY best-practices SYSTEM "modules/best_practices.xml">
|
|
||||||
]>
|
|
||||||
|
|
||||||
<book lang="es">
|
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
<bookinfo>
|
|
||||||
<title>HIBERNATE - Persistencia Relacional para Java Idiomático</title>
|
|
||||||
<subtitle>Documentación de Referencia de Hibernate</subtitle>
|
|
||||||
<releaseinfo>3.0.5</releaseinfo>
|
|
||||||
</bookinfo>
|
|
||||||
|
|
||||||
<toc/>
|
|
||||||
|
|
||||||
<preface id="preface" revision="2">
|
<preface id="preface" revision="2">
|
||||||
<title>Prefacio</title>
|
<title>Prefacio</title>
|
||||||
|
|
||||||
<para>
|
|
||||||
Advertencia! Esta es una versión traducida del inglés de
|
|
||||||
la documentacién de referencia de Hibernate. La versión
|
|
||||||
traducida puede no estar actualizada! Sin embargo, las diferencias
|
|
||||||
deberían ser sólo menores. Consulta la documentación
|
|
||||||
de referencia en inglés si estás perdiendo información
|
|
||||||
o encuentras algún error de traducción. Si quieres colaborar con
|
|
||||||
una traducción en particular, contáctanos en la lista de correo
|
|
||||||
de desarrolladores de Hibernate.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
Traductor(es): Bernardo Antonio Buffa Colomé <kreimer@bbs.frc.utn.edu.ar>
|
|
||||||
<!--,
|
|
||||||
Antonio López Gota <antoniogota@gmail.com> -->
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Trabajar con software orientado a objetos y una base de datos relacional puede ser
|
Trabajar con software orientado a objetos y una base de datos relacional puede ser
|
||||||
|
@ -163,41 +110,3 @@
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
</preface>
|
</preface>
|
||||||
|
|
||||||
&quickstart;
|
|
||||||
&tutorial;
|
|
||||||
&architecture;
|
|
||||||
|
|
||||||
&configuration;
|
|
||||||
|
|
||||||
&persistent-classes;
|
|
||||||
|
|
||||||
&basic-mapping;
|
|
||||||
&collection-mapping;
|
|
||||||
&association-mapping;
|
|
||||||
&component-mapping;
|
|
||||||
&inheritance-mapping;
|
|
||||||
|
|
||||||
&session-api;
|
|
||||||
&transactions;
|
|
||||||
&events;
|
|
||||||
&batch;
|
|
||||||
|
|
||||||
&query-hql;
|
|
||||||
&query-criteria;
|
|
||||||
&query-sql;
|
|
||||||
&filters;
|
|
||||||
&xml;
|
|
||||||
|
|
||||||
&performance;
|
|
||||||
|
|
||||||
&toolset-guide;
|
|
||||||
|
|
||||||
&example-parentchild;
|
|
||||||
&example-weblog;
|
|
||||||
&example-mappings;
|
|
||||||
|
|
||||||
&best-practices;
|
|
||||||
|
|
||||||
</book>
|
|
||||||
|
|
|
@ -0,0 +1,431 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,477 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,666 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,459 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,925 @@
|
||||||
|
<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>
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
<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.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>
|
||||||
|
|
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version='1.0'?>
|
||||||
|
<!DOCTYPE legalnotice PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved.
|
||||||
|
~
|
||||||
|
~ This copyrighted material is made available to anyone wishing to use, modify,
|
||||||
|
~ copy, or redistribute it subject to the terms and conditions of the GNU
|
||||||
|
~ Lesser General Public License, v. 2.1. This program is distributed in the
|
||||||
|
~ hope that it will be useful, but WITHOUT A WARRANTY; without even the implied
|
||||||
|
~ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
~ Lesser General Public License for more details. You should have received a
|
||||||
|
~ copy of the GNU Lesser General Public License, v.2.1 along with this
|
||||||
|
~ distribution; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
~
|
||||||
|
~ Red Hat Author(s): Steve Ebersole
|
||||||
|
-->
|
||||||
|
<legalnotice id="Legal_Notice">
|
||||||
|
<title>Legal Notice</title>
|
||||||
|
<para>
|
||||||
|
<address>
|
||||||
|
<street>1801 Varsity Drive</street>
|
||||||
|
<city>Raleigh</city>, <state>NC</state><postcode>27606-2072</postcode><country>USA</country>
|
||||||
|
<phone>Phone: +1 919 754 3700</phone>
|
||||||
|
<phone>Phone: 888 733 4281</phone>
|
||||||
|
<fax>Fax: +1 919 754 3701</fax>
|
||||||
|
<pob>PO Box 13588</pob><city>Research Triangle Park</city>, <state>NC</state><postcode>27709</postcode><country>USA</country>
|
||||||
|
</address>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Copyright <trademark class="copyright"></trademark> 2007 by Red Hat, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, V1.0 or later (the latest version is presently available at <ulink url="http://www.opencontent.org/openpub/">http://www.opencontent.org/openpub/</ulink>).
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Distribution of the work or derivative of the work in any standard (paper) book form for commercial purposes is prohibited unless prior permission is obtained from the copyright holder.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Red Hat and the Red Hat "Shadow Man" logo are registered trademarks of Red Hat, Inc. in the United States and other countries.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
All other trademarks referenced herein are the property of their respective owners.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The GPG fingerprint of the security@redhat.com key is:
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
CA 20 86 86 2B D6 9D FC 65 F6 EC C4 21 91 80 CD DB 42 A6 0E
|
||||||
|
</para>
|
||||||
|
</legalnotice>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version='1.0'?>
|
||||||
|
<!DOCTYPE legalnotice PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
|
<legalnotice id="Legal_Notice">
|
||||||
|
<title>Translation-specific Legal Notice</title>
|
||||||
|
<para>
|
||||||
|
Advertencia! Esta es una versión traducida del inglés de
|
||||||
|
la documentacién de referencia de Hibernate. La versión
|
||||||
|
traducida puede no estar actualizada! Sin embargo, las diferencias
|
||||||
|
deberían ser sólo menores. Consulta la documentación
|
||||||
|
de referencia en inglés si estás perdiendo información
|
||||||
|
o encuentras algún error de traducción. Si quieres colaborar con
|
||||||
|
una traducción en particular, contáctanos en la lista de correo
|
||||||
|
de desarrolladores de Hibernate.
|
||||||
|
</para>
|
||||||
|
</legalnotice>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version='1.0'?>
|
||||||
|
|
||||||
|
<!DOCTYPE authorgroup PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
|
<authorgroup id="AuthorGroup">
|
||||||
|
<othercredit class="translator">
|
||||||
|
<othername><![CDATA[Bernardo Antonio Buffa Colomé]]></othername>
|
||||||
|
<email>kreimer@bbs.frc.utn.edu.ar</email>
|
||||||
|
</othercredit>
|
||||||
|
</authorgroup>
|
|
@ -1,97 +0,0 @@
|
||||||
A {
|
|
||||||
color: #003399;
|
|
||||||
}
|
|
||||||
|
|
||||||
A:active {
|
|
||||||
color: #003399;
|
|
||||||
}
|
|
||||||
|
|
||||||
A:visited {
|
|
||||||
color: #888888;
|
|
||||||
}
|
|
||||||
|
|
||||||
P, OL, UL, LI, DL, DT, DD, BLOCKQUOTE {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
TD, TH, SPAN {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLOCKQUOTE {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
H1, H2, H3, H4, H5, H6 {
|
|
||||||
color: #000000;
|
|
||||||
font-weight:500;
|
|
||||||
margin-top:10px;
|
|
||||||
padding-top:15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
TABLE {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing:0;
|
|
||||||
border: 1px thin black;
|
|
||||||
empty-cells: hide;
|
|
||||||
}
|
|
||||||
|
|
||||||
TD {
|
|
||||||
padding: 4pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
H1 { font-size: 150%; }
|
|
||||||
H2 { font-size: 140%; }
|
|
||||||
H3 { font-size: 110%; font-weight: bold; }
|
|
||||||
H4 { font-size: 110%; font-weight: bold;}
|
|
||||||
H5 { font-size: 100%; font-style: italic; }
|
|
||||||
H6 { font-size: 100%; font-style: italic; }
|
|
||||||
|
|
||||||
TT {
|
|
||||||
font-size: 90%;
|
|
||||||
font-family: "Courier New", Courier, monospace;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
PRE {
|
|
||||||
font-size: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: #CCCCCC;
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
}
|
|
||||||
|
|
||||||
UL, OL, LI {
|
|
||||||
list-style: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
HR {
|
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
background-color: #CCCCCC;
|
|
||||||
border-width: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
color: #CCCCCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variablelist {
|
|
||||||
padding-top: 10;
|
|
||||||
padding-bottom:10;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemizedlist, UL {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom:0;
|
|
||||||
margin:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.term {
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 8.4 KiB |