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
This commit is contained in:
Steve Ebersole 2007-10-09 18:45:36 +00:00
parent cd06d79254
commit 1a839084d0
50 changed files with 18950 additions and 193 deletions

View File

@ -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&#x00e1;tico</title>
<subtitle>Documentaci&#x00f3;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>&copyrightYear;</year>
<holder>&copyrightHolder;</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>

View File

@ -0,0 +1,279 @@
<chapter id="architecture">
<title>Arquitectura</title>
<sect1 id="architecture-overview" revision="1">
<title>Visi&#x00f3;n General</title>
<para>
Una visi&#x00f3;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&#x00f3;n para proveer servicios de persistencia (y objetos
persistentes) a la aplicaci&#x00f3;n.
</para>
<para>
Nos gustar&#x00ed;a mostrar una vista m&#x00e1;s detallada de la arquitectura de tiempo
de ejecuci&#x00f3;n. Desafortunadamente, Hibernate es flexible y soporta diferentes
enfoques. Mostraremos los dos extremos. En la arquitectura "sencilla", es la
aplicaci&#x00f3;n la que provee su propias conexiones JDBC y gestiona sus propias
transacciones. Este enfoque usa un m&#x00ed;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&#x00f3;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&#x00ed; algunas definiciones de los objetos en los diagramas:
<variablelist spacing="compact">
<varlistentry>
<term>SessionFactory (<literal>org.hibernate.SessionFactory</literal>)</term>
<listitem>
<para>
Cach&#x00e9; threadsafe (inmutable) de mapeos compilados para
una sola base de datos. Es una f&#x00e1;brica de <literal>Session</literal>
y un cliente de <literal>ConnectionProvider</literal>. Opcionalmente,
puede mantener una cach&#x00e9; (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&#x00f3;n
entre la aplicaci&#x00f3;n y el almacenamiento persistente. Envuelve una
conexi&#x00f3;n JDBC. Es una f&#x00e1;brica de <literal>Transaction</literal>.
Mantiene una cach&#x00e9; 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&#x00f3;nalidad de negocio. Estos pueden ser JavaBeans/POJOs
(Plain Old Java Objects, o sea, cualquier objeto Java), la &#x00fa;nica
cosa especial en ellos es que estan asociados actualmente con una
(y s&#x00f3;lo una) <literal>Session</literal>. Tan pronto como la
<literal>Session</literal> sea cerrada, ser&#x00e1;n separados y
estar&#x00e1;n libres para ser usados en cualquier capa de aplicaci&#x00f3;n.
(por ejemplo, directamente como objetos de transferencia de datos hacia
y desde la capa de presentaci&#x00f3;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&#x00f3;n y (a&#x00fa;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&#x00f3;n
para especificar unidades at&#x00f3;micas de trabajo. Abstrae a la aplicaci&#x00f3;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&#x00f3;n de la transacci&#x00f3;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&#x00e1;brica (y pool) de conexiones JDBC. Abstrae a la aplicaci&#x00f3;n
del <literal>Datasource</literal> o <literal>DriverManager</literal> subyacente.
No se expone a la aplicaci&#x00f3;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&#x00e1;brica de instancias de <literal>Transaction</literal>.
No se expone a la aplicaci&#x00f3;n, pero puede ser extendido/implementado por
el desarrollador.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><emphasis>Interfaces de Extensi&#x00f3;n</emphasis></term>
<listitem>
<para>
Hibernate ofrece muchas interfaces de extensi&#x00f3;n opcional que puedes
implementar para modificar a medida el comportamiento de tu capa de persistencia.
Para m&#x00e1;s detalles, mira la documentaci&#x00f3;n de la API.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
Dada una arquitectura "sencilla", la aplicaci&#x00f3;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&#x00e1; 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&#x00e1; actualmente asociada con un
contexto de persistencia. Tiene una identidad persistente
(valor de clave primaria) y, quiz&#x00e1;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&#x00f3;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&#x00e1;s, una fila correspondiente
en la base de datos. Para las instancias separadas,
Hibernate no establece ninguna garant&#x00ed;a sobre
la relaci&#x00f3;n entre identidad persistente e identidad Java.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1 id="architecture-jmx" revision="1">
<title>Integraci&#x00f3;n JMX</title>
<para>
JMX es el est&#x00e1;ndar J2EE para la gesti&#x00f3;n de componentes Java. Hibernate
puede ser gestionado por medio de un servicio est&#x00e1;ndar JMX.
Proveemos una implementaci&#x00f3;n de MBean en la distribuci&#x00f3;n,
<literal>org.hibernate.jmx.HibernateService</literal>.
</para>
<para>
Para ejemplo de c&#x00f3;mo desplegar Hibernate como un servicio JMX en un Servidor
de Aplicaciones JBoss, por favor, mira la Gu&#x00ed;a del Usuario de JBoss.
En JBoss AS, tienes adem&#x00e1;s estos beneficios si despliegas usando JMX:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Gesti&#x00f3;n de Sesi&#x00f3;n:</emphasis> El ciclo de vida de la <literal>Session</literal>
de Hibernate puede estar autom&#x00e1;ticamente ligado al &#x00e1;mbito de una transacci&#x00f3;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&#x00e1;s tampoco tienes
que preocuparte m&#x00e1;s de la demarcaci&#x00f3;n de la transacci&#x00f3;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&#x00f3;n usuales de una <literal>SessionFactory</literal> de
Hibernate. Sin embargo, todav&#x00ed;a tienes que nombrar todos tus ficheros de mapeo en el
descriptor de despliegue. Si decides usar el depliegue de HAR opcional, JBoss detectar&#x00e1;
autom&#x00e1;ticamente todos los ficheros de mapeo en tu fichero HAR.
</para>
</listitem>
</itemizedlist>
<para>
Para m&#x00e1;s informaci&#x00f3;n sobre estas opciones, consulta la
Gu&#x00ed;a de Usuario del JBoss AS.
</para>
<para>
Otra funcionalidad disponible como un servicio JMX son las estad&#x00ed;sticas en
tiempo de ejecuci&#x00f3;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&#x00e1;s ser configurado como un conector JCA. Por favor mira el
sitio web para m&#x00e1;s detalles. Por favor ten en cuenta que el soporte de JCA
de Hibernate est&#x00e1; a&#x00fa;n considerado experimental.
</para>
</sect1>
</chapter>

View File

@ -0,0 +1,527 @@
<chapter id="associations">
<title>Mapeos de Asociaci&#x00f3;n</title>
<sect1 id="assoc-intro" revision="1">
<title>Introducci&#x00f3;n</title>
<para>
Los mapeos de asociaci&#x00f3;n son frecuentemente las cosas mas dif&#x00ed;ciles
de hacer correctamente. En esta secci&#x00f3;n iremos a trav&#x00e9;s de los casos
can&#x00f3;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&#x00f3;n interviniente, y por su multiplicidad.
</para>
<para>
Las claves for&#x00e1;neas que aceptan valores nulos (en adelante, nullables)
no son consideradas una buena pr&#x00e1;ctica en el modelado tradicional de datos,
as&#x00ed; que todos nuestros ejemplos usan claves for&#x00e1;neas no nullables.
Esto no es un requerimiento de Hibernate, y todos los mapeos funcionar&#x00e1;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&#x00f3;n unidireccional muchos-a-uno</emphasis> es el tipo
m&#x00e1;s com&#x00fa;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&#x00f3;n unidireccional uno-a-uno en una clave primaria</emphasis>
es casi id&#x00e9;ntica. La &#x00fa;nica diferencia es la restricci&#x00f3;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&#x00f3;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&#x00f3;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&#x00f3;n unidireccional uno-a-muchos en una clave for&#x00e1;nea</emphasis>
es un caso muy inusual, y realmente no est&#x00e1; 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&#x00f3;n para este tipo de asociaci&#x00f3;n.
</para>
</sect2>
</sect1>
<sect1 id="assoc-unidirectional-join" revision="1">
<title>Asociaciones unidireccionales con tablas de uni&#x00f3;n</title>
<sect2 id="assoc-unidirectional-join-12m">
<title>uno a muchos</title>
<para>
Una <emphasis>asociaci&#x00f3;n unidireccional uno-a-muchos en una tabla de uni&#x00f3;n</emphasis>
es m&#x00e1;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&#x00f3;n unidireccional muchos-a-uno en una tabla de uni&#x00f3;n</emphasis>
es bastante com&#x00fa;n cuando la asociaci&#x00f3;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&#x00f3;n unidireccional uno-a-uno en una tabla de uni&#x00f3;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&#x00f3;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&#x00f3;n bidireccional muchos-a-uno</emphasis> es
el tipo m&#x00e1;s com&#x00fa;n de asociaci&#x00f3;n. (Esta es la relaci&#x00f3;n
est&#x00e1;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&#x00f3;n bidireccional uno-a-uno en una clave for&#x00e1;nea</emphasis>
es bastante com&#x00fa;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&#x00f3;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&#x00f3;n</title>
<sect2 id="assoc-bidirectional-join-12m">
<title>uno a muchos / muchos a uno</title>
<para>
Una <emphasis>asociaci&#x00f3;n bidireccional uno-a-muchos en una tabla de uni&#x00f3;n</emphasis>.
Observa que el <literal>inverse="true"</literal> puede ir a cualquier lado de la asociaci&#x00f3;n,
en la colecci&#x00f3;n, o en la uni&#x00f3;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&#x00f3;n bidireccional uno-a-uno en una tabla de uni&#x00f3;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&#x00f3;n bidireccional muchos-a-muchos</emphasis>.
</para>
<programlisting><![CDATA[<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<set name="addresses">
<key column="personId"/>
<many-to-many column="addressId"
class="Address"/>
</set>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
<set name="people" inverse="true">
<key column="addressId"/>
<many-to-many column="personId"
class="Person"/>
</set>
</class>]]></programlisting>
<programlisting><![CDATA[
create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
create table Address ( addressId bigint not null primary key )
]]></programlisting>
</sect2>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -0,0 +1,229 @@
<chapter id="best-practices" revision="3">
<title>Mejores Pr&#x00e1;cticas</title>
<variablelist spacing="compact">
<varlistentry>
<term>Escribe clase finamente granularizadas y mapealas usando <literal>&lt;component&gt;</literal>.</term>
<listitem>
<para>
Usa una clase <literal>Direcci&#x00f3;n</literal> para encapsular <literal>calle</literal>,
<literal>distrito</literal>, <literal>estado</literal>, <literal>c&#x00f3;digo postal</literal>.
Esto alienta la reutilizaci&#x00f3;n de c&#x00f3;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&#x00e9;ticos'
(generados, sin ning&#x00fa;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>&lt;natural-id&gt;</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&#x00ed;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&#x00e1;ctica si tus consultas llaman a funciones SQL que no son del
est&#x00e1;ndar ANSI. Externalizar las cadenas de consulta a ficheros de mapeo har&#x00e1; la
aplicaci&#x00f3;n m&#x00e1;s portable.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Usa variables de ligado.</term>
<listitem>
<para>
Igual que en JDBC, siempre remplaza valores no constantes con "?". &#x00a1;Nunca uses manipulaci&#x00f3;n
de cadenas para ligar un valor no constante en una consulta! Incluso mejor, considera usar
par&#x00e1;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&#x00f3;n administre las conexiones JDBC. Este enfoque debe considerarse
como &#x00fa;ltimo recurso. Si no puedes usar los provedores de conexi&#x00f3;n prefabricados, considera
prover tu propia implementaci&#x00f3;n de <literal>org.hibernate.connection.ConnectionProvider</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Considera usar un tipo personalizado.</term>
<listitem>
<para>
Sup&#x00f3;n que tienes un tipo Java, digamos de alguna biblioteca, que necesita hacerse persistente
pero no provee los m&#x00e9;todos de acceso necesarios para mapearlo como un componente. Debes considerar
implementar <literal>org.hibernate.UserType</literal>. Este enfoque libera al c&#x00f3;digo de aplicaci&#x00f3;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 &#x00e1;reas del sistema de rendimiento cr&#x00ed;tico, algunos tipos de operaciones podr&#x00ed;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&#x00e1;s r&#x00e1;pido. Si necesitas
usar JDBC directo, podr&#x00ed;a ser valioso abrir una <literal>Session</literal> de Hibernate y usar esa
conexi&#x00f3;n JDBC. De esta forma puedes usar a&#x00fa;n la misma estrategia de transacci&#x00f3;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&#x00f3;n sincroniza su estado persistente con la base de datos. El rendimiento
se ver&#x00e1; afectado si este proceso ocurre demasiado frecuentemente. A veces puedes minimizar
limpieza innecesaria deshabilitando la limpieza autom&#x00e1;tica o incluso cambiando el orden de las
consultas u otras operaciones en una transacci&#x00f3;n en particular.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>En una aplicaci&#x00f3;n en tres gradas, considera usar objetos separados.</term>
<listitem>
<para>
Al usar una arquitectura de servlet / sesi&#x00f3;n, puedes pasar objetos persistentes en el bean de
sesi&#x00f3;n hacia y desde la capa de servlet / JSP. Usa una sesi&#x00f3;n nueva para atender el servicio de cada
petici&#x00f3;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&#x00f3;n</emphasis>
ejecut&#x00e1;ndose en largo, una sola unidad de trabajo desde el punto de vista de un usuario.
Una transacci&#x00f3;n de aplicaci&#x00f3;n puede abarcar muchos ciclos petici&#x00f3;n/respuesta del cliente.
Es com&#x00fa;n usar objetos separados para implementar transacciones de aplicaci&#x00f3;n. Una alternativa,
extremadamente apropiada en arquitecturas en dos gradas, es mantener un solo contacto de persistencia
abierto (sesi&#x00f3;n) para todo el ciclo de vida de la transacci&#x00f3;n de aplicaci&#x00f3;n y simplemente
desconectar de la conexi&#x00f3;n JDBC al final de cada petici&#x00f3;n, y reconectar al comienzo de la
petici&#x00f3;n subsecuente. Nunca compartas una &#x00fa;nica sesi&#x00f3;n a trav&#x00e9;s de m&#x00e1;s de una transacci&#x00f3;n
de aplicaci&#x00f3;n, o estar&#x00e1;s trabajando con datos añejos.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>No trates la excepciones como recuperables.</term>
<listitem>
<para>
Esto es m&#x00e1;s una pr&#x00e1;ctica necesaria que una "mejor" pr&#x00e1;ctica. Cuando ocurra una excepci&#x00f3;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&#x00f3;n perezosa para las asociaciones.</term>
<listitem>
<para>
Usa escasamente la recuperaci&#x00f3;n temprana. Usa proxies y colecciones perezosas para la mayor&#x00ed;a
de asociaciones a clases probablemente no est&#x00e9;n mantenidas en el cach&#x00e9; de segundo nivel. Para
las asociaciones a clases en cach&#x00e9;, donde hay una probabilidad de acceso a cach&#x00e9; extremadamente
alta, deshabilita expl&#x00ed;citamente la recuperaci&#x00f3;n temprana usando <literal>lazy="false"</literal>.
Cuando sea apropiada la recuperaci&#x00f3;n por uni&#x00f3;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&#x00f3;n <emphasis>sesi&#x00f3;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&#x00f3;sito doble: primero, atacan el problema que los beans de entidad no son serializables.
Segundo, definen impl&#x00ed;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&#x00f3;n. Hibernate elimina el primer prop&#x00f3;sito. Sin embargo, a&#x00fa;n necesitas una fase
de ensamblado (piensa en tus m&#x00e9;todos de negocio como si tuviesen un contrato estricto con la grada
de presentaci&#x00f3;n sobre qu&#x00e9; datos est&#x00e1;n disponibles en los objetos separados) a menos que est&#x00e9;s
preparado para tener el contexto de persistencia (la sesi&#x00f3;n) abierto a trav&#x00e9;s del proceso
de renderizaci&#x00f3;n de la vista. &#x00a1;Esta no es una limitaci&#x00f3;n de Hibernate! Es un requerimiento
fundamental de acceso seguro a datos transaccionales.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Considera abstraer tu l&#x00f3;gica de negocio de Hibernate</term>
<listitem>
<para>
Oculta el c&#x00f3;digo de acceso a datos (Hibernate) detr&#x00e1;s de una interface. Combina los patrones
<emphasis>DAO</emphasis> y <emphasis>Sesi&#x00f3;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&#x00e1; pensado para aplicaciones "suficientemente
grandes"; &#x00a1;no es apropiado para una aplicaci&#x00f3;n con cinco tablas!)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>No uses mapeos de asociaci&#x00f3;n ex&#x00f3;ticos.</term>
<listitem>
<para>
Son raros los casos de uso de asociaciones reales muchos-a-muchos. La mayor parte del tiempo
necesitas informaci&#x00f3;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&#x00ed;a de asociaciones son uno-a-muchos y muchos-a-uno, debes ser cuidadoso al usr
cualquier otro estilo de asociaci&#x00f3;n y preguntarte si es realmente necesario.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Prefiere las asociaciones bidireccionales.</term>
<listitem>
<para>
Las asociaciones unidireccionales son m&#x00e1;s dif&#x00ed;ciles de consultar. En una aplicaci&#x00f3;n grande,
casi todas las asociaciones deben ser navegables en ambas direcciones en consultas.
</para>
</listitem>
</varlistentry>
</variablelist>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,403 @@
<chapter id="components">
<title>Mapeo de Componentes</title>
<para>
La noci&#x00f3;n de un <emphasis>componente</emphasis> es reusada en muchos contextos diferentes,
para prop&#x00f3;sitos diferentes, a trav&#x00e9;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&#x00e9;rmino "componente" hace referencia a la noci&#x00f3;n orientada a
objetos de composici&#x00f3;n (no a componentes a nivel de arquitectura). Por ejemplo, podr&#x00ed;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&#x00e9;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&#x00ed;a as&#x00ed;:
</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&#x00ed;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&#x00ed;an dos objetos nombre independientes, s&#x00f3;lo "iguales" en valor.
La sem&#x00e1;ntica de valor nulo de un componente es <emphasis>ad hoc</emphasis>.
Cuando se recargue el objeto contenedor, Hibernate asumir&#x00e1; que si todas las columnas del
componente son nulas, el componente entero es nulo. Esto debe estar bien para la mayor&#x00ed;a
de prop&#x00f3;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&#x00f3;tico. Hibernate est&#x00e1;
concebido para soportar un modelo de objetos granularizado en fino.
</para>
<para>
El elemento <literal>&lt;component&gt;</literal> permite un subelemento
<literal>&lt;parent&gt;</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&#x00e1;n soportadas (por ejemplo,
un array de tipo <literal>Name</literal>). Declara tu colecci&#x00f3;n
de componentes remplazando la etiqueta <literal>&lt;element&gt;</literal>
por una etiqueta <literal>&lt;composite-element&gt;</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>&lt;nested-composite-element&gt;</literal>. Este es un caso bastante
ex&#x00f3;tico - una colecci&#x00f3;n de componentes que a su vez tienen componentes. A esta
altura debes estar pregunt&#x00e1;ndote si una asociaci&#x00f3;n uno-a-muchos es m&#x00e1;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&#x00e1;ntica de persistencia siguen siendo ligeramente diferentes.
</para>
<para>
Por favor observa que un mapeo de elemento compuesto no soporta
propiedades nulables si est&#x00e1;s usando un <literal>&lt;set&gt;</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&#x00f3;lo propiedades no nulas en un elemento compuesto o elegir un
<literal>&lt;list&gt;</literal>, <literal>&lt;map&gt;</literal>,
<literal>&lt;bag&gt;</literal> o <literal>&lt;idbag&gt;</literal>.
</para>
<para>
Un caso especial de un elemento compuesto es un elemento compuesto con un
elemento anidado <literal>&lt;many-to-one&gt;</literal>. Un mapeo como este
te permite mapear columnas extra de una tabla de asociaci&#x00f3;n muchos-a-muchos
a la clase del elemento compuesto. La siguiente es una asociaci&#x00f3;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&#x00f3;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&#x00f3;n bidireccional de la asociaci&#x00f3;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&#x00e1;xis que las asociaciones a otras entidades.
</para>
</sect1>
<sect1 id="components-asmapindex">
<title>Componentes como &#x00ed;ndices de Map</title>
<para>
El elemento <literal>&lt;composite-map-key&gt;</literal> te permite mapear
una clase componente como la clave de un <literal>Map</literal>. Aseg&#x00fa;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&#x00f3;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&#x00ed;gido de Hibernate. Pero de todas formas, h&#x00e1;zlo.</emphasis>
</para>
<para>
No puedes usar un <literal>IdentifierGenerator</literal> para generar claves
compuestas. La aplicaci&#x00f3;n debe, en cambio, asignar sus propios identificadores.
</para>
<para>
Usa la etiqueta <literal>&lt;composite-id&gt;</literal> (con elementos
anidados <literal>&lt;key-property&gt;</literal>) en lugar de la usual
declaraci&#x00f3;n <literal>&lt;id&gt;</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&#x00e1;nea que referencie la tabla de <literal>OrderLine</literal>
es tambi&#x00e9;n compuesta. Debes declarar esto en tus mapeos de otras clases. Una asociaci&#x00f3;n
a <literal>OrderLine</literal> ser&#x00ed;a mapeado as&#x00ed;:
</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>&lt;column&gt;</literal> es una alternativa al
atributo <literal>column</literal> en cualquier sitio.)
</para>
<para>
Una asociaci&#x00f3;n <literal>muchos-a-muchos</literal> a <literal>OrderLine</literal>
tambi&#x00e9;n usa la clave for&#x00e1;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&#x00f3;n de <literal>OrderLine</literal>s en <literal>Order</literal> usar&#x00ed;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>&lt;one-to-many&gt;</literal>, como es usual, no declara columnas.)
</para>
<para>
Si <literal>OrderLine</literal> posee una colecci&#x00f3;n por s&#x00ed; misma, tiene tambi&#x00e9;n
una clave for&#x00e1;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&#x00e1;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&#x00e1;ntica de un mapeo <literal>&lt;dynamic-component&gt;</literal> es &#x00ed;dentica
a la de <literal>&lt;component&gt;</literal>. La ventaja de este tipo de mapeos es
la habilidad para determinar las propiedades reales del bean en tiempo de despliegue,
s&#x00f3;lo con editar el documento de mapeo. La manipulaci&#x00f3;n del documento de mapeo en tiempo
de ejecuci&#x00f3;n es tambi&#x00e9;n posible, usando un analizador DOM. Incluso mejor, puedes acceder
(y cambiar) el metamodelo de tiempo de configuraci&#x00f3;n de Hibernate por medio del objeto
<literal>Configuration</literal>.
</para>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,233 @@
<chapter id="events">
<title>Interceptores y eventos</title>
<para>
Frecuentemente es &#x00fa;til para la aplicaci&#x00f3;n reaccionar a ciertos eventos que ocurran dentro de Hibernate.
Esto permite la implementaci&#x00f3;n de ciertos tipos de funcionalidade gen&#x00e9;rica, y extensi&#x00f3;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&#x00f3;n a la aplicaci&#x00f3;n
permitiendo a &#x00e9;sta &#x00fa;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&#x00f3;n de auditor&#x00ed;a. Por ejemplo, el siguiente <literal>Interceptor</literal> establece
autom&#x00e1;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&#x00ed;a ser especificado cuando se crea la sesi&#x00f3;n:
</para>
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
<para>
Puedes adem&#x00e1;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&#x00e9;n la
arquitectura de <emphasis>eventos</emphasis> de Hibernate3. El sistema de eventos puede ser usado
en adici&#x00f3;n o como un remplazo a los interceptores.
</para>
<para>
Esencialmente todos los m&#x00e9;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&#x00f3;n XML o el paquete <literal>org.hibernate.event</literal>
para la lista completa de tipos de evento definidos). Cuando se hace una petici&#x00f3;n de uno de estos
m&#x00e9;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&#x00e1;brica, estos oyentes implementan
el mismo procesamiento en los que siempre resultan aquellos m&#x00e9;todos. Sin embargo, eres libre de
implementar una personalizaci&#x00f3;n de una de las interfaces oyentes (es decir, el
<literal>LoadEvent</literal> es procesado por la implementaci&#x00f3;n registrada de la interface
<literal>LoadEventListener</literal>), en cuyo caso su implementaci&#x00f3;n ser&#x00ed;a responsable
de procesar cualquier petici&#x00f3;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&#x00fa;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&#x00e1;brica al ser &#x00e9;stos declarados non-final para este prop&#x00f3;sito). Los
oyentes personalizados pueden ser registrados program&#x00e1;ticamente a trav&#x00e9;s del objeto
<literal>Configuration</literal>, o especificados en el XML de configuraci&#x00f3;n de Hibernate
(la declaraci&#x00f3;n declarativa a trav&#x00e9;s del fichero de propiedades no est&#x00e1; soportada).
He aqu&#x00ed; 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&#x00e1;s una entrada de configuraci&#x00f3;n dici&#x00e9;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&#x00e1;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&#x00fa;ltiples elementos <literal>&lt;listener/&gt;</literal>, cada referencia resultar&#x00e1; 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&#x00f3;n program&#x00e1;tica.
</para>
<para>
&#x00bf;Por qu&#x00e9; implementar una interface y definir el tipo espc&#x00ed;fico durante la configuraci&#x00f3;n?
Bueno, una implementaci&#x00f3;n de oyente podr&#x00ed;a implementar m&#x00fa;ltiples interfaces de oyente
de eventos. Teniendo el tipo definido adicionalmente durante la registraci&#x00f3;n lo hace m&#x00e1;s
f&#x00e1;cil para activar o desactivar oyentes personalizados durante la configuraci&#x00f3;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&#x00f3;n. Ahora, Hibernate3 permite que ciertas acciones sean permitidas v&#x00ed;a JACC, y autorizadas v&#x00ed;a
JAAS. Esta en una funcionalidad opcional constru&#x00ed;da encima de la arquitectura de eventos.
</para>
<para>
Primero, debes configurar los oyentes de eventos apropiados, para habilitar el uso de
autorizaci&#x00f3;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&#x00fa;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>

View File

@ -0,0 +1,654 @@
<chapter id="example-mappings">
<title>Ejemplo: Varios Mapeos</title>
<para>
Este cap&#x00ed;tulo muestra mapeos de asociaciones m&#x00e1;s complejos.
</para>
<sect1 id="example-mappings-emp">
<title>Empleador/Empleado</title>
<para>
El siguiente modelo de la relaci&#x00f3;n entre <literal>Employer</literal> y <literal>Employee</literal>
usa una clase de entidad real (<literal>Employment</literal>) para representar la asociaci&#x00f3;n.
Esto se ha hecho esto porque podr&#x00ed;a haber m&#x00e1;s de un per&#x00ed;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&#x00ed; 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&#x00ed; 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&#x00f3;n entre <literal>Work</literal>
y <literal>Author</literal> como una asociaci&#x00f3;n muchos-a-muchos. Elegimos representar la relaci&#x00f3;n entre
<literal>Author</literal> y <literal>Person</literal> como una asociaci&#x00f3;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&#x00f3;n enlazando autores a obras. He aqu&#x00ed; 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&#x00f3;n uno-a-muchos entre <literal>Customer</literal> y <literal>Order</literal>,
pero, &#x00bf;c&#x00f3;mo deber&#x00ed;amos representar <literal>Order</literal> / <literal>LineItem</literal> / <literal>Product</literal>?
He elegido mapear <literal>LineItem</literal> como una clase de asociaci&#x00f3;n representando la
asociaci&#x00f3;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, &#x00ed;tem de l&#x00ed;nea de orden y producto
respectivamente. Adem&#x00e1;s <literal>line_items</literal> act&#x00fa;a como una tabla de asociaci&#x00f3;n enlazando
&#x00f3;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&#x00e1;neos de ejemplo</title>
<para>
Todos estos ejemplos est&#x00e1;n tomados de la bater&#x00ed;a de pruebas de Hibernate.
Encontrar&#x00e1;s muchos otros mapeos de ejemplo &#x00fa;tiles all&#x00ed;. Mira en la carpeta
<literal>test</literal> de la distribuci&#x00f3;n de Hibernate.
</para>
<para>POR HACER: poner palabras alrededor de este material</para>
<sect2 id="example-mappings-typed-onetone">
<title>Asociaci&#x00f3;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&#x00f3;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>

View File

@ -0,0 +1,362 @@
<chapter id="example-parentchild">
<title>Ejemplo: Padre/Hijo</title>
<para>
Una de las primer&#x00ed;simas cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relaci&#x00f3;n de
tipo padre / hijo. Para esto hay dos enfoques diferentes. Por varias razones, el enfoque m&#x00e1;s conveniente,
especialmente para usuarios nuevos, es modelar tanto <literal>Parent</literal> como <literal>Child</literal>
como clases de entidad con una asociaci&#x00f3;n <literal>&lt;one-to-many&gt;</literal> desde <literal>Parent</literal>
a <literal>Child</literal>. (El enfoque alternativo es declarar el <literal>Child</literal> como un
<literal>&lt;composite-element&gt;</literal>.) Ahora, resulta que la sem&#x00e1;ntica por defecto de una asociaci&#x00f3;n
uno a muchos (en Hibernate) es mucho menos cercana a la sem&#x00e1;ntica usual de una relaci&#x00f3;n padre / hijo que aquellas
de un mapeo de elementos compuestos. Explicaremos c&#x00f3;mo usar una <emphasis>asociaci&#x00f3;n uno a muchos bidireccional
con tratamiento en cascada</emphasis> para modelar una relaci&#x00f3;n padre / hijo eficiente y elegantemente.
&#x00a1;No es para nada dif&#x00ed;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&#x00f3;gica de la entidad que las posee; nunca de
las entidades contenidas. &#x00a1;Esta es una distinci&#x00f3;n crucial! Esto tiene las siguientes consecuencias:
</para>
<itemizedlist>
<listitem>
<para>
Cuando se quita / a&#x00f1;ade un objeto desde / a una colecci&#x00f3;n, se incrementa el n&#x00fa;mero de versi&#x00f3;n del
due&#x00f1;o de la colecci&#x00f3;n.
</para>
</listitem>
<listitem>
<para>
Si un objeto que fue quitado de una colecci&#x00f3;n es una instancia de un tipo de valor (por ejemplo, un
elemento compuesto), ese objeta cesar&#x00e1; de ser persistente y su estado ser&#x00e1; completamente quitado de la
base de datos. Asimismo, a&#x00f1;adir una instancia de tipo de valor a la colecci&#x00f3;n causar&#x00e1; que su estado
sea inmediatamente persistente.
</para>
</listitem>
<listitem>
<para>
Por otro lado, si se quita una entidad de una colecci&#x00f3;n (una asociaci&#x00f3;n uno-a-muchos o muchos-a-muchos),
no ser&#x00e1; borrado, por defecto. Este comportamiento es completamente consistente. &#x00a1;Un cambio en el
estado interno de otra entidad no hace desaparecer la entidad asociada! Asimismo, a&#x00f1;adir una entidad a
una colecci&#x00f3;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&#x00f1;adir una entidad a una colecci&#x00f3;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&#x00f3;n padre / hijo. donde
la vida del hijo est&#x00e1; ligada al ciclo de vida del padre.
</para>
</sect1>
<sect1 id="example-parentchild-bidir">
<title>Uno-a-muchos bidirectional</title>
<para>
Sup&#x00f3;n que empezamos con una asociaci&#x00f3;n simple <literal>&lt;one-to-many&gt;</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&#x00e1;semos el siguiente c&#x00f3;digo
</para>
<programlisting><![CDATA[Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();]]></programlisting>
<para>
Hibernate publicar&#x00ed;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&#x00f3;lo ineficiente, sino que adem&#x00e1;s viola cualquier restricci&#x00f3;n <literal>NOT NULL</literal> en la
columna <literal>parent_id</literal>. Podemos reparar la violaci&#x00f3;n de restricci&#x00f3;n de nulabilidad
especificando <literal>not-null="true"</literal> en el mapeo de la colecci&#x00f3;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&#x00f3;n recomendada.
</para>
<para>
El caso subyacente de este comportamiento es que el enlace (la clave for&#x00e1;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&#x00f3;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&#x00e1;s a&#x00f1;adir la propiedad <literal>parent</literal> a la clase <literal>Child</literal>.)
</para>
<para>
Ahora que la entidad <literal>Child</literal> est&#x00e1; gestionando el estado del enlace, le decimos a la
colecci&#x00f3;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&#x00f3;digo podr&#x00ed;a ser usado para a&#x00f1;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, &#x00a1;S&#x00f3;lo se publicar&#x00ed;a un <literal>INSERT</literal> de SQL!
</para>
<para>
Para ajustar un poco m&#x00e1;s las cosas, podr&#x00ed;amos crear un m&#x00e9;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&#x00f3;digo para a&#x00f1;adir un <literal>Child</literal> se ve as&#x00ed;
</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&#x00ed;cita a <literal>save()</literal> es a&#x00fa;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&#x00f3;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&#x00f3;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&#x00e1; <literal>c</literal> de la base de datos; s&#x00f3;lo quitar&#x00e1; el enlace a <literal>p</literal>
(y causar&#x00e1; una violaci&#x00f3;n a una restricci&#x00f3;n <literal>NOT NULL</literal>). Necesitas borrar el hijo
expl&#x00ed;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&#x00f3;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&#x00f3;n especifique <literal>inverse="true"</literal>, el tratamiento en
cascada se procesa a&#x00fa;n al iterar los elementos de colecci&#x00f3;n. De modo que si requieres que un objeto sea
salvado, borrado o actualizado en cascada, debes a&#x00f1;adirlo a la colecci&#x00f3;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&#x00f3;n que hemos cargado un <literal>Parent</literal> en una <literal>Session</literal>, hemos hecho algunos
cambios en una acci&#x00f3;n de UI y deseamos hacer persistentes estos cambios en una nueva sesi&#x00f3;n llamando a
<literal>update()</literal>. El <literal>Parent</literal> contendr&#x00e1; una colecci&#x00f3;n de hijos y, ya que
est&#x00e1; habilitado el tratamiento en cascada, Hibernate necesita saber qu&#x00e9; hijos est&#x00e1;n reci&#x00e9;n instanciados
y cu&#x00e1;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&#x00e1; el identificador y el valor de la propiedad de versi&#x00f3;n/timestamp para determinar cu&#x00e1;les de
los hijos son nuevos. (Ver <xref linkend="objectstate-saveorupdate"/>.) <emphasis>En Hibernate3, no es
m&#x00e1;s necesario especificar un <literal>unsaved-value</literal> expl&#x00ed;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&#x00e1; muy bien para el caso de un identificador generado, pero &#x00bf;qu&#x00e9; de los
identificadores asignados y de los identificadores compuestos? Esto es m&#x00e1;s dif&#x00ed;cil, ya que Hibernate
no puede usar la propiedad identificadora para distinguir entre un objeto reci&#x00e9;n instanciado (con un
identificador asignado por el usuario) y un objeto cargado en una sesi&#x00f3;n previa. En este caso, Hibernate
bien usar&#x00e1; la propiedad de versi&#x00f3;n o timestamp, o bien consultar&#x00e1; realmente el cach&#x00e9; 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&#x00f3;n</title>
<para>
Hay que resumir un poco aqu&#x00ed; y podr&#x00ed;a parecer confuso a la primera vez. Sin embargo, en la pr&#x00e1;ctica,
todo funciona muy agradablemente. La mayor&#x00ed;a de las aplicaciones de Hibernate usan el patr&#x00f3;n
padre / hijo en muchos sitios.
</para>
<para>
Hemos mencionado una alternativa en el primer p&#x00e1;rrafo. Ninguno de los temas anteriores existe en el caso
de los mapeos <literal>&lt;composite-element&gt;</literal>, que tienen exactamente la sem&#x00e1;ntica de una
relaci&#x00f3;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 &#x00fa;nico.
</para>
</sect1>
</chapter>

View File

@ -0,0 +1,429 @@
<chapter id="example-weblog">
<title>Ejemplo: Aplicaci&#x00f3;n de Weblog</title>
<sect1 id="example-weblog-classes">
<title>Clases Persistentes</title>
<para>
Las clases persistentes representan un weblog, y un &#x00ed;tem enviado a un weblog. Van a ser modelados como una
relaci&#x00f3;n padre/hijo est&#x00f1;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&#x00f3;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>

View File

@ -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>&lt;filter-def/&gt;</literal> dentro
de un elemento <literal>&lt;hibernate-mapping/&gt;</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>

View File

@ -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&#x00e1;sicas de mapeo de herencia:
</para>
<itemizedlist>
<listitem>
<para>
tabla por jerarqu&#x00ed;a de clases
</para>
</listitem>
<listitem>
<para>
tabla por subclase
</para>
</listitem>
<listitem>
<para>
tabla por clase concreta
</para>
</listitem>
</itemizedlist>
<para>
En adici&#x00f3;n, Hibernate soporta un cuarto, ligeramente diferente tipo
de polimorfismo:
</para>
<itemizedlist>
<listitem>
<para>
polimorfismo impl&#x00ed;cito
</para>
</listitem>
</itemizedlist>
<para>
Es posible usar estrategias de mapeo diferentes para diferentes
ramificaciones de la misma jerarqu&#x00ed;a de herencia, y entonces usar
polimorfismo impl&#x00ed;cito para conseguir polimorfismo a trav&#x00e9;s de
toda la jerarqu&#x00ed;a. Sin embargo, Hibernate no soporta la mezcla de
mapeos <literal>&lt;subclass&gt;</literal>,
y <literal>&lt;joined-subclass&gt;</literal>
y <literal>&lt;union-subclass&gt;</literal> bajo el mismo elemento
<literal>&lt;class&gt;</literal> ra&#x00ed;z. Es posible mezclar juntas las
estrategias de tabla por jerarqu&#x00ed;a y tabla por subclase, bajo el mismo
elemento <literal>&lt;class&gt;</literal>, combinando los elementos
<literal>&lt;subclass&gt;</literal> y <literal>&lt;join&gt;</literal>
(ver debajo).
</para>
<sect2 id="inheritance-tableperclass" >
<title>Tabla por jerarqu&#x00ed;a de clases</title>
<para>
Sup&#x00f3;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&#x00ed;a se ver&#x00ed;a as&#x00ed;:
</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&#x00f3;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&#x00ed;a as&#x00ed;:
</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&#x00f3;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&#x00f3;n de Hibernate de tabla por subclase
no requiere ninguna columna discriminadora. Otros mapeadores
objeto/relacional usan una implementaci&#x00f3;n diferente de tabla por
subclase que requiere una columna discriminadora de tipo en la tabla
de superclase. Este enfoque es mucho m&#x00e1;s dif&#x00ed;cil de implementar
pero discutiblemente m&#x00e1;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>&lt;subclass&gt;</literal>
y <literal>&lt;join&gt;</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&#x00f3;n opcional <literal>fetch="select"</literal> dice a Hibernate
que no recupere los datos de la subclase <literal>ChequePayment</literal>
usando una uni&#x00f3;n externa (outer join) al consultar la superclase.
</para>
</sect2>
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
<title>Mezclando tabla por jerarqu&#x00ed;a de clases con tabla por subclase</title>
<para>
Puedes incluso mezclar las estrategias de tabla po jerarqu&#x00ed;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&#x00f3;n polim&#x00f3;rfica
a la clase ra&#x00ed;z <literal>Payment</literal> es mapeada usando <literal>&lt;many-to-one&gt;</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&#x00ed;amos ir de dos maneras a la estrategia de mapeo de tabla por clase
concreta. La primera es usar <literal>&lt;union-subclass&gt;</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&#x00e1;n implicadas tres tablas. Cada tabla define columnas para todas las
propiedades de la clase, inccluyendo las propiedades heredadas.
</para>
<para>
La limitaci&#x00f3;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&#x00ed;amos relajar esto en un lanzamiento futuro de Hibernate.)
La estrategia de generador de indentidad no est&#x00e1; permitida en la herencia
de uni&#x00f3;n de subclase, de hecho la semilla de clave primaria tiene que ser
compartida a trav&#x00e9;s de todas las subclases unidas de una jerarqu&#x00ed;a.
</para>
</sect2>
<sect2 id="inheritance-tableperconcreate-polymorphism">
<title>Tabla por clase concreta, usando polimorfismo impl&#x00ed;cito</title>
<para>
Un enfoque alternativo es hacer uso de polimorfismo impl&#x00ed;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&#x00fa;n sitio mencionamos la interface <literal>Payment</literal>
expl&#x00ed;citamente. Nota adem&#x00e1;s que las propiedades de <literal>Payment</literal>
son mapeadas en cada una de las subclases. Si quieres evitar duplicaci&#x00f3;n,
considera usar entidades XML. (por ejemplo,
<literal>[ &lt;!ENTITY allproperties SYSTEM "allproperties.xml"&gt; ]</literal>
en la declaraci&#x00f3;n <literal>DOCTYPE</literal> y <literal>&amp;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&#x00f3;rficas.
</para>
<para>
Para esta estrategia de mapeo, una asociaci&#x00f3;n polim&#x00f3;rfica a <literal>Payment</literal>
es mapeada generalmente usando <literal>&lt;any&gt;</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&#x00ed;cito con otros mapeos de herencia</title>
<para>
Hay una cosa m&#x00e1;s por notar acerca de este mapeo. Ya que las subclases se mapean
cada una en su propio elemento <literal>&lt;class&gt;</literal> (y ya que
<literal>Payment</literal> es s&#x00f3;lo una interface), cada una de las subclases
podr&#x00ed;a ser parte de otra jerarqu&#x00ed;a de herencia! (Y todav&#x00ed;a puedes seguir usando
consultas polim&#x00f3;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&#x00e1;s, no mencionamos a <literal>Payment</literal> expl&#x00ed;citamente.
Si ejecutamos una consulta contra la interface <literal>Payment</literal>
- por ejemplo, <literal>from Payment</literal> - Hibernate devuelve
autom&#x00e1;ticamente instancias de <literal>CreditCardPayment</literal>
(y sus subclases, ya que ellas tambi&#x00e9;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&#x00ed;cito" en
la estrategia de mapeo de tabla por clase concreta. Existen limitaciones
algo menos restrictivas a los mapeos <literal>&lt;union-subclass&gt;</literal>.
</para>
<para>
La siguiente tabla muestra las limitaciones de mapeos de tabla por
clase concreta, y de polmorfismo impl&#x00ed;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&#x00f3;rfica</entry>
<entry>uno-a-uno polim&#x00f3;rfica</entry>
<entry>uno-a-muchos polim&#x00f3;rfica</entry>
<entry>mushos-a-muchos polim&#x00f3;rfica</entry>
<entry><literal>load()/get()</literal> polim&#x00f3;rficos</entry>
<entry>Consultas polim&#x00f3;rficas</entry>
<entry>Uniones polim&#x00f3;rficas</entry>
<entry>Recuperaci&#x00f3;n por uni&#x00f3;n externa (outer join)</entry>
</row>
</thead>
<tbody>
<row>
<entry>tabla por jerarqu&#x00ed;a de clases</entry>
<entry><literal>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry>
<entry><literal>&lt;many-to-many&gt;</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>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry>
<entry><literal>&lt;many-to-many&gt;</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>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal> (para <literal>inverse="true"</literal> solamente)</entry>
<entry><literal>&lt;many-to-many&gt;</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&#x00ed;cito)</entry>
<entry><literal>&lt;any&gt;</literal></entry>
<entry><emphasis>no soportada</emphasis></entry>
<entry><emphasis>no soportada</emphasis></entry>
<entry><literal>&lt;many-to-any&gt;</literal></entry>
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
<entry><literal>from Payment p</literal></entry>
<entry><emphasis>no suportadas</emphasis></entry>
<entry><emphasis>no soportada</emphasis></entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,478 @@
<chapter id="persistent-classes" revision="2">
<title>Clases Persistentes</title>
<para>
Clases presistentes son clases en una aplicaci&#x00f3;n que implementan las
entidades del problema de negocio (por ejemplo, Customer y Order en una
aplicaci&#x00f3;n de comercio electr&#x00f3;nico). No todas las instancias de una
clase persistente se considera que est&#x00e9;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&#x00e9;n
conocidas como el modelo de programaci&#x00f3;n de Viejas Clases Java Planas
(Plain Old Java Object o POJO). Sin embargo, ninguna de estas reglas son
requerimientos r&#x00ed;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 &#x00e1;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&#x00ed;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&#x00ed; 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&#x00fa;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&#x00f3;n de proxies en tiempo de ejecuci&#x00f3;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&#x00ed;a llamarse cualquierCosa, y su tipo podr&#x00ed;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&#x00f3;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&#x00e1; disponible s&#x00f3;lo para clases que
declaran una propiedad identificadora:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Reasociaci&#x00f3;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&#x00fa;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&#x00f3;n
de una interface que declare todos los m&#x00e9;todos p&#x00fa;blicos.
</para>
<para>
Puedes persistir con Hibernate clases <literal>final</literal> que no implementen una
interface, pero no ser&#x00e1;s capaz de usar proxies para recuperaci&#x00f3;n perezosa
de asociaciones, lo que limitar&#x00e1; tus opciones para afinar el rendimiento.
</para>
<para>
Debes tambi&#x00e9;n evitar declarar m&#x00e9;todos <literal>public final</literal>
en clases non-final. Si quieres usar una clase con un m&#x00e9;todo <literal>public
final</literal>, debes deshabilitar expl&#x00ed;citamente el uso de proxies estableciendo
<literal>lazy="false"</literal>.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-accessors" revision="2">
<title>Declara m&#x00e9;todos de acceso y modificaci&#x00f3;n para los campos persistentes (opcional)</title>
<para>
<literal>Cat</literal> declara m&#x00e9;todos de acceso para todos sus campos persistente.
Muchas otras herramientas ORM persisten directamente variables de instancia. Creemos que
es mejor proveer una indirecci&#x00f3;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&#x00e9;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&#x00fa;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&#x00e9;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&#x00f3;n de instancias separadas.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos) y
identidad Java s&#x00f3;lo dentro del &#x00e1;mbito de una sesi&#x00f3;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&#x00e1;ntica significativa de <literal>Set</literal>s.
</para>
<para>
La forma m&#x00e1;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&#x00f3;lo tendremos un elemento en el <literal>Set</literal>).
Desafortunadamente, no podemos usar este enfoque con identificadores generados! Hibernate s&#x00f3;lo
asignar&#x00e1; valores identificadores a objetos que son persistentes, una instancia reci&#x00e9;n
creada no tendr&#x00e1; ning&#x00fa;n valor identificador! Adem&#x00e1;s, si una instancia no est&#x00e1;
salvada y est&#x00e1; actualmente en un <literal>Set</literal>, salvarla asignar&#x00e1; un
valor identificador al objeto. Si <literal>equals()</literal> and <literal>hashCode()</literal>
est&#x00e1;n basados en el valor identificador, el c&#x00f3;digo hash podr&#x00ed;a cambiar,
rompiendo el contrato de <literal>Set</literal>. Ver el sitio web de Hibernate para una
discusi&#x00f3;n completa de este problema. Observa que esto no es una incidencia de Hibernate,
sino la sem&#x00e1;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&#x00e9;todo <literal>equals()</literal>
compara s&#x00f3;lo las propiedades que forman la clave de negocio, una clave que podr&#x00ed;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&#x00f3;lida como
una clave primaria candidata de base de datos (ver
<xref linkend="transactions-basics-identity"/>). Las propiedades inmutables o
&#x00fa;nicas son usualmente buenas candidatas para una clave de negocio.
</para>
</sect1>
<sect1 id="persistent-classes-dynamicmodels">
<title>Modelos din&#x00e1;micos</title>
<para>
<emphasis>Ten en cuenta que las siguientes funcionalidades est&#x00e1;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&#x00f3;n. Hibernate
soporta adem&#x00e1;s modelos din&#x00e1;micos (usando <literal>Map</literal>s de
<literal>Map</literal>s en tiempo de ejecuci&#x00f3;n) y la representaci&#x00f3;n
de entidades como &#x00e1;rboles de DOM4J. Con este enfoque no escribes clases
persistentes, s&#x00f3;lo ficheros de mapeo.
</para>
<para>
Por defecto, Hibernate funciona en modo POJO normal. Puedes establecer una
representaci&#x00f3;n de entidad por defecto para una <literal>SessionFactory</literal>
en particular usando la opci&#x00f3;n de configuraci&#x00f3;n
<literal>default_entity_mode</literal>
(ver <xref linkend="configuration-optional-properties"/>).
</para>
<para>
Los siguientes ejemplos demuestran la representaci&#x00f3;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&#x00f3;n puede
ser adem&#x00e1;s una entidad din&#x00e1;mica en vez de un POJO.
</para>
<para>
Despu&#x00e9;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&#x00f3;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&#x00e1;mico es r&#x00e1;pido tiempo de ciclo
de prototipado sin la necesidad de implementaci&#x00f3;n de clases de entidad.
Sin embargo, pierdes chequeo de tipos en tiempo de compilaci&#x00f3;n y
muy probablemente tratar&#x00e1;s con muchas excepciones en tiempo de ejecuci&#x00f3;n.
Gracias al mapeo de Hibernate, el esquema de base de datos puede estar facilmente
sano y normalizado, permitiendo agregar una implementaci&#x00f3;n apropiada del
modelo de dominio m&#x00e1;s tarde.
</para>
<para>
Los modos de representaci&#x00f3;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&#x00e1; 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&#x00f3;n
JDBC, transacci&#x00f3;n y otra informaci&#x00f3;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&#x00e9;n dejar el manejo
de la transacci&#x00f3;n y de la conexi&#x00f3;n a la unidad de trabajo primaria.
</para>
<para>
Puede encontrarse m&#x00e1;s informaci&#x00f3;n sobre las capacidades de
representaci&#x00f3;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>

View File

@ -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&#x00e1;tico</title>
<subtitle>Documentaci&#x00f3;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&#x00f3;n traducida del ingl&#x00e9;s de
la documentaci&#x00e9;n de referencia de Hibernate. La versi&#x00f3;n
traducida puede no estar actualizada! Sin embargo, las diferencias
deber&#x00ed;an ser s&#x00f3;lo menores. Consulta la documentaci&#x00f3;n
de referencia en ingl&#x00e9;s si est&#x00e1;s perdiendo informaci&#x00f3;n
o encuentras alg&#x00fa;n error de traducci&#x00f3;n. Si quieres colaborar con
una traducci&#x00f3;n en particular, cont&#x00e1;ctanos en la lista de correo
de desarrolladores de Hibernate.
</para>
<para>
Traductor(es): Bernardo Antonio Buffa Colom&#x00e9; &lt;kreimer@bbs.frc.utn.edu.ar&gt;
<!--,
Antonio L&#x00f3;pez Gota &lt;antoniogota@gmail.com&gt; -->
</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
@ -108,8 +55,8 @@
<para> <para>
Dale una mirada al directorio <literal>eg/</literal> en la distribuci&#x00f3;n Dale una mirada al directorio <literal>eg/</literal> en la distribuci&#x00f3;n
de Hibernate, contiene una aplicaci&#x00f3;n independiente simple. de Hibernate, contiene una aplicaci&#x00f3;n independiente simple.
Copia tu driver JDBC al directorio <literal>lib/</literal> y edita Copia tu driver JDBC al directorio <literal>lib/</literal> y edita
<literal>etc/hibernate.properties</literal>, especificando los valores <literal>etc/hibernate.properties</literal>, especificando los valores
correctos para tu base de datos. Desde l&#x00ed;nea de comandos en el correctos para tu base de datos. Desde l&#x00ed;nea de comandos en el
directorio de la distribuci&#x00f3;n, tipea <literal>ant eg</literal> directorio de la distribuci&#x00f3;n, tipea <literal>ant eg</literal>
(usando Ant), o bajo Windows, tipea <literal>build eg</literal>. (usando Ant), o bajo Windows, tipea <literal>build eg</literal>.
@ -162,42 +109,4 @@
JBoss Professional. JBoss Professional.
</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>

View File

@ -0,0 +1,431 @@
<chapter id="querycriteria">
<title>Consultas por Criterios</title>
<para>
Acompa&#x00f1;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&#x00e1;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&#x00e9;todos de f&#x00e1;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&#x00f3;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&#x00e1; 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&#x00e1;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&#x00f3;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>
&#x00a1;Observa que las colecciones de gatitos tenidas por las instancias de <literal>Cat</literal> devueltas
por las dos consultas previas <emphasis>no</emphasis> est&#x00e1;n prefiltradas por los criterios! Si deseas
recuperar s&#x00f3;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&#x00f3;n din&#x00e1;mica de asociaciones</title>
<para>
Puedes especificar la sem&#x00e1;ntica de recuperaci&#x00f3;n de asociaciones en tiempo de ejecuci&#x00f3;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&#x00e1; tanto <literal>mate</literal> como <literal>kittens</literal> por
uni&#x00f3;n exterior (outer join). Ver <xref linkend="performance-fetching"/> para m&#x00e1;s informaci&#x00f3;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&#x00f3;n, los identificadores y las asociaciones son ignorados. Por defecto,
las propiedades valuadas a nulo son exclu&#x00ed;das.
</para>
<para>
Puedes ajustar c&#x00f3;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&#x00f3;n y agrupamiento</title>
<para>
La clase <literal>org.hibernate.criterion.Projections</literal> es una f&#x00e1;brica de instancias de
<literal>Projection</literal>. Aplicamos una proyecci&#x00f3;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&#x00ed;cito en una consulta por criterios.
Ciertos tipos de proyecciones son definidos para ser <emphasis>proyecciones agrupadas</emphasis>,
que adem&#x00e1;s aparecen en la cl&#x00e1;usula SQL <literal>group by</literal>.
</para>
<para>
Puede opcionalmente asignarse un alias a una proyecci&#x00f3;n, de modo que el valor proyectado pueda
ser referido en restricciones u ordenamientos. Aqu&#x00ed; 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&#x00e9;todos <literal>alias()</literal> y <literal>as()</literal> simplemente envuelven una instancia
de proyecci&#x00f3;n en otra instancia de <literal>Projection</literal> con alias. Como un atajo, puedes asignar
un alias cuando agregas la proyecci&#x00f3;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&#x00e9;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 &#x00e1;mbito de una sesi&#x00f3;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&#x00e9;n una <literal>DetachedCriteria</literal> puede usarse para expresar una subconsulta.
Las instancias de Criterion implicando subconsultas pueden obtenerse v&#x00ed;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&#x00ed;a de consultas, incluyendo las consultas por criterios, el cach&#x00e9; de consulta no es
muy eficiente, debido a que la invalidaci&#x00f3;n del cach&#x00e9; de consulta ocurre demasiado frecuentemente.
Sin embargo, hay un tipo especial de consulta donde podemos optimizar el algoritmo de invalidaci&#x00f3;n
de cach&#x00e9;: búsquedas por una clave natural constante. En algunas aplicaciones, este tipo de consulta,
ocurre frecuentemente. La API de criterios brinda especial provisi&#x00f3;n para este caso de uso.
</para>
<para>
Primero, debes mapear la clave natural de tu entidad usando
<literal>&lt;natural-id&gt;</literal>, y habilitar el uso del cach&#x00e9; 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&#x00e1; pensada para uso con entidades con claves naturales
<emphasis>mutable</emphasis>.
</para>
<para>
Seguido, habilita el cach&#x00e9; de consulta de Hibernate.
</para>
<para>
Ahora, <literal>Restrictions.naturalId()</literal> nos permite hacer uso de el algoritmo de cach&#x00e9;
m&#x00e1;s eficiente.
</para>
<programlisting><![CDATA[session.createCriteria(User.class)
.add( Restrictions.naturalId()
.set("name", "gavin")
.set("org", "hb")
).setCacheable(true)
.uniqueResult();]]></programlisting>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,477 @@
<chapter id="querysql" revision="2">
<title>SQL Nativo</title>
<para>
Puedes tambi&#x00e9;n expresar consultas en el dialecto SQL nativo de tu base de datos. Esto es &#x00fa;til si quieres
utilizar aspectos espec&#x00ed;ficos de base de datos tal como consejos (hints) de consulta o la palabra clave
<literal>CONNECT</literal> en Oracle. Provee adem&#x00e1;s una clara ruta de migraci&#x00f3;n desde una aplicaci&#x00f3;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&#x00f3;n, actualizaci&#x00f3;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&#x00e9;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&#x00e9;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&#x00ed;a devolver un valor escalar simple o una combinaci&#x00f3;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&#x00f3;n <literal>{cat.*}</literal> usada arriba es un atajo para "todas las propiedades".
Alternativamente, puedes listar las columnas expl&#x00ed;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&#x00f3;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&#x00ed;amos incluso usar los alias de propiedad en la
cl&#x00e1;usula where si quisieramos.
</para>
<para>
La sint&#x00e1;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&#x00ed;citamente, &#x00a1;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>&lt;return-join&gt;</literal> y <literal>&lt;load-collection&gt;</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>&lt;return-scalar&gt;</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&#x00ed;citamente nombres de columna/alias</title>
<para>
Con <literal>&lt;return-property&gt;</literal> puedes decirle expl&#x00ed;citamente a Hibernate qu&#x00e9;
alias de columna usar, en vez de usar la sint&#x00e1;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>&lt;return-property&gt;</literal> tambi&#x00e9;n trabaja con m&#x00fa;ltiples columnas. Esto resuelve una
limitaci&#x00f3;n de la sint&#x00e1;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>&lt;return-property&gt;</literal> en combinaci&#x00f3;n con
la sint&#x00e1;xis <literal>{}</literal> para inyecci&#x00f3;n, permitiendo a los usuarios elejir c&#x00f3;mo quieren
referirse a las columnas y propiedades.
</para>
<para>
Si tu mapeo tiene un discriminador debes usar <literal>&lt;return-discriminator&gt;</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&#x00ed;a procedimientos almacenados. Los procedimientos
almacenados deben devolver un conjunto resultado como el primer par&#x00e1;metro de salida para ser
capaces de funcionar con Hibernate. Un ejemplo de uno procedimiento almacenado en Oracle 9
o superior es as&#x00ed;:
</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&#x00f3;lo devuelven escalares y entidades.
No est&#x00e1;n soportados <literal>&lt;return-join&gt;</literal> y <literal>&lt;load-collection&gt;</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&#x00fa;n quisieras usar estos procedimientos
tendr&#x00ed;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&#x00e1;nticas/sint&#x00e1;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(&lt;parameters&gt;) }</literal> o
<literal>{ ? = call procName }</literal> (esto es m&#x00e1;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&#x00e1;n m&#x00fa;ltiples conjuntos resultados y cuentas de actualizaci&#x00f3;n, Hibernate iterar&#x00e1;
los resultados y tomar&#x00e1; el primer resultado que sea un conjunto resultado como su valor
a devolver. Todo lo dem&#x00e1;s ser&#x00e1; descartado.
</para>
</listitem>
<listitem>
<para>
Si habilitas <literal>SET NOCOUNT ON</literal> en tu procedimiento ser&#x00e1; probablemente m&#x00e1;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&#x00f3;n (insertsql,
deletesql, updatesql, etc.). Las etiquetas de mapeo <literal>&lt;sql-insert&gt;</literal>,
<literal>&lt;sql-delete&gt;</literal>, y <literal>&lt;sql-update&gt;</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&#x00e1;, por supuesto, la portabilidad de tu mapeo si usas SQL
espec&#x00ed;fico de la base de datos.
</para>
<para>
Los procedimientos almacenados son soportados si est&#x00e1; 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&#x00e1;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&#x00f3;n para el nivel
<literal>org.hibernate.persister.entity</literal>. Con este nivel habilitado, Hibernate
imprimir&#x00e1; el SQL est&#x00e1;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&#x00e1;n el sql est&#x00e1;tico generado por Hibernate.)
</para>
<para>
Los procedimientos almacenados son, en la mayor&#x00ed;a de los casos (l&#x00e9;ase, mejor hacerlo que no hacerlo),
obligados a devolver el n&#x00fa;mero de filas insertadas/actualizadas/borradas, ya que Hibernate tiene algunas
comprobaciones en tiempo de ejecuci&#x00f3;n del &#x00e9;xito de la sentencia. Hibernate siempre registra el primer
par&#x00e1;metro de la sentencia como un par&#x00e1;metro de salida num&#x00e9;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&#x00e9;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&#x00f3;lo una declaraci&#x00f3;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&#x00ed;as incluso definir un cargador de entidades que cargue una colecci&#x00f3;n por
recuperaci&#x00f3;n por uni&#x00f3;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>

View File

@ -0,0 +1,666 @@
<chapter id="quickstart">
<title>Comienzo r&#x00e1;pido con Tomcat</title>
<sect1 id="quickstart-intro" revision="2">
<title>Empezando con Hibernate</title>
<para>
Este tutorial explica una instalaci&#x00f3;n de Hibernate con el
contenedor de servlets Apache Tomcat (hemos usado la versi&#x00f3;n 4.1,
las diferencias con la 5.0 deben ser m&#x00ed;nimas) para una aplicaci&#x00f3;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&#x00f3;lo una cuesti&#x00f3;n
de cambiar la configuraci&#x00f3;n del dialecto SQL de Hibernate y las
propiedades de conexi&#x00f3;n.
</para>
<para>
Primero, tenemos que copiar todas las bibliotecas requeridas a la instalaci&#x00f3;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&#x00fa;squeda
de bibliotecas global (<literal>TOMCAT/common/lib</literal>) como tambi&#x00e9;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&#x00e1;s dentro de la ruta del cargador de clases global
en Tomcat, o tendr&#x00e1;s problemas con varias herramientas, incluyendo
Log4J, commons-logging y otras. Siempre usa el classpath de contexto para
cada aplicaci&#x00f3;n web, esto es, copia las bibliotecas a
<literal>WEB-INF/lib</literal> y tus propias clases y ficheros de
configuraci&#x00f3;n/propiedades a <literal>WEB-INF/classes</literal>.
Ambos directorios est&#x00e1;n a nivel del classpath de contexto por defecto.
</para>
</listitem>
<listitem>
<para>
Hibernate est&#x00e1; 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&#x00f3;n. Hibernate requiere algunas
bibliotecas de terceros en tiempo de ejecuci&#x00f3;n; &#x00e9;stas vienen
inclu&#x00ed;das con la distribuci&#x00f3;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&#x00f3;n
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
antlr (requerida)
</entry>
<entry>
Hibernate usa ANTLR para producir analizadores de consultas,
esta biblioteca tambi&#x00e9;n se necesita en tiempo de ejecuci&#x00f3;n.
</entry>
</row>
<row>
<entry>
dom4j (requerida)
</entry>
<entry>
Hibernate usa dom4j para analizar ficheros de configuraci&#x00f3;n
XML y ficheros de metadatos de mapeo XML.
</entry>
</row>
<row>
<entry>
CGLIB, asm (requerida)
</entry>
<entry>
Hibernate usa la biblioteca de generaci&#x00f3;n de c&#x00f3;digo
para aumentar las clases en tiempo de ejecuci&#x00f3;n
(en combinaci&#x00f3;n con reflecci&#x00f3;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&#x00e9; para
el cach&#x00e9; de segundo nivel. EHCache es el provedor de
cach&#x00e9; por defecto si no se cambia en la configuraci&#x00f3;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&#x00e1; disponible en el directorio de
bibliotecas del contexto, Commons Logging usar&#x00e1; Log4J
y la configuraci&#x00f3;n <literal>log4j.properties</literal>
en el classpath de contexto. Un fichero de propiedades de ejemplo
para Log4J se incluye con la distribuci&#x00f3;n de Hibernate.
As&#x00ed; que copia log4j.jar y el fichero de configuraci&#x00f3;n
(de <literal>src/</literal>) a tu classpath de contexto si quieres
ver que ocurre tras esc&#x00e9;nas.
</entry>
</row>
<row>
<entry>
¿Requerida o no?
</entry>
<entry>
Echa una mirada al fichero <literal>lib/README.txt</literal> en la
distribuci&#x00f3;n de Hibernate. Esta es una lista actualizada
de bibliotecas de terceros distribu&#x00ed;das con Hibernate.
Encontrar&#x00e1;s listadas ah&#x00ed; todas las bibliotecas
requeridas y opcionales (Observa que "buildtame required" significa
aqu&#x00ed; para la construcci&#x00f3;n de Hibernate, no de tu
aplicaci&#x00f3;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&#x00e1;
conexiones JDBC en pool (usando su funcionalidad prefabricada de pooling DBCP).
Hibernate pide esas conexiones a trav&#x00e9;s de JNDI. Alternativamente,
puedes dejar que Hibernate maneje el pool de conexiones. Tomcat liga su pool
de conexiones a JNDI; agregamos una declaraci&#x00f3;n de recurso al fichero
de configuraci&#x00f3;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&#x00e9;n ir m&#x00e1;s all&#x00e1; y crear
ahora un servlet simple que tenga un m&#x00e9;todo <literal>process()</literal>
vac&#x00ed;o.
</para>
<para>
Tomcat provee ahora conexiones a trav&#x00e9;s de JNDI en
<literal>java:comp/env/jdbc/quickstart</literal>. Si tienes problemas obteniendo
el pool de conexiones en ejecuci&#x00f3;n, refi&#x00e9;rete a la documentaci&#x00f3;n
de Tomcat. Si obtienes mensajes de excepci&#x00f3;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&#x00f3;ximo paso es configurar Hibernate. Hibernate tiene que saber c&#x00f3;mo
debe obtener conexiones JDBC. Usamos la configuraci&#x00f3;n de Hibernate basada en XML.
El otro enfoque, usando un ficheros de propiedad, es casi equivalente pero pierde unas
pocas funcionalidades que s&#x00ed; permite la sintaxis XML. El fichero de configuraci&#x00f3;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&#x00e9; dialecto SQL de base de datos se usa y d&#x00f3;nde obtener
conexiones JDBC (declarando la direcci&#x00f3;n JNDI del pool ligado a
Tomcat). El dialecto es una configuraci&#x00f3;n requerida, las bases de
datos difieren en su interpretaci&#x00f3;n del "est&#x00e1;ndar" de SQL.
Hibernate cuidar&#x00e1; de las diferencias y viene con dialectos inclu&#x00ed;dos
para todas las principales bases de datos comerciales y de c&#x00f3;digo
abierto.
</para>
<para>
Una <literal>SessionFactory</literal> es el concepto de Hibernate
de un almac&#x00e9;n de datos solo. Pueden usarse m&#x00fa;ltiples
bases de datos creando m&#x00fa;ltiples ficheros de configuraci&#x00f3;n
XML y creando m&#x00fa;ltiples objetos <literal>Configuration</literal>
y <literal>SessionFactory</literal> en tu aplicaci&#x00f3;n.
</para>
<para>
El &#x00fa;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&#x00f3;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&#x00ed;a m&#x00e9;todos getter y setter,
encapsulando la representaci&#x00f3;n interna de la interfaz publicamente
visible (Hibernate puede tambi&#x00e9;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&#x00e1; 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&#x00f3;lo
internamente, pero perder&#x00ed;amos algo de la flexibilidad en nuestra
arquitectura de aplicaci&#x00f3;n.
</para>
<para>
No tiene que implementarse ninguna interface especial para las clases persistentes
ni tienes que subclasear de una clase persistente ra&#x00ed;z en especial. Hibernate
tampoco requiere ning&#x00fa;n procesamiento en tiempo de construcci&#x00f3;n,
como manipulaci&#x00f3;n del byte-code. Se basa solamente en reflecci&#x00f3;n de Java
y aumentaci&#x00f3;n de clases en tiempo de ejecuci&#x00f3;n (a trav&#x00e9;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&#x00f3;n de clases persistentes y el mapeo de
propiedades (a columnas y relaciones de claves for&#x00e1;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&#x00f3;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&#x00f3;n).
Usamos el generador UUID (recomendado s&#x00f3;lo para pruebas, pues deben
preferirse las claves enteras delegadas generadas por la base de datos) y
tambi&#x00e9;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&#x00e1;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&#x00f3;n expl&#x00ed;cita de columna de base de datos. Esto es
especialmente &#x00fa;til cuando el esquema de base de datos es generado
autom&#x00e1;ticamente (como sentencias DDL de SQL) desde la declaraci&#x00f3;n
de mapeo con la herramienta <emphasis>SchemaExport</emphasis> de Hibernate.
Todas las dem&#x00e1;s propiedades son mapeadas usando la configuraci&#x00f3;n
por defecto de Hibernate, que es lo que necesitas la mayor&#x00ed;a del tiempo.
La tabla <literal>CAT</literal> en la base de datos se ve as&#x00ed; 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&#x00f3;n de tablas, restricciones
personalizadas de tipo de columnas, restricciones de unicidad e &#x00ed;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&#x00f3;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&#x00f3;nde creamos la <literal>SessionFactory</literal> y c&#x00f3;mo
accedemos a ella en nuestra aplicaci&#x00f3;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&#x00e9;n que no debes mantenerla en una variable de instancia
en tus servlets, sino en alguna otro sitio. Adem&#x00e1;s, necesitamos alg&#x00fa;n
tipo de <emphasis>Singleton</emphasis>, de modo que podamos acceder a la
<literal>SessionFactory</literal> f&#x00e1;cilmente en el c&#x00f3;digo de
aplicaci&#x00f3;n. El siguiente enfoque mostrado resuelve ambos problemas:
configuraci&#x00f3;n de arranque y f&#x00e1;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&#x00f3;lo cuida de la <literal>SessionFactory</literal>
con su inicializador static, sino que adem&#x00e1;s tiene una variable
<literal>ThreadLocal</literal> que tiene la <literal>Session</literal>
para la hebra actual. Aseg&#x00fa;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&#x00e1;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&#x00e1; completo. Un ejemplo en el m&#x00e9;todo
<literal>process()</literal> de tu servlet podr&#x00ed;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&#x00f3;n de base de datos
ocurre dentro de una transacci&#x00f3;n que a&#x00ed;sla las operaciones
de base de datos (incluso operaciones de s&#x00f3;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&#x00f3;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&#x00e1;s la <literal>Session</literal>
actual de esta hebra. Tienes que asegurarte que la <literal>Session</literal>
sea cerrada despu&#x00e9;s que se complete tu unidad-de-trabajo, ya sea en
c&#x00f3;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&#x00f3;n es la
f&#x00e1;cil inicializaci&#x00f3;n perezosa: la <literal>Session</literal> todav&#x00ed;a
est&#x00e1; 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&#x00e9;todos que pueden ser usados para traer
objetos desde la base de datos. La forma m&#x00e1;s flexible es usando
el Lenguaje de Consulta de Hibernate (Hibernate Query Language o HQL),
que es una extensi&#x00f3;n orientada a objetos de SQL f&#x00e1;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&#x00e9;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&#x00e1;metros para toda la comunicaci&#x00f3;n SQL con la base de datos.
Tambi&#x00e9;n puedes usar la funcionalidad de consulta SQL directa de Hibernate
u obtener una conexi&#x00f3;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&#x00f1;amos solamente la superficie de Hibernate en este peque&#x00f1;o
tutorial. Por favor, observa que no incluimos ning&#x00fa;n c&#x00f3;digo
espec&#x00ed;fico de servlet en nuestros ejemplos. Tienes que crear un servlet
por t&#x00ed; mismo e insertar el c&#x00f3;digo de Hibernate como lo veas
ubicado.
</para>
<para>
Ten en mente que Hibernate, como capa de acceso a datos, est&#x00e1; firmemente
integrado dentro de tu aplicaci&#x00f3;n. Usualmente, todas las otras capas dependen
del mecanismo de persistencia. Aseg&#x00fa;rate de entender las implicaciones
de este dise&#x00f1;o.
</para>
<para>
Para un ejemplo de aplicaci&#x00f3;n m&#x00e1;s compleja, ver
http://caveatemptor.hibernate.org/ y echa una mirada a los
otros tutoriales con links en http://www.hibernate.org/Documentation
</para>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,459 @@
<chapter id="toolsetguide" revision="2">
<title>Gu&#x00ed;a del Conjunto de Herramientas</title>
<para>
La ingenier&#x00ed;a de ida y vuelta con Hibernate es posible usando un conjunto de plugins de Eclipse,
herramientas de l&#x00ed;nea de comandos, as&#x00ed; como tareas de Ant.
</para>
<para>
Las <emphasis>Herramientas de Hibernate</emphasis> actualmente incluyen plugins para la IDE de
Eclipse as&#x00ed; como tareas de Ant para la ingenier&#x00ed;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&#x00f3;n
y resaltado de sint&#x00e1;xis. Soporta tambi&#x00e9;n autocompleci&#x00f3;n sem&#x00e1;ntica de nombres de clases y nombres de
campos/propiedades, haci&#x00e9;ndolo mucho m&#x00e1;s vers&#x00e1;til que un editor de XML normal.
</para></listitem>
<listitem><para>
<emphasis>Consola:</emphasis> La consola es una nueva vista en Eclipse. Adem&#x00e1;s de la vista de
&#x00e1;rbol de tus configuraciones de consola, tienes tambi&#x00e9;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&#x00e1;pidamente ficheros de configuraci&#x00f3;n de Hibernate
(cfg.xml), o incluso puedes haceruna ingenier&#x00ed;a inversa completa de un esquema de base de datos existente
en ficheros de c&#x00f3;digo de POJO y ficheros de mapeo de Hibernate. El asistente de ingenier&#x00ed;a inversa soporta
plantillas personalizables.
</para></listitem>
<listitem><para>
<emphasis>Tareas de Ant:</emphasis>
</para></listitem>
</itemizedlist>
<para>
Por favor refi&#x00e9;rete al paquete <emphasis>Herramientas de Hibernate</emphasis> y su documentaci&#x00f3;n para
m&#x00e1;s informaci&#x00f3;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&#x00e9;n conocido como <literal>hbm2ddl</literal>.
</para>
<sect1 id="toolsetguide-s1" revision="2">
<title>Generaci&#x00f3;n autom&#x00e1;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&#x00e1;neas) para las tablas de entidades y
colecciones. Las tablas y secuencias tambi&#x00e9;n son creadas para los generadores de identificadores mapeados.
</para>
<para>
<emphasis>Debes</emphasis> especificar un <literal>Dialecto</literal> SQL v&#x00ed;a la propiedad
<literal>hibernate.dialect</literal> al usar esta herramienta, ya que el DDL es altamente espec&#x00ed;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&#x00f1;o de una columna. (O, para tipos de datos
num&#x00e9;ricos/decimales, la precisi&#x00f3;n.)
</para>
<para>
Algunas etiquetas tambi&#x00e9;n aceptan un atributo <literal>not-null</literal> (para generar una restricci&#x00f3;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 &#x00ed;ndice
para esa columna. Se puede usar un atributo <literal>unique-key</literal> para agrupar columnas en una
restricci&#x00f3;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&#x00f3;n, s&#x00f3;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&#x00ed;en un elemento hijo <literal>&lt;column&gt;</literal>.
Esto es particularmente &#x00fa;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&#x00f3;n de restricci&#x00f3;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&#x00f3;n</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>length</literal></entry>
<entry>number</entry>
<entry>largo de columna/precisi&#x00f3;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&#x00f3;n de unicidad</entry>
</row>
<row>
<entry><literal>index</literal></entry>
<entry><literal>index_name</literal></entry>
<entry>especifica el nombre de un &#x00ed;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&#x00f3;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&#x00f3;n de clave for&#x00e1;nea generada por una
asociaci&#x00f3;n, &#x00fa;salo en los elementos de mapeo &lt;one-to-one&gt;, &lt;many-to-one&gt;,
&lt;key&gt;, y &lt;many-to-many&gt;. Nota que los lados
<literal>inverse="true"</literal> no ser&#x00e1;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&#x00f3;lo atributo del elemento
<literal>&lt;column&gt;</literal>)
</entry>
</row>
<row>
<entry><literal>check</literal></entry>
<entry>expresi&#x00f3;n SQL</entry>
<entry>
crea una restricci&#x00f3;n de comprobaci&#x00f3;n SQL en columna o tabla
</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
El elemento <literal>&lt;comment&gt;</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&#x00e9; soportado).
</para>
</sect2>
<sect2 id="toolsetguide-s1-3">
<title>Ejecutando la herramienta</title>
<para>
La herramienta <literal>SchemaExport</literal> escribe un gui&#x00f3;n DDL a la salida est&#x00e1;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&#x00ed;nea de Comandos de <literal>SchemaExport</literal></title>
<tgroup cols="2">
<colspec colwidth="1.5*"/>
<colspec colwidth="2*"/>
<thead>
<row>
<entry>Opci&#x00f3;n</entry>
<entry>Descripci&#x00f3;n</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>--quiet</literal></entry>
<entry>no enviar a salida est&#x00e1;ndar el gui&#x00f3;n</entry>
</row>
<row>
<entry><literal>--drop</literal></entry>
<entry>s&#x00f3;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&#x00f3;n ddl a un fichero</entry>
</row>
<row>
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
<entry>lee la configuraci&#x00f3;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&#x00f3;n</entry>
</row>
<row>
<entry><literal>--delimiter=x</literal></entry>
<entry>establece un delimitador de fin de l&#x00ed;nea para el gui&#x00f3;n</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Puedes incluso encajar <literal>SchemaExport</literal> en tu aplicaci&#x00f3;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>&lt;property&gt;</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&#x00f3;n de SchemaExport</title>
<tgroup cols="2">
<colspec colwidth="1.5*"/>
<colspec colwidth="2*"/>
<thead>
<row>
<entry>Nombre de Propiedad</entry>
<entry>Descripci&#x00f3;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&#x00f1;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&#x00f3;n de construcci&#x00f3;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&#x00e1; 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&#x00e1; 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&#x00ed;nea de Comandos de <literal>SchemaUpdate</literal></title>
<tgroup cols="2">
<colspec colwidth="1.5*"/>
<colspec colwidth="2*"/>
<thead>
<row>
<entry>Opci&#x00f3;n</entry>
<entry>Descripci&#x00f3;n</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>--quiet</literal></entry>
<entry>no enviar a salida est&#x00e1;ndar el gui&#x00f3;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&#x00f3;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&#x00f3;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>

View File

@ -0,0 +1,925 @@
<chapter id="transactions" revision="1">
<title>Transacciones y Concurrencia</title>
<para>
El punto m&#x00e1;s importante sobre Hibernate y el control de concurrencia es que muy f&#x00e1;cil
de comprender. Hibernate usa directamente conexiones JDBC y recursos JTA sin agregar
ning&#x00fa;n comportamiento de bloqueo adicional. Recomendamos altamente que gastes algo de
tiempo con la especificaci&#x00f3;n de JDBC, ANSI, y el aislamiento de transacciones de tu sistema
de gesti&#x00f3;n de base de datos. Hibernate s&#x00f3;lo a&#x00f1;ade versionado autom&#x00e1;tico pero no bloquea
objetos en memoria ni cambia el nivel de aislamiento de tus transacciones de base de datos.
B&#x00e1;sicamente, usa Hibernate como usar&#x00ed;as JDBC directo (o JTA/CMT) con tus recursos de base de
datos.
</para>
<para>
Sin embargo, adem&#x00e1;s del versionado autom&#x00e1;tico, Hibernate ofrece una API (menor) para
bloqueo pesimista de filas, usando la sint&#x00e1;xis <literal>SELECT FOR UPDATE</literal>.
Esta API se discute m&#x00e1;s adelante en este cap&#x00ed;tulo:
</para>
<para>
Comenzamos la discusi&#x00f3;n del control de concurrencia en Hibernate con la granularidad
de <literal>Configuration</literal>, <literal>SessionFactory</literal>, y
<literal>Session</literal>, as&#x00ed; como la base de datos y las transacciones de aplicaci&#x00f3;n
largas.
</para>
<sect1 id="transactions-basics">
<title>&#x00c1;mbitos de sesi&#x00f3;n y de transacci&#x00f3;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&#x00f3;n. Es creado una sola vez,
usualmente en el arranque de la aplicaci&#x00f3;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&#x00e1; 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&#x00e1;s seguro
que se necesitar&#x00e1; acceso a los datos para servir una petici&#x00f3;n en particular. (Esto se
vuelve importante en cuanto est&#x00e9;s implementando alguno de los siguientes patrones usando
intercepci&#x00f3;n de peticiones).
</para>
<para>
Para completar este cuadro tienes que pensar tambi&#x00e9;n en las transacciones de base de
datos. Una transacci&#x00f3;n de base de datos tiene que ser tan corta como sea posible, para
reducir la contenci&#x00f3;n de bloqueos en la base de datos. Las transacciones largas de base de
datos prevendr&#x00e1;n a tu aplicaci&#x00f3;n de escalar a una carga altamente concurrente.
</para>
<para>
&#x00bf;Qu&#x00e9; es el &#x00e1;mbito de una unidad de trabajo? &#x00bf;Puede una sola <literal>Session</literal> de Hibernate
extenderse a trav&#x00e9;s de varias transacciones de base de datos o es &#x00e9;sta una relaci&#x00f3;n uno-a-uno
de &#x00e1;mbitos? &#x00bf;Cu&#x00e1;ndo debes abrir y cerrar una <literal>Session</literal> y c&#x00f3;mo demarcas los
l&#x00ed;mites de la transacci&#x00f3;n de base de datos?
</para>
<sect2 id="transactions-basics-uow">
<title>Unidad de trabajo</title>
<para>
Primero, no uses el antipatr&#x00f3;n <emphasis>sesi&#x00f3;n-por-operaci&#x00f3;n</emphasis>, esto es,
&#x00a1;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&#x00f3;n se hacen usando una secuencia
prevista, que est&#x00e1;n agrupadas dentro de unidades de trabajo at&#x00f3;micas. (Nota que esto
tambi&#x00e9;n significa que el auto-commit despu&#x00e9;s de cada una de las sentencias SQL es in&#x00fa;til
en una aplicaci&#x00f3;n, este modo est&#x00e1; 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&#x00f3;n m&#x00e1;s com&#x00fa;n en una aplicaci&#x00f3;n mutiusuario cliente/servidor es
<emphasis>sesi&#x00f3;n-por-petici&#x00f3;n</emphasis>. En este modelo, una petici&#x00f3;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&#x00f3;n es limpiada y cerrada.
Podr&#x00ed;as usar una sola transacci&#x00f3;n de base de datos para servir a petici&#x00f3;n del cliente,
comenz&#x00e1;ndola y comprometi&#x00e9;ndola cuando abres y cierras la <literal>Session</literal>.
La relaci&#x00f3;n entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas
aplicaciones.
</para>
<para>
El desaf&#x00ed;o yace en la implementaci&#x00f3;n: no s&#x00f3;lo tienen que comenzarse y terminarse correctamente
la <literal>Session</literal> y la transacci&#x00f3;n, sino que adem&#x00e1;s tienen que estar accesibles
para las operaciones de acceso a datos. La demarcaci&#x00f3;n de una unidad de trabajo se implementa
idealmente usando un interceptor que se ejecuta cuando una petici&#x00f3;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&#x00f3;n, usando una variable
<literal>ThreadLocal</literal>. Esto permite un f&#x00e1;cil acceso (como acceder a una variable static)
en t&#x00f3;do el c&#x00f3;digo que se ejecuta en esta hebra. Dependiendo del mecanismo de demarcaci&#x00f3;n de
transacciones de base de datos que elijas, podr&#x00ed;as mantener tambi&#x00e9;n el contexto de la transacci&#x00f3;n
en una variable <literal>ThreadLocal</literal>. Los patrones de implementaci&#x00f3;n para esto son
conocidos como <emphasis>Sesi&#x00f3;n Local de Hebra (ThreadLocal Session)</emphasis> y
<emphasis>Sesi&#x00f3;n Abierta en Vista (Open Session in View)</emphasis>. Puedes extender f&#x00e1;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&#x00f3;n</title>
<para>
El patr&#x00f3;n sesi&#x00f3;n-por-petici&#x00f3;n no es el &#x00fa;nico concepto &#x00fa;til que puedes usar para dise&#x00f1;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&#x00f3;n de base de datos se extienda a trav&#x00e9;s de la interacci&#x00f3;n de un usuario.
Considera el siguiente ejemplo:
</para>
<itemizedlist>
<listitem>
<para>
Se abre la primera pantalla de un di&#x00e1;logo, los datos vistos por el usuario han sido
cargados en una <literal>Session</literal> y transacci&#x00f3;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&#x00e9;s de 5 minutos y espera que sus modificaciones
sean hechas persistentes. Tambi&#x00e9;n espera que &#x00e9;l sea la &#x00fa;nica persona editando esta
informaci&#x00f3;n y que no puede ocurrir ninguna modificaci&#x00f3;n en conflicto.
</para>
</listitem>
</itemizedlist>
<para>
Llamamos a esto unidad de trabajo, desde el punto de vista del usuario, una larga
<emphasis>transacci&#x00f3;n de aplicaci&#x00f3;n</emphasis> ejecut&#x00e1;ndose. Hay muchas formas en
que puedes implementar esto en tu aplicaci&#x00f3;n.
</para>
<para>
Una primera implementaci&#x00f3;n ingenua podr&#x00ed;a mantener abierta la <literal>Session</literal>
y la transacci&#x00f3;n de base de datos durante el tiempo de pensar del usuario, con bloqueos
tomados en la base de datos para prevenir la modificaci&#x00f3;n concurrente, y para garantizar
aislamiento y atomicidad. Esto es, por supuesto, un antipatr&#x00f3;n, ya que la contenci&#x00f3;n de
bloqueo no permitir&#x00ed;a a la aplicaci&#x00f3;n escalar con el n&#x00fa;mero de usuarios concurrentes.
</para>
<para>
Claramente, tenemos que usar muchas transacciones de base de datos para implementar la transacci&#x00f3;n
de aplicaci&#x00f3;n. En este caso, mantener el aislamiento de los procesos de negocio se vuelve una
responsabilidad parcial de la capa de aplicaci&#x00f3;n. Una sola transacci&#x00f3;n de aplicaci&#x00f3;n usualmente
abarca varias transacciones de base de datos. Ser&#x00e1; at&#x00f3;mica si s&#x00f3;lo una de estas transacciones de
base de datos (la &#x00fa;ltima) almacena los datos actualizados, todas las otras simplemente leen datos
(por ejemplo, en un di&#x00e1;logo estilo-asistente abarcando muchos ciclos petici&#x00f3;n/respuesta).
Esto es m&#x00e1;s f&#x00e1;cil de implementar de lo que suena, especialmente si usas las funcionalidades de
Hibernate:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Versionado Autom&#x00e1;tico</emphasis> - Hibernate puede llevar un control autom&#x00e1;tico de
concurrencia optimista por ti, puede detectar autom&#x00e1;ticamente si una modificaci&#x00f3;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&#x00f3;n
de <emphasis>sesi&#x00f3;n-por-petici&#x00f3;n</emphasis>, todas las instancias cargadas estar&#x00e1;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&#x00f3;n se
llama <emphasis>sesi&#x00f3;n-por-petici&#x00f3;n-con-objetos-separados</emphasis>. Se usa
versionado autom&#x00e1;tico para aislar las modificaciones concurrentes.
</para>
</listitem>
<listitem>
<para>
<emphasis>Sesi&#x00f3;n Larga</emphasis> - La <literal>Session</literal> de Hibernate puede ser
desconectada de la conexi&#x00f3;n JDBC subyacente despu&#x00e9;s que se haya sido comprometida la
transacci&#x00f3;n de base de datos, y reconectada cuando ocurra una nueva petici&#x00f3;n del cliente.
Este patr&#x00f3;n es conocido como <emphasis>sesi&#x00f3;n-por-transacci&#x00f3;n-de-aplicaci&#x00f3;n</emphasis>
y hace la re-uni&#x00f3;n innecesaria. Para aislar las modificaciones concurrentes se usa el
versionado autom&#x00e1;tico.
</para>
</listitem>
</itemizedlist>
<para>
Tanto <emphasis>sesi&#x00f3;n-por-petici&#x00f3;n-con-objetos-separados</emphasis> como
<emphasis>sesi&#x00f3;n-por-transacci&#x00f3;n-de-aplicaci&#x00f3;n</emphasis>, ambas tienen
ventajas y desventajas, las discutimos m&#x00e1;s adelante en este cap&#x00ed;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&#x00f3;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 &#x00e1;mbito de una <literal>Session</literal>) las dos nociones son equivalentes, y
la identidad JVM para la identidad de base de datos est&#x00e1; garantizada por Hibernate. Sin embargo,
mientras la aplicaci&#x00f3;n acceda concurrentemente al "mismo" (identidad persistente) objeto de negocio
en dos sesiones diferentes, las dos instancias ser&#x00e1;n realmente "diferentes" (identidad JVM).
Los conflictos se resuelven (con versionado autom&#x00e1;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&#x00e1;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&#x00f3;n. La aplicaci&#x00f3;n nunca necesita
sincronizar sobre ning&#x00fa;n objeto de negocio, siempre que se apegue a una sola hebra por
<literal>Session</literal>. Dentro de una <literal>Session</literal> la aplicaci&#x00f3;n puede usar
con seguridad <literal>==</literal> para comparar objetos.
</para>
<para>
Sin embargo, una aplicaci&#x00f3;n que usa <literal>==</literal> fuera de una <literal>Session</literal>,
podr&#x00ed;a ver resultados inesperados. Esto podr&#x00ed;a ocurrir incluso en sitios algo inesperados,
por ejemplo, si pones dos instancias separadas dentro del mismo <literal>Set</literal>.
Ambas podr&#x00ed;an tener la misma identidad de base de datos (es decir, representar la misma fila),
pero la identidad JVM, por definici&#x00f3;n, no est&#x00e1; garantizada para las instancias en estado separado.
El desarrollador tiene que sobrescribir los m&#x00e9;todos <literal>equals()</literal> y
<literal>hashCode()</literal> en las clases persistentes e implementar su propia noci&#x00f3;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&#x00f3;n de atributos &#x00fa;nicos, usualmente inmutables.
El identificador de base de datos cambiar&#x00e1; 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&#x00f3;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&#x00f3;lo tienes que garantizar estabilidad en tanto los objetos est&#x00e9;n en el mismo
<literal>Set</literal>. Mira el sitio web de Hibernate para una discusi&#x00f3;n m&#x00e1;s cuidadosa de este
tema. Nota tambi&#x00e9;n que &#x00e9;ste no es un tema de Hibernate, sino simplemente c&#x00f3;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&#x00f3;n-por-sesi&#x00f3;n-de-usuario</emphasis> o
<emphasis>sesi&#x00f3;n-por-aplicaci&#x00f3;n</emphasis> (por supuesto, hay raras excepciones a esta
regla). Nota que algunis de los siguientes temas podr&#x00ed;an tambi&#x00e9;n aparecer con los patrones
recomendados. Aseg&#x00fa;rate que entiendes las implicaciones antes de tomar una decisi&#x00f3;n de
dise&#x00f1;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&#x00f3;n, o workers de
Swing, provocar&#x00e1;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&#x00e1;s adelante), debes considerar sincronizar
el acceso a tu sesi&#x00f3;n HTTP. De otro modo, un usuario que hace click lo suficientemente
r&#x00e1;pido puede llegar a usar la misma <literal>Session</literal> en dos hebras ejecut&#x00e1;ndose
concurrentemente.
</para>
</listitem>
<listitem>
<para>
Una excepci&#x00f3;n lanzada por Hibernate significa que tienes que deshacer (rollback) tu
transacci&#x00f3;n de base de datos y cerrar la <literal>Session</literal> inmediatamente
(discutido en m&#x00e1;s detalle luego). Si tu <literal>Session</literal> est&#x00e1; ligada a la
aplicaci&#x00f3;n, tienes que parar la aplicaci&#x00f3;n. Deshacer (rollback) la transacci&#x00f3;n de base
de datos no pone a tus objetos de vuelta al estado en que estaban al comienzo de la
transacci&#x00f3;n. Esto significa que el estado de la base de datos y los objetos de negocio
quedan fuera de sincron&#x00ed;a. Usualmente esto no es un problema, pues las excepciones no
son recuperables y tienes que volver a comenzar despu&#x00e9;s del rollback de todos modos.
</para>
</listitem>
<listitem>
<para>
La <literal>Session</literal> pone en cach&#x00e9; todo objeto que est&#x00e9; 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&#x00f3;n para esto es llamar a
<literal>clear()</literal> y <literal>evict()</literal> para gestionar el cach&#x00e9; 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&#x00f3;n
de una sesi&#x00f3;n de usuario significa tambi&#x00e9;n una alta probabilidad de datos a&#x00f1;ejos.
</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<sect1 id="transactions-demarcation">
<title>Demarcaci&#x00f3;n de la transacci&#x00f3;n de base de datos</title>
<para>
Los l&#x00ed;mites de las transacciones de base de datos (o sistema) son siempre necesarios. Ninguna comunicaci&#x00f3;n
con la base de datos puede darse fuera de una transacci&#x00f3;n de base de datos (esto parece confundir muchos
desarrolladores acostumbrados al modo auto-commit). Siempre usa l&#x00ed;mites de transacci&#x00f3;n claros, incluso
para las operaciones de s&#x00f3;lo lectura. Dependiendo del nivel de aislamiento y las capacidades de base de
datos, esto podr&#x00ed;a o no ser requerido, pero no hay un merma si siempre demarcas expl&#x00ed;citamente
las transacciones.
</para>
<para>
Una aplicaci&#x00f3;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&#x00ed;mites de transacci&#x00f3;n, en otras palabras, hacer begin, commit, o
rollback las transacciones de base de datos por s&#x00ed; mismo. Un entorno manejado usualmente provee transacciones
gestionadas por contenedor, con el ensamble de transacci&#x00f3;n definido declarativamente en descriptores de
despliegue de beans de sesi&#x00f3;n EJB, por ejemplo. La demarcaci&#x00f3;n program&#x00e1;tica de transacciones no es m&#x00e1;s
necesario, incluso limpiar (flush) la <literal>Session</literal> es hecho autom&#x00e1;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&#x00e9;s en un bean de sesi&#x00f3;n CMT.
</para>
<para>
Usualmente, finalizar una <literal>Session</literal> implica cuatro fases distintas:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
limpiar (flush) la sesi&#x00f3;n
</para>
</listitem>
<listitem>
<para>
comprometer la transacci&#x00f3;n
</para>
</listitem>
<listitem>
<para>
cerrar la sesi&#x00f3;n
</para>
</listitem>
<listitem>
<para>
manejar excepciones
</para>
</listitem>
</itemizedlist>
<para>
Limpiar la sesi&#x00f3;n ha sido discutido anteriormente, tendremos ahora una mirada m&#x00e1;s de cerca
a la demarcaci&#x00f3;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&#x00f3;n/transacci&#x00f3;n se ve as&#x00ed;:
</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&#x00ed;citamente -
la llamada a <literal>commit()</literal> autom&#x00e1;ticamente dispara la sincronizaci&#x00f3;n.
</para>
<para>
Una llamada a <literal>close()</literal> marca el fin de una sesi&#x00f3;n. La principal implicaci&#x00f3;n
de <literal>close()</literal> es que la conexi&#x00f3;n JDBC ser&#x00e1; abandonada por la sesi&#x00f3;n.
</para>
<para>
Este c&#x00f3;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&#x00f3;digo de negocio en una aplicaci&#x00f3;n normal;
las excepciones fatales (sistema) deben siempre ser capturadas en la "cima". En otras palabras,
el c&#x00f3;digo que ejecuta las llamadas de Hibernate (en la capa de persistencia) y el c&#x00f3;digo que
maneja <literal>RuntimeException</literal> (y usualmente s&#x00f3;lo puede limpiar y salir) est&#x00e1;n en
capas diferentes. Esto puede ser un desaf&#x00ed;o de dise&#x00f1;arlo t&#x00fa; mismo y debes usar los servicios
de contenedor J2EE/EJB en cuanto estuviesen disponibles. El manejo de excepciones se dicute
m&#x00e1;s adelante en este cap&#x00ed;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&#x00e1;s
de beans de sesi&#x00f3;n EJB), cada conexi&#x00f3;n de datasource obtenida por Hibernate ser&#x00e1; parte
autom&#x00e1;ticamente de la transacci&#x00f3;n JTA global. Hibernate ofrece dos estrategias para esta
integraci&#x00f3;n.
</para>
<para>
Si usas transacciones gestionadas-por-bean (BMT) Hibernate le dir&#x00e1; al servidor de aplicaciones
que comience y finalice una transacci&#x00f3;n BMT si usas la API de <literal>Transaction</literal>.
De modo que, el c&#x00f3;digo de gesti&#x00f3;n de la transacci&#x00f3;n es id&#x00e9;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&#x00f3;n de la transacci&#x00f3;n se hace en descriptores de despliegue de beans de sesi&#x00f3;n,
no program&#x00e1;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&#x00e1; y cerrar&#x00e1; entonces autom&#x00e1;ticamente la <literal>Session</literal> para ti. Lo &#x00fa;nico que resta
es deshacer (rollback) la transacci&#x00f3;n cuando ocurra una excepci&#x00f3;n. Afortunadamente, en un bean CMT,
incluso esto ocurre autom&#x00e1;ticamente, ya que una <literal>RuntimeException</literal> no manejada
disparada por un m&#x00e9;todo de un bean de sesi&#x00f3;n le dice al contenedor que ponga a deshacer la transacci&#x00f3;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&#x00f3;n BMT, y <literal>org.hibernate.transaction.CMTTransactionFactory</literal> en un
bean de sesi&#x00f3;n CMT, cuando configures la f&#x00e1;brica de transacciones de Hibernate. Recuerda adem&#x00e1;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&#x00e1;ticos de la sesi&#x00f3;n,
podr&#x00ed;as querer tambi&#x00e9;n usar la misma sesi&#x00f3;n en diferentes partes de tu c&#x00f3;digo. T&#x00ed;picamente,
en un entorno no manejado, usar&#x00ed;as una variable <literal>ThreadLocal</literal> para tener la sesi&#x00f3;n,
pero una sola petici&#x00f3;n de EJB puede ejecutarse en diferentes hebras (por ejemplo, un bean de sesi&#x00f3;n
llamando a otro bean de sesi&#x00f3;n). Si no quieres molestarte en pasar tu <literal>Session</literal>
por alrededor, la <literal>SessionFactory</literal> provee el m&#x00e9;todo
<literal>getCurrentSession()</literal>, que devuelve una sesi&#x00f3;n que est&#x00e1; pegada al contexto de
transacci&#x00f3;n JTA. &#x00a1;Esta es la forma m&#x00e1;s f&#x00e1;cil de integrar Hibernate en una aplicaci&#x00f3;n!
La sesi&#x00f3;n "actual" siempre tiene habilitados limpieza, cierre y liberaci&#x00f3;n de conexi&#x00f3;n autom&#x00e1;ticos
(sin importar la configuraci&#x00f3;n de las propiedades anteriores). Nuestra idioma de gesti&#x00f3;n de
sesi&#x00f3;n/transacci&#x00f3;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&#x00ed;mites de transacci&#x00f3;n se establecen declarativamente
en los descriptores de despliegue de tu bean de sesi&#x00f3;n. El ciclo de vida de la sesi&#x00f3;n es
manejado completamente por Hibernate.
</para>
<para>
Existe una advertencia al uso del modo de liberaci&#x00f3;n de conexi&#x00f3;n <literal>after_statement</literal>.
Debido a una limitaci&#x00f3;n tonta de la especificaci&#x00f3;n de JTA, no es posible para Hibernate
limpiar autom&#x00e1;ticamente ning&#x00fa;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&#x00ed;citamente desde un bloque <literal>finally</literal>.
(Por supuesto, la mayor&#x00ed;a de las aplicaciones pueden evitarlo f&#x00e1;cilmente no usando en absoluto ning&#x00fa;n
<literal>scroll()</literal> o <literal>iterate()</literal> desde el c&#x00f3;digo CMT.)
</para>
</sect2>
<sect2 id="transactions-demarcation-exceptions">
<title>Manejo de excepciones</title>
<para>
Si la <literal>Session</literal> lanza una excepci&#x00f3;n (incluyendo cualquier
<literal>SQLException</literal>), debes inmediatamente deshacer (rollback) la
transacci&#x00f3;n de base de datos, llamar a <literal>Session.close()</literal> y
descartar la instancia de <literal>Session</literal>. Ciertos m&#x00e9;todos de
<literal>Session</literal> <emphasis>no</emphasis> dejar&#x00e1;n la sesi&#x00f3;n en un
estado consistente. Ninguna excepci&#x00f3;n lanzada por Hibernate puede ser tratada
como recuperable. Aseg&#x00fa;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&#x00ed;a de los errores que
pueden ocurrir en la capa de persistencia de Hibernate, en una excepci&#x00f3;n no chequeada
(no lo era en versiones anteriores de Hibernate). En nuestra opini&#x00f3;n, no debemos forzar
al desarrollador de aplicaciones a capturar una excepci&#x00f3;n irrecuperable en una capa baja.
En la mayor&#x00ed;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&#x00e9;todos (es decir, en las capas
m&#x00e1;s altas) y se presenta un mensaje de error al usuario de la aplicaci&#x00f3;n (o se toma alguna
otra acci&#x00f3;n apropiada). Nota que Hibernate podr&#x00ed;a tambi&#x00e9;n lanzar otras excepciones no chequeadas
que no sean una <literal>HibernateException</literal>. Una vez m&#x00e1;s, no son recuperables y debe
tomarse una acci&#x00f3;n apropiada.
</para>
<para>
Hibernate envuelve <literal>SQLException</literal>s lanzadas mientras se interact&#x00fa;a con la base
de datos en una <literal>JDBCException</literal>. De hecho, Hibernate intentar&#x00e1; convertir la excepci&#x00f3;n
en una subclase de <literal>JDBCException</literal> m&#x00e1;s significativa. La <literal>SQLException</literal>
est&#x00e1; siempre disponible v&#x00ed;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&#x00e1; definido para el dialecto configurado; sin embargo,
es tambi&#x00e9;n posible enchufar una implementaci&#x00f3;n personalizada (ver los javadocs de la clase
<literal>SQLExceptionConverterFactory</literal> para los detalles). Los subtipos est&#x00e1;ndar de
<literal>JDBCException</literal> son:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>JDBCConnectionException</literal> - indica un error con la comunicaci&#x00f3;n JDBC subyacente.
</para>
</listitem>
<listitem>
<para>
<literal>SQLGrammarException</literal> - indica un problema de gram&#x00e1;tica o sint&#x00e1;xis con el
SQL publicado.
</para>
</listitem>
<listitem>
<para>
<literal>ConstraintViolationException</literal> - indica alguna forma de violaci&#x00f3;n de restricci&#x00f3;n
de integridad.
</para>
</listitem>
<listitem>
<para>
<literal>LockAcquisitionException</literal> - indica un error adquiriendo un nivel de bloqueo
necesario para realizar una operaci&#x00f3;n solicitada.
</para>
</listitem>
<listitem>
<para>
<literal>GenericJDBCException</literal> - una excepci&#x00f3;n gen&#x00e9;rica que no cay&#x00f3; en ninguna de las
otras categor&#x00ed;as.
</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<sect1 id="transactions-optimistic">
<title>Control optimista de concurrencia</title>
<para>
El &#x00fa;nico enfoque que es consistente con alta concurrencia y alta escalabilidad es el control
optimista de concurrencia con versionamiento. El chuequeo de versi&#x00f3;n usa n&#x00fa;meros de versi&#x00f3;n,
o timestamps, para detectar actualizaciones en conflicto (y para prevenir actualizaciones perdidas).
Hibernate provee para tres enfoques posibles de escribir c&#x00f3;digo de aplicaci&#x00f3;n que use concurrencia
optimista. Los casos de uso que hemos mostrado est&#x00e1;n en el contexto de transacciones de aplicaci&#x00f3;n
largas pero el chequeo de versiones tiene adem&#x00e1;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&#x00f3;n</title>
<para>
En una implementaci&#x00f3;n sin mucha ayuda de Hibernate, cada interacci&#x00f3;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&#x00f3;n a
realizar su propio chequeo de versiones para asegurar el aislamiento de transacciones de base de datos.
Es el enfoque m&#x00e1;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>&lt;version&gt;</literal>,
e Hibernate la incrementar&#x00e1; autom&#x00e1;ticamente durante la limpieza si la entidad est&#x00e1; sucia.
</para>
<para>
Por supuesto, si est&#x00e1;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 &#x00fa;ltimo compromiso (commit) gana</emphasis> ser&#x00e1; la estrategia por
defecto para tus transacciones de aplicaci&#x00f3;n largas. Ten en mente que esto podr&#x00ed;a confundir
a los usuarios de la aplicaci&#x00f3;n, pues podr&#x00ed;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&#x00e1;ctico para la mayor&#x00ed;a de aplicaciones. Frecuentemente, no s&#x00f3;lo intancias solas, sino grafos
completos de objetos modificados tienen que ser chequeados. Hibernate ofrece chequeo de versiones
autom&#x00e1;tico con el paradigma de dise&#x00f1;o de <literal>Session</literal> larga o de instancias separadas.
</para>
</sect2>
<sect2 id="transactions-optimistic-longsession">
<title>Sesi&#x00f3;n larga y versionado autom&#x00e1;tico</title>
<para>
Una sola instancia de <literal>Session</literal> y sus instancias persistentes
son usadas para toda la transacci&#x00f3;n de aplicaci&#x00f3;n. Hibernate chequea las versiones
de instancia en el momento de limpieza (flush), lanzando una excepci&#x00f3;n si se detecta
una modificaci&#x00f3;n concurrente. Concierne al desarrollador capturar y manejar esta excepci&#x00f3;n
(las opciones comunes son la oportunidad del usuario de fusionar los cambios, o recomenzar el
proceso de negocio sin datos a&#x00f1;ejos).
</para>
<para>
La <literal>Session</literal> se desconecta de cualquier conexi&#x00f3;n JDBC subyacente
al esperar por una interacci&#x00f3;n del usuario. Este enfoque es el m&#x00e1;s eficiente en t&#x00e9;rminos
de acceso a base de datos. La aplicaci&#x00f3;n no necesita tratar por s&#x00ed; misma con el chequeo de
versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cada
transacci&#x00f3;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&#x00ed;a conoce en qu&#x00e9; <literal>Session</literal> fue cargado.
<literal>Session.reconnect()</literal> obtiene una nueva conexi&#x00f3;n (o puedes proveer una) y
reasume la sesi&#x00f3;n. El m&#x00e9;todo <literal>Session.disconnect()</literal> desconectar&#x00e1; la sesi&#x00f3;n
de la conexi&#x00f3;n JDBC y la devolver&#x00e1; la conexi&#x00f3;n al pool (a menos que hayas provisto la conexi&#x00f3;n).
Despu&#x00e9;s de la reconexi&#x00f3;n, para forzar un chequeo de versi&#x00f3;n en datos que no est&#x00e9;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&#x00f3;n. No necesitas bloquear
ning&#x00fa;n dato que <emphasis>s&#x00ed; est&#x00e9;s</emphasis> actualizando.
</para>
<para>
Si las llamadas expl&#x00ed;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&#x00f3;n es problem&#x00e1;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&#x00f1;a como sea posible. Ya que la <literal>Session</literal> es tambi&#x00e9;n el cach&#x00e9;
(obligatorio) de primer nivel y contiene todos los objetos cargados, podemos probablemente cargar
esta estrategia s&#x00f3;lo para unos pocos ciclos petici&#x00f3;n/respuesta. Esto est&#x00e1; de hecho recomendado, ya que
la <literal>Session</literal> tendr&#x00e1; pronto tambi&#x00e9;n datos a&#x00f1;ejos.
</para>
<para>
Nota tambi&#x00e9;n que debes mantener la <literal>Session</literal> desconectada pr&#x00f3;xima a la capa
de persistencia. En otras palabras, usa una sesi&#x00f3;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&#x00e1;tico</title>
<para>
Cada interacci&#x00f3;n con el almac&#x00e9;n persistente ocurre en una nueva <literal>Session</literal>.
Sin embargo, las mismas instancias persistentes son reusadas para cada interacci&#x00f3;n con la base de
datos. La aplicaci&#x00f3;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&#x00e1; las versiones de instancia durante la limpieza (flush),
lanzando una excepci&#x00f3;n si ocurrieron actualizaciones en conflicto.
</para>
<para>
Puedes tambi&#x00e9;n llamar a <literal>lock()</literal> en vez de <literal>update()</literal>
y usar <literal>LockMode.READ</literal> (realizando un chequeo de versi&#x00f3;n, puenteando
todos los cach&#x00e9;s) si est&#x00e1;s seguro que el objeto no ha sido modificado.
</para>
</sect2>
<sect2 id="transactions-optimistic-customizing">
<title>Personalizando el versionado autom&#x00e1;tico</title>
<para>
Puedes deshabilitar el incremento de versi&#x00f3;n autom&#x00e1;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&#x00e1; ya m&#x00e1;s las versiones si la propiedad est&#x00e1;
sucia.
</para>
<para>
Los esquemas de base de datos heredados son frecuentemente est&#x00e1;ticos y no pueden ser modificados.
U otras aplicaciones podr&#x00ed;an tambi&#x00e9;n acceder la misma base de datos y no saber c&#x00f3;mo manejar los n&#x00fa;meros
de versi&#x00f3;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&#x00f3;n o
timestamp, con una comparaci&#x00f3;n del estado de todos los campos en una fila, activa
<literal>optimistic-lock="all"</literal> en el mapeo de <literal>&lt;class&gt;</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&#x00f3;n-por-petici&#x00f3;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>&lt;class&gt;</literal>, Hibernate s&#x00f3;lo comparar&#x00e1; los campos sucios durante la limpieza.
</para>
<para>
En ambos casos, con columnas de versi&#x00f3;n/timestamp dedicadas o con comparaci&#x00f3;n de campos
completa/sucios, Hibernate usa una sola sentencia <literal>UPDATE</literal>
(con una cl&#x00e1;usula <literal>WHERE</literal> apropiada) por entidad para ejecutar el chequeo
de versiones y actualizar la informaci&#x00f3;n. Si usas persistencia transitiva para la re-uni&#x00f3;n
en cascada de entidades asociadas, Hibernate podr&#x00ed;a ejecutar actualizaciones innecesarias.
Esto usualmente no es un problema, pero podr&#x00ed;an ejecutarse disparadores (triggers)
<emphasis>on update</emphasis> en la base de datos incluso cuando no se haya hecho ning&#x00fa;n cambio
a las instancias separadas. Puedes personalizar este comportamiento estableciendo
<literal>select-before-update="true"</literal> en el mapeo de <literal>&lt;class&gt;</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&#x00e1;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&#x00f3;n.
</para>
<para>
&#x00a1;Hibernate siempre usar&#x00e1; 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&#x00e1;ticamente cuando Hibernate actualiza o
inserta una fila.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.UPGRADE</literal> puede ser adquirido bajo petici&#x00f3;n expl&#x00ed;cita del usuario
usando <literal>SELECT ... FOR UPDATE</literal> en base de datos que soporten esa sint&#x00e1;xis.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.UPGRADE_NOWAIT</literal> puede ser adquirido bajo petici&#x00f3;n expl&#x00ed;cita del usuario
usando un <literal>SELECT ... FOR UPDATE NOWAIT</literal> bajo Oracle.
</para>
</listitem>
<listitem>
<para>
<literal>LockMode.READ</literal> es adquirido autom&#x00e1;ticamente cuando Hibernate lee datos
bajo los niveles de aislamiento Repeatable Read o Serializable. Puede ser readquirido por
pedido expl&#x00ed;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&#x00f3;n v&#x00ed;a una llamada a <literal>update()</literal> o <literal>saveOrUpdate()</literal> tambi&#x00e9;n
comienzan en este modo de bloqueo.
</para>
</listitem>
</itemizedlist>
<para>
La "petici&#x00f3;n expl&#x00ed;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&#x00fa;n cargado por la sesi&#x00f3;n, el objeto es
cargado usando <literal>SELECT ... FOR UPDATE</literal>. Si se llama a <literal>load()</literal> para
un objeto que ya est&#x00e9; 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&#x00fa;mero de versi&#x00f3;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&#x00e1; un modo alternativo
apropiado (en vez de lanzar una excepci&#x00f3;n). Esto asegura que las aplicaciones ser&#x00e1;n portables.
</para>
</sect1>
</chapter>

File diff suppressed because it is too large Load Diff

View File

@ -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>&lt;one-to-many&gt;</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -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>

View File

@ -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&#x00f3;n traducida del ingl&#x00e9;s de
la documentaci&#x00e9;n de referencia de Hibernate. La versi&#x00f3;n
traducida puede no estar actualizada! Sin embargo, las diferencias
deber&#x00ed;an ser s&#x00f3;lo menores. Consulta la documentaci&#x00f3;n
de referencia en ingl&#x00e9;s si est&#x00e1;s perdiendo informaci&#x00f3;n
o encuentras alg&#x00fa;n error de traducci&#x00f3;n. Si quieres colaborar con
una traducci&#x00f3;n en particular, cont&#x00e1;ctanos en la lista de correo
de desarrolladores de Hibernate.
</para>
</legalnotice>

View File

@ -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&#x00e9]]></othername>
<email>kreimer@bbs.frc.utn.edu.ar</email>
</othercredit>
</authorgroup>

View File

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB