1228 lines
56 KiB
XML
1228 lines
56 KiB
XML
|
<chapter id="objectstate">
|
||
|
<title>Trabajando con objetos</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate es una solución completa de mapeo objeto/relacional que no sólo
|
||
|
abstrae al desarrollador de los detalles del sistema de manejo de base datos
|
||
|
subyacente, sino que además ofrece <emphasis>manejo de estado</emphasis> de
|
||
|
objetos. Esto es, al contrario del manejo de <literal>sentencias</literal>
|
||
|
SQL en capas comunes de persistencia JDBC/SQL, una vista de la persistencia
|
||
|
en aplicaciones Java muy natural y orientada a objetos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
En otras palabras, los desarroladores de aplicaciones Hibernate deben siempre
|
||
|
pensar en el <emphasis>estado</emphasis> de sus objetos, y no necesariamente
|
||
|
en la ejecución de sentencias SQL. Esta parte es cuidada por Hibernate y es
|
||
|
sólo relevante para el desarrollador de la aplicación al afinar el rendimiento
|
||
|
del sistema.
|
||
|
</para>
|
||
|
|
||
|
<sect1 id="objectstate-overview">
|
||
|
<title>Estados de objeto de Hibernate</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate define y soporta los siguientes estados de objeto:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<emphasis>Transitorio</emphasis> - un objeto es transitorio si ha sido
|
||
|
recién instanciado usando el operador <literal>new</literal>, y no está
|
||
|
asociado a una <literal>Session</literal> de Hibernate. No tiene una
|
||
|
representación persistente en la base de datos y no se le ha asignado un
|
||
|
valor identificador. Las instancias transitorias serán destruídas por el
|
||
|
recolector de basura si la aplicación no mantiene más una referencia.
|
||
|
Usa la <literal>Session</literal> de Hibernate para hacer un objeto
|
||
|
persistente (y deja que Hibernate cuide de las sentencias SQL que necesitan
|
||
|
ejecutarse para esta transición).
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<emphasis>Persistente</emphasis> - una instancia persistente tiene una
|
||
|
representación en la base de datos y un valor identificador. Puede haber
|
||
|
sido salvado o cargado, sin embargo, está por definición en el ámbito de
|
||
|
una <literal>Session</literal>. Hibernate detectará cualquier cambio hecho
|
||
|
a un objeto en estado persistentey sincronizará el estado con la base de
|
||
|
datos cuando se complete la unidad de trabajo. Los desarrolladores no ejecutan
|
||
|
sentencias <literal>UPDATE</literal> manuales, o sentencias <literal>DELETE</literal>
|
||
|
cuando un objeto debe ser hecho transitorio.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<emphasis>Separado (detached)</emphasis> - una instancia separada es un objeto
|
||
|
que ha sido hecho persistente, pero su <literal>Session</literal> ha sido cerrada.
|
||
|
La referencia al objeto todavía es válida, por supuesto, y la instancia separada
|
||
|
podría incluso ser modificada en este estado. Una instancia separada puede ser
|
||
|
re-unida a una nueva <literal>Session</literal> en un punto posterior en el tiempo,
|
||
|
haciéndola persistente de nuevo (con todas las modificaciones). Este aspecto
|
||
|
habilita un modelo de programación para unidades de trabajo de ejecución larga
|
||
|
que requieren tiempo-para-pensar del usuario. Las llamamos <emphasis>transaccciones
|
||
|
de aplicación</emphasis>, es decir, una unidad de trabajo desde el punto de vista
|
||
|
del usuario.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Discutiremos ahora los estados y transiciones de estados (y los métodos de Hibernate que
|
||
|
disparan una transición) en más detalle:
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-makingpersistent" revision="1">
|
||
|
<title>Haciendo los objetos persistentes</title>
|
||
|
|
||
|
<para>
|
||
|
Las instancias recién instanciadas de una clase persistente son consideradas
|
||
|
<emphasis>transitorias</emphasis> por Hibernate. Podemos hacer una instancia
|
||
|
transitoria <emphasis>persistente</emphasis> asociándola con una sesión:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
|
||
|
fritz.setColor(Color.GINGER);
|
||
|
fritz.setSex('M');
|
||
|
fritz.setName("Fritz");
|
||
|
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Si <literal>Cat</literal> tiene un identificador generado, el identificador es
|
||
|
generado y asignado al <literal>cat</literal> cuando se llama a <literal>save()</literal>.
|
||
|
Si <literal>Cat</literal> tiene un identificador <literal>assigned</literal>,
|
||
|
o una clave compuesta, el identificador debe ser asignado a la instancia de
|
||
|
<literal>cat</literal> antes de llamar a <literal>save()</literal>. Puedes también
|
||
|
usar <literal>persist()</literal> en vez de <literal>save()</literal>, con la semántica
|
||
|
definida en el temprano borrador de EJB3.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Alternativamente, puedes asignar el identificador usando una versión sobrecargada
|
||
|
de <literal>save()</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[DomesticCat pk = new DomesticCat();
|
||
|
pk.setColor(Color.TABBY);
|
||
|
pk.setSex('F');
|
||
|
pk.setName("PK");
|
||
|
pk.setKittens( new HashSet() );
|
||
|
pk.addKitten(fritz);
|
||
|
sess.save( pk, new Long(1234) );]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Si el objeto que haces persistente tiene objetos asociados (por ejemplo,
|
||
|
la colección <literal>kittens</literal> en el ejemplo anterior), estos
|
||
|
objetos pueden ser hechos persistentes en cualquier orden que quieras
|
||
|
a menos que tengas una restricción <literal>NOT NULL</literal> sobre una
|
||
|
columna clave foránea. Nunca hay riesgo de violar restricciones de clave
|
||
|
foránea. Sin embargo, podrías violar una restricción <literal>NOT NULL</literal>
|
||
|
si llamas a <literal>save()</literal> sobre objetos en orden erróneo.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Usualmente no te preocupas con este detalle, pues muy probablemente usarás
|
||
|
la funcionalidad de <emphasis>persistencia transitiva</emphasis> de Hibernate
|
||
|
para salvar los objetos asociados automáticamente. Entonces, ni siquiera ocurren
|
||
|
violaciones de restricciones <literal>NOT NULL</literal> - Hibernate cuidará de todo.
|
||
|
La persistencia transitiva se discute más adelante en este capítulo.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-loading">
|
||
|
<title>Cargando un objeto</title>
|
||
|
|
||
|
<para>
|
||
|
Los métodos <literal>load()</literal> de <literal>Session</literal> te brindan
|
||
|
una forma de traer una instancia persistente si ya saves su identificador.
|
||
|
<literal>load()</literal> toma un objeto clase y cargará el estado dentro de
|
||
|
una instancia recién instanciada de esta clase, en estado persistente.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
|
||
|
|
||
|
<programlisting><![CDATA[// you need to wrap primitive identifiers
|
||
|
long pkId = 1234;
|
||
|
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Alternativamente, puedes cargar estado dentro de una instancia dada:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
||
|
// load pk's state into cat
|
||
|
sess.load( cat, new Long(pkId) );
|
||
|
Set kittens = cat.getKittens();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Nota que <literal>load()</literal> lanzará una excepción irrecuperable si no
|
||
|
hay una fila correspondiente en base de datos. Si la clase es mapeada con un
|
||
|
proxy, <literal>load()</literal> sólo devuelve un proxy no inicializado y no
|
||
|
llamará realmente a la base de datos hasta que invoques un método del proxy.
|
||
|
Este comportamiento es muy útil si deseas crear una asociación a un objeto
|
||
|
sin cargarlo realmente de la base de datos. Permite además que múltiples
|
||
|
instancias sean cargadas como un lote si se define <literal>batch-size</literal>
|
||
|
para el mapeo de la clase.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Si no tienes certeza que exista una fila correspondiente, debes usar el
|
||
|
método <literal>get()</literal>, que llama a la base de datos inmediatamente
|
||
|
y devuelve nulo si no existe una fila correspondiente.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id);
|
||
|
if (cat==null) {
|
||
|
cat = new Cat();
|
||
|
sess.save(cat, id);
|
||
|
}
|
||
|
return cat;]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes incluso cargar un objeto usando un <literal>SELECT ... FOR UPDATE</literal> de SQL,
|
||
|
usando un <literal>LockMode</literal>. Ver la documentación de la API para más
|
||
|
información.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Ten en cuenta que <emphasis>ninguna</emphasis> instancia asociada o colección contenida es
|
||
|
selecciona <literal>FOR UPDATE</literal>, a menos que decidas especificar
|
||
|
<literal>lock</literal> o <literal>all</literal> como un estilo de cascada para la
|
||
|
asociación.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Es posible volver a cargar un objeto y todas sus colecciones en cualquier momento,
|
||
|
usando el método <literal>refresh()</literal>. Esto es útil cuando se usan disparadores de
|
||
|
base de datos para inicializar algunas de las propiedades del objeto.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[sess.save(cat);
|
||
|
sess.flush(); //force the SQL INSERT
|
||
|
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Una cuestión importante aparece usualmente en este punto: ¿Cuánto carga Hibernate de
|
||
|
la base de datos y cuántos <literal>SELECT</literal>s de SQL usará? Esto depende de la
|
||
|
<emphasis>estrategia de recuperación</emphasis> y se explica en <xref linkend="performance-fetching"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-querying" revision="1">
|
||
|
<title>Consultando</title>
|
||
|
|
||
|
<para>
|
||
|
Si no sabes los identificadores de los objetos que estás buscando,
|
||
|
necesitas una consulta. Hibernate soporta un lenguaje de consulta
|
||
|
orientado a objetos (HQL) fácil de usar pero potente. Para la creación
|
||
|
de consultas programáticas, Hibernate soporta una funcionalidad sofisticada
|
||
|
de consulta de Criteria y Example (QBC and QBE). También puedes expresar tu
|
||
|
consulta en el SQL nativo de tu base de datos, con soporte opcional de Hibernate
|
||
|
para la conversión del conjunto resultado en objetos.
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="objectstate-querying-executing">
|
||
|
<title>Ejecutando consultas</title>
|
||
|
|
||
|
<para>
|
||
|
Las consultas HQL y SQL nativas son representadas con una instancia de
|
||
|
<literal>org.hibernate.Query</literal>. Esta interface ofrece métodos para
|
||
|
la ligación de parámetros, manejo del conjunto resultado, y para la
|
||
|
ejecución de la consulta real. Siempre obtienes una <literal>Query</literal>
|
||
|
usando la <literal>Session</literal> actual:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[List cats = session.createQuery(
|
||
|
"from Cat as cat where cat.birthdate < ?")
|
||
|
.setDate(0, date)
|
||
|
.list();
|
||
|
|
||
|
List mothers = session.createQuery(
|
||
|
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
|
||
|
.setString(0, name)
|
||
|
.list();
|
||
|
|
||
|
List kittens = session.createQuery(
|
||
|
"from Cat as cat where cat.mother = ?")
|
||
|
.setEntity(0, pk)
|
||
|
.list();
|
||
|
|
||
|
Cat mother = (Cat) session.createQuery(
|
||
|
"select cat.mother from Cat as cat where cat = ?")
|
||
|
.setEntity(0, izi)
|
||
|
.uniqueResult();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Una consulta se ejecuta usualmente invocando a <literal>list()</literal>,
|
||
|
el resultado de la consulta será cargado completamente dentro de una
|
||
|
colección en memoria. Las instancias de entidad traídas por una consulta
|
||
|
están en estado persistente. El método <literal>uniqueResult()</literal>
|
||
|
ofrece un atajo si sabes que tu consulta devolverá sólo un objeto.
|
||
|
</para>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-iterate">
|
||
|
<title>Iterando los resultados</title>
|
||
|
|
||
|
<para>
|
||
|
Ocasionalmente, podrías ser capaz de lograr mejor rendimiento al ejecutar la consulta
|
||
|
usando el método <literal>iterate()</literal>. Esto sólo será en el caso que esperes
|
||
|
que las instancias reales de entidad devueltas por la consulta estén ya en la sesión
|
||
|
o caché de segundo nivel. Si todavía no están en caché, <literal>iterate()</literal>
|
||
|
será más lento que <literal>list()</literal> y podría requerir muchas llamadas a la
|
||
|
base de datos para una consulta simple, usualmente <emphasis>1</emphasis> para la
|
||
|
selección inicial que solamente devuelve identificadores, y <emphasis>n</emphasis>
|
||
|
selecciones adicionales para inicializar las instancias reales.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[// fetch ids
|
||
|
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
|
||
|
while ( iter.hasNext() ) {
|
||
|
Qux qux = (Qux) iter.next(); // fetch the object
|
||
|
// something we couldnt express in the query
|
||
|
if ( qux.calculateComplicatedAlgorithm() ) {
|
||
|
// delete the current instance
|
||
|
iter.remove();
|
||
|
// dont need to process the rest
|
||
|
break;
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-tuples">
|
||
|
<title>Consultas que devuelven tuplas</title>
|
||
|
|
||
|
<para>
|
||
|
Las consultas de Hibernate a veces devuelven tuplas de objetos, en cuyo caso
|
||
|
cada tupla se devuelve como un array:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
|
||
|
"select kitten, mother from Cat kitten join kitten.mother mother")
|
||
|
.list()
|
||
|
.iterator();
|
||
|
|
||
|
while ( kittensAndMothers.hasNext() ) {
|
||
|
Object[] tuple = (Object[]) kittensAndMothers.next();
|
||
|
Cat kitten = tuple[0];
|
||
|
Cat mother = tuple[1];
|
||
|
....
|
||
|
}]]></programlisting>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-scalar">
|
||
|
<title>Resultados escalares</title>
|
||
|
|
||
|
<para>
|
||
|
Las consultas pueden especificar una propiedad de una clase en la cláusula
|
||
|
<literal>select</literal>. Pueden incluso llamar a funciones de agregación SQL.
|
||
|
Las propiedades o agregaciones son considerados resultados "escalares"
|
||
|
(y no entidades en estado persistente).
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Iterator results = sess.createQuery(
|
||
|
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
|
||
|
"group by cat.color")
|
||
|
.list()
|
||
|
.iterator();
|
||
|
|
||
|
while ( results.hasNext() ) {
|
||
|
Object[] row = results.next();
|
||
|
Color type = (Color) row[0];
|
||
|
Date oldest = (Date) row[1];
|
||
|
Integer count = (Integer) row[2];
|
||
|
.....
|
||
|
}]]></programlisting>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-parameters">
|
||
|
<title>Ligación de parámetros</title>
|
||
|
|
||
|
<para>
|
||
|
Se proveen métodos en <literal>Query</literal> para ligar valores a
|
||
|
parámetros con nombre o parámetros <literal>?</literal> de estilo JDBC.
|
||
|
<emphasis>Al contrario de JDBC, Hibernate numera los parámetros desde cero.</emphasis>
|
||
|
Los parámetros con nombre son identificadores de la forma <literal>:name</literal>
|
||
|
en la cadena de la consulta. Las ventajas de los parámetros con nombre son:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
los parámetros con nombre son insensibles al orden en que aparecen
|
||
|
en la cadena de consulta
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
pueden aparecer múltiples veces en la misma consulta
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
son auto-documentados
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<programlisting><![CDATA[//named parameter (preferred)
|
||
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
|
||
|
q.setString("name", "Fritz");
|
||
|
Iterator cats = q.iterate();]]></programlisting>
|
||
|
|
||
|
<programlisting><![CDATA[//positional parameter
|
||
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
|
||
|
q.setString(0, "Izi");
|
||
|
Iterator cats = q.iterate();]]></programlisting>
|
||
|
|
||
|
<programlisting><![CDATA[//named parameter list
|
||
|
List names = new ArrayList();
|
||
|
names.add("Izi");
|
||
|
names.add("Fritz");
|
||
|
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
|
||
|
q.setParameterList("namesList", names);
|
||
|
List cats = q.list();]]></programlisting>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-pagination">
|
||
|
<title>Paginación</title>
|
||
|
|
||
|
<para>
|
||
|
Si necesitas especificar límites sobre tu conjunto resultado (el número máximo de filas
|
||
|
que quieras traer y/o la primera fila que quieras traer) debes usar los métodos de la
|
||
|
interface <literal>Query</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
|
||
|
q.setFirstResult(20);
|
||
|
q.setMaxResults(10);
|
||
|
List cats = q.list();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Hibernate sabe cómo traducir este límite de consulta al SQL nativo de tu
|
||
|
DBMS.
|
||
|
</para>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-scrolling">
|
||
|
<title>Iteración scrollable</title>
|
||
|
|
||
|
<para>
|
||
|
Si tu driver JDBC soporta <literal>ResultSet</literal>s scrollables, la
|
||
|
interface <literal>Query</literal> puede ser usada para obtener un objeto
|
||
|
<literal>ScrollableResults</literal>, que permite una navegación flexible
|
||
|
de los resultados de consulta.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
|
||
|
"order by cat.name");
|
||
|
ScrollableResults cats = q.scroll();
|
||
|
if ( cats.first() ) {
|
||
|
|
||
|
// find the first name on each page of an alphabetical list of cats by name
|
||
|
firstNamesOfPages = new ArrayList();
|
||
|
do {
|
||
|
String name = cats.getString(0);
|
||
|
firstNamesOfPages.add(name);
|
||
|
}
|
||
|
while ( cats.scroll(PAGE_SIZE) );
|
||
|
|
||
|
// Now get the first page of cats
|
||
|
pageOfCats = new ArrayList();
|
||
|
cats.beforeFirst();
|
||
|
int i=0;
|
||
|
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
|
||
|
|
||
|
}
|
||
|
cats.close()]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Nota que se requiere una conexión de base de datos abierta (y cursor) para esta
|
||
|
funcionalidad, usa <literal>setMaxResult()</literal>/<literal>setFirstResult()</literal>
|
||
|
si necesitas la funcionalidad de paginación fuera de línea.
|
||
|
</para>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
<sect3 id="objectstate-querying-executing-named">
|
||
|
<title>Externalizando consultas con nombre</title>
|
||
|
|
||
|
<para>
|
||
|
Puedes además definir consultas con nombre en el documento de mapeo.
|
||
|
(Recuerda usar una sección <literal>CDATA</literal> si tu consulta
|
||
|
contiene caracteres que puedan ser interpretados como etiquetado.)
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
|
||
|
from eg.DomesticCat as cat
|
||
|
where cat.name = ?
|
||
|
and cat.weight > ?
|
||
|
] ]></query>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
La ligación de parámetros y ejecución se hace programáticamente:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
|
||
|
q.setString(0, name);
|
||
|
q.setInt(1, minWeight);
|
||
|
List cats = q.list();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Nota que el código real del programa es independiente del lenguaje de consulta
|
||
|
usado; puedes además definir consultas SQL nativas en metadatos, o migrar
|
||
|
consultas existentes a Hibernate colocándolas en ficheros de mapeo.
|
||
|
</para>
|
||
|
|
||
|
</sect3>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="objectstate-filtering" revision="1">
|
||
|
<title>Filtrando colecciones</title>
|
||
|
<para>
|
||
|
Un <emphasis>filtro</emphasis> de colección es un tipo especial de consulta que puede ser
|
||
|
aplicado a una colección persistente o array. La cadena de consulta puede referirse a
|
||
|
<literal>this</literal>, significando el elemento de colección actual.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
|
||
|
pk.getKittens(),
|
||
|
"where this.color = ?")
|
||
|
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
|
||
|
.list()
|
||
|
);]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
La colección devuelta es considerada un bag, y es una copia de la colección
|
||
|
dada. La colección original no es modificada (esto es contrario a la implicación
|
||
|
del nombre "filtro", pero consistente con el comportamiento esperado).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Observa que los filtros no requieren una cláusula <literal>from</literal> (aunque pueden
|
||
|
tener uno si se requiere). Los filtros no están limitados a devolver los elementos de
|
||
|
colección por sí mismos.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
|
||
|
pk.getKittens(),
|
||
|
"select this.mate where this.color = eg.Color.BLACK.intValue")
|
||
|
.list();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Incluso una consulta de filtro vacío es útil, por ejemplo, para cargar un
|
||
|
subconjunto de elementos en una colección enorme:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
|
||
|
mother.getKittens(), "")
|
||
|
.setFirstResult(0).setMaxResults(10)
|
||
|
.list();]]></programlisting>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="objecstate-querying-criteria" revision="1">
|
||
|
<title>Consultas de criterios</title>
|
||
|
|
||
|
<para>
|
||
|
HQL es extremadamente potente pero algunos desarrolladores prefieren construir
|
||
|
consultas dinámicamente usando una API orientada a objetos, en vez construir
|
||
|
cadenas de consulta. Hibernate provee una API intuitiva de consulta <literal>Criteria</literal>
|
||
|
para estos casos:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
|
||
|
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
|
||
|
crit.setMaxResults(10);
|
||
|
List cats = crit.list();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Las APIs de <literal>Criteria</literal> y la asociada <literal>Example</literal>
|
||
|
son discutidas en más detalle en <xref linkend="querycriteria"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="objectstate-querying-nativesql" revision="2">
|
||
|
<title>Consultas en SQL nativo</title>
|
||
|
|
||
|
<para>
|
||
|
Puedes expresar una consulta en SQL, usando <literal>createSQLQuery()</literal> y
|
||
|
dejando que Hibernate cuide del mapeo de los conjuntos resultado a objetos.
|
||
|
Nota que puedes llamar en cualquier momento a <literal>session.connection()</literal> y
|
||
|
usar la <literal>Connection</literal> JDBC directamente. Si eliges usar la API de
|
||
|
Hibernate, debes encerrar los alias de SQL entre llaves:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
||
|
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
|
||
|
"cat",
|
||
|
Cat.class
|
||
|
).list();]]></programlisting>
|
||
|
|
||
|
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
||
|
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
|
||
|
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
|
||
|
"FROM CAT {cat} WHERE ROWNUM<10",
|
||
|
"cat",
|
||
|
Cat.class
|
||
|
).list()]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Las consultas SQL pueden contener parámetros con nombre y posicionales, al igual que
|
||
|
las consultas de Hibernate. Puede encontrarse más información sobre consultas en SQL
|
||
|
nativo en <xref linkend="querysql"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-modifying" revision="1">
|
||
|
<title>Modificando objetos persistentes</title>
|
||
|
|
||
|
<para>
|
||
|
Las <emphasis>instancias persistentes transaccionales</emphasis> (es decir, objetos cargados,
|
||
|
creados o consultados por la <literal>Session</literal>) pueden ser manipulados por la
|
||
|
aplicación y cualquier cambio al estado persistente será persistido cuando la <literal>Session</literal>
|
||
|
sea <emphasis>limpiada (flushed)</emphasis> (discutido más adelante en este capítulo). No hay
|
||
|
necesidad de llamar un método en particular (como <literal>update()</literal>, que tiene un
|
||
|
propósito diferente) para hacer persistentes tus modificaciones. De modo que la forma más
|
||
|
directa de actualizar el estado de un objeto es cargarlo con <literal>load()</literal>,
|
||
|
y entonces manipularlo directamente, mientras la <literal>Session</literal> está abierta:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
|
||
|
cat.setName("PK");
|
||
|
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
A veces este modelo de programación es ineficiente pues podría requerir una
|
||
|
<literal>SELECT</literal> de SQL (para cargar un objeto) y un <literal>UPDATE</literal>
|
||
|
de SQL (para hacer persistentes sus datos actualizados) en la misma sesión. Por lo tanto,
|
||
|
Hibernate ofrece un enfoque alternativo, usando instancias separadas (detached).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
<emphasis>Nota que Hibernate no ofreve su propia API para ejecución directa de
|
||
|
sentencias <literal>UPDATE</literal> o <literal>DELETE</literal>. Hibernate es un
|
||
|
servicio de <emphasis>gestión de estado</emphasis>, no tienes que pensar en
|
||
|
<literal>sentencias</literal> para usarlo. JDBC es una API perfecta para ejecutar
|
||
|
sentencias SQL; puedes obtener una <literal>Connection</literal> JDBC en cualquier
|
||
|
momento llamando a <literal>session.connection()</literal>. Además, la noción de
|
||
|
operaciones masivas entra en conflicto con el mapeo objeto/relacional en aplicaciones
|
||
|
en línea orientadas al procesamiento de transacciones. Versiones futuras de Hibernate
|
||
|
pueden, sin embargo, proveer funciones de operación masiva especiales. Ver
|
||
|
<xref linkend="batch"/> por algunos trucos de operación en lote (batch) posibles.
|
||
|
</emphasis>
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-detached" revision="2">
|
||
|
<title>Modificando objetos separados</title>
|
||
|
|
||
|
<para>
|
||
|
Muchas aplicaciones necesitan recuperar un objeto en una transacción, enviarla
|
||
|
a la capa de UI para su manipulación, y entonces salvar los cambios en una nueva
|
||
|
transacción. Las aplicaciones que usan este tipo de enfoque en un entorno de
|
||
|
alta concurrencia usualmente usan datos versionados para asegurar el aislamiento
|
||
|
de la unidad de trabajo "larga".
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Hibernate soporta este modelo al proveer re-unión de instancias separadas usando
|
||
|
los métodos <literal>Session.update()</literal> o <literal>Session.merge()</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[// in the first session
|
||
|
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
||
|
Cat potentialMate = new Cat();
|
||
|
firstSession.save(potentialMate);
|
||
|
|
||
|
// in a higher layer of the application
|
||
|
cat.setMate(potentialMate);
|
||
|
|
||
|
// later, in a new session
|
||
|
secondSession.update(cat); // update cat
|
||
|
secondSession.update(mate); // update mate]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Si el <literal>Cat</literal> con identificador <literal>catId</literal> ya hubiera
|
||
|
sido cargado por <literal>secondSession</literal> cuando la aplicación intentó
|
||
|
volver a unirlo, se habría lanzado una excepción.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Usa <literal>update()</literal> si no estás seguro que la sesión tenga
|
||
|
una instancia ya persistente con el mismo identificador, y <literal>merge()</literal>
|
||
|
si quieres fusionar tus modificaciones en cualquier momento sin consideración del
|
||
|
estado de la sesión. En otras palabras, <literal>update()</literal> es usualmente
|
||
|
el primer método que llamarías en una sesión fresca, asegurando que la re-unión de
|
||
|
tus instancias separadas es la primera operación que se ejecuta.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La aplicación debe actualizar individualmente las instancias separadas alcanzables
|
||
|
por la instancia separada dada llamando a <literal>update()</literal>, si y
|
||
|
<emphasis>sólo</emphasis> si quiere que sus estados sean también actualizados.
|
||
|
Esto puede, por supuesto, ser automatizado usando <emphasis>persistencia transitiva</emphasis>,
|
||
|
ver <xref linkend="objectstate-transitive"/>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
El método <literal>lock()</literal> también permite a una aplicación reasociar
|
||
|
un objeto con una sesión nueva. Sin embargo, la instancia separada no puede
|
||
|
haber sido modificada!
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[//just reassociate:
|
||
|
sess.lock(fritz, LockMode.NONE);
|
||
|
//do a version check, then reassociate:
|
||
|
sess.lock(izi, LockMode.READ);
|
||
|
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
|
||
|
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Nota que <literal>lock()</literal> puede ser usado con varios <literal>LockMode</literal>s,
|
||
|
ver la documentación de la API y el capítulo sobre manejo de transacciones para más
|
||
|
información. La re-unión no es el único caso de uso para <literal>lock()</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Se discuten otros modelos para unidades de trabajo largas en <xref linkend="transactions-optimistic"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-saveorupdate">
|
||
|
<title>Detección automática de estado</title>
|
||
|
|
||
|
<para>
|
||
|
Los usuarios de Hibernate han pedido un método de propósito general que bien
|
||
|
salve una instancia transitoria generando un identificador nuevo, o bien
|
||
|
actualice/reúna las instancias separadas asociadas con su identificador actual.
|
||
|
El método <literal>saveOrUpdate()</literal> implementa esta funcionalidad.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[// in the first session
|
||
|
Cat cat = (Cat) firstSession.load(Cat.class, catID);
|
||
|
|
||
|
// in a higher tier of the application
|
||
|
Cat mate = new Cat();
|
||
|
cat.setMate(mate);
|
||
|
|
||
|
// later, in a new session
|
||
|
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
|
||
|
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El uso y semántica de <literal>saveOrUpdate()</literal> parece ser confuso para
|
||
|
usuarios nuevos. Primeramente, en tanto no estés intentando usar instancias de una
|
||
|
sesión en otra sesión nueva, no debes necesitar usar <literal>update()</literal>,
|
||
|
<literal>saveOrUpdate()</literal>, o <literal>merge()</literal>. Algunas aplicaciones
|
||
|
enteras nunca usarán ninguno de estos métodos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Usualmente <literal>update()</literal> o <literal>saveOrUpdate()</literal> se usan en
|
||
|
el siguiente escenario:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
la aplicación carga un objeto en la primera sesión
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
el objeto es pasado a la capa de UI
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
se hacen algunas modificaciones al objeto
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
el objeto se pasa abajo de regreso a la capa de negocio
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
la aplicación hace estas modificaciones persistentes llamando
|
||
|
a <literal>update()</literal> en una segunda sesión
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
<literal>saveOrUpdate()</literal> hace lo siguiente:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si el objeto ya es persistente en esta sesión, no hace nada
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si otro objeto asociado con la sesión tiene el mismo identificador,
|
||
|
lanza una excepción
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si el objeto no tiene ninguna propiedad identificadora, lo salva llamando a
|
||
|
<literal>save()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si el identificador del objeto tiene el valor asignado a un objeto recién
|
||
|
instanciado, lo salva llamando a <literal>save()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si el objeto está versionado (por un <literal><version></literal> o
|
||
|
<literal><timestamp></literal>), y el valor de la propiedad de versión
|
||
|
es el mismo valor asignado a una objeto recién instanciado, lo salva llamando
|
||
|
a <literal>save()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
en cualquier otro caso se actualiza el objeto llamando a <literal>update()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
y <literal>merge()</literal> es muy diferente:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si existe una instancia persistente con el mismo identificador asignado actualmente con la
|
||
|
sesión, copia el estado del objeto dado en la instancia persistente
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
si no existe ninguna instancia persistente actualmente asociada a la sesión,
|
||
|
intente cargarla de la base de datos, o crear una nueva instancia persistente
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
la instancia persistente es devuelta
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
la instancia dada no resulta ser asociada a la sesión, permanece separada
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-deleting" revision="1">
|
||
|
<title>Borrando objetos persistentes</title>
|
||
|
|
||
|
<para>
|
||
|
<literal>Session.delete()</literal> quitará el estado de un objeto de la base de datos.
|
||
|
Por supuesto, tu aplicación podría tener aún una referencia a un objeto borrado. Lo mejor
|
||
|
es pensar en <literal>delete()</literal> como hacer transitoria una instancia persistente.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes borrar los objetos en el orden que gustes, sin riesgo de violaciones
|
||
|
de restricción de clave foránea. Aún es posible violar una restricción
|
||
|
<literal>NOT NULL</literal> sobre una columna clave foránea borrando objetos
|
||
|
en un orden erróneo, por ejemplo, si borras el padre, pero olvidas borrar los
|
||
|
hijos.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-replicating" revision="1">
|
||
|
<title>Replicando objetos entre dos almacénes de datos diferentes</title>
|
||
|
|
||
|
<para>
|
||
|
Es ocasionalmente útil ser capaz de tomar un grafo de instancias persistentes
|
||
|
y hacerlas persistentes en un almacén de datos diferente, sin regenerar los valores
|
||
|
identificadores.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[//retrieve a cat from one database
|
||
|
Session session1 = factory1.openSession();
|
||
|
Transaction tx1 = session1.beginTransaction();
|
||
|
Cat cat = session1.get(Cat.class, catId);
|
||
|
tx1.commit();
|
||
|
session1.close();
|
||
|
|
||
|
//reconcile with a second database
|
||
|
Session session2 = factory2.openSession();
|
||
|
Transaction tx2 = session2.beginTransaction();
|
||
|
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
|
||
|
tx2.commit();
|
||
|
session2.close();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El <literal>ReplicationMode</literal> determina cómo <literal>replicate()</literal>
|
||
|
tratará los conflictos con filas existentes en la base de datos.
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>ReplicationMode.IGNORE</literal> - ignora el objeto cuando existe una fila
|
||
|
de base de datos con el mismo identificador
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>ReplicationMode.OVERWRITE</literal> - sobrescribe cualquier fila de base de
|
||
|
datos existente con el mismo identificador
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>ReplicationMode.EXCEPTION</literal> - lanza una excepción si existe una fila
|
||
|
de base de datos con el mismo identificador
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>ReplicationMode.LATEST_VERSION</literal> - sobrescribe la fila si su número
|
||
|
de versión es anterior al número de versión del objeto, o en caso contrario ignora el objeto
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Los casos de uso para esta funcionalidad incluyen reconciliar datos ingresados en
|
||
|
instancias diferentes de bases de datos, actualizar información de configuración de
|
||
|
sistema durante actualizaciones de producto, deshacer cambios producidos durante
|
||
|
transacciones no-ACID y más.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-flushing">
|
||
|
<title>Limpiando (flushing) la sesión</title>
|
||
|
|
||
|
<para>
|
||
|
Cada tanto, la <literal>Session</literal> ejecutará las sentencias SQL necesarias para
|
||
|
sincronizar el estado de la conexión JDBC con el estado de los objetos mantenidos en menoria.
|
||
|
Este proceso, <emphasis>limpieza (flush)</emphasis>, ocurre por defecto en los siguientes
|
||
|
puntos
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
antes de algunas ejecuciones de consulta
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
desde <literal>org.hibernate.Transaction.commit()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
desde <literal>Session.flush()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Las sentencias SQL son liberadas en el siguiente orden
|
||
|
</para>
|
||
|
|
||
|
<orderedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todas las inserciones de entidades, en el mismo orden que los objetos
|
||
|
correspondientes fueron salvados usando <literal>Session.save()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todas las actualizaciones de entidades
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todas los borrados de colecciones
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todos los borrados, actualizaciones e inserciones de elementos de colección
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todas las inserciones de colecciones
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
todos los borrados de entidades, en el mismo orden que los objetos
|
||
|
correspondientes fueron borrados usando <literal>Session.delete()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</orderedlist>
|
||
|
|
||
|
<para>
|
||
|
(Una excepción es que los objetos que usan generación de ID <literal>native</literal>
|
||
|
se insertan cuando son salvados.)
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Excepto cuando llamas explícitamente a <literal>flush()</literal>, no hay en absoluto
|
||
|
garantías sobre <emphasis>cuándo</emphasis> la <literal>Session</literal> ejecuta las
|
||
|
llamadas JDBC. sólo sobre el <emphasis>orden</emphasis> en que son ejecutadas. Sin embargo,
|
||
|
Hibernate garantiza que los métodos <literal>Query.list(..)</literal> nunca devolverán datos
|
||
|
añejos o erróneos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Es posible cambiar el comportamiento por defecto de modo que la limpieza (flush)
|
||
|
ocurra menos frecuentemente. La clase <literal>FlushMode</literal> tres modos
|
||
|
diferentes: sólo en tiempo de compromiso (y sólo cuando se use la API de
|
||
|
<literal>Transaction</literal> de Hibernate), limpieza automática usando la rutina
|
||
|
explicada, o nunca limpiar a menos que se llame a <literal>flush()</literal>
|
||
|
explícitamente. El último modo es útil para unidades de trabajo largas, donde una
|
||
|
<literal>Session</literal> se mantiene abierta y desconectada por largo tiempo
|
||
|
(ver <xref linkend="transactions-optimistic-longsession"/>).
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[sess = sf.openSession();
|
||
|
Transaction tx = sess.beginTransaction();
|
||
|
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
|
||
|
|
||
|
Cat izi = (Cat) sess.load(Cat.class, id);
|
||
|
izi.setName(iznizi);
|
||
|
|
||
|
// might return stale data
|
||
|
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
||
|
|
||
|
// change to izi is not flushed!
|
||
|
...
|
||
|
tx.commit(); // flush occurs]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Durante la limpieza, puede ocurrir una excepción (por ejemplo, si una operación DML
|
||
|
violase una restricción). Ya que el manejo de excepciones implica alguna comprensión
|
||
|
del comportamiento transaccional de Hibernate, lo discutimos en <xref linkend="transactions"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-transitive">
|
||
|
<title>Persistencia transitiva</title>
|
||
|
|
||
|
<para>
|
||
|
Es absolutamente incómodo dalvar, borrar, o reunir objetos individuales,
|
||
|
especialmente si tratas con un grafo de objetos asociados. Un caso común es
|
||
|
una relación padre/hijo. Considera el siguiente ejemplo:
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Si los hijos en una relación padre/hijo pudieran ser tipificados en valor
|
||
|
(por ejemplo, una colección de direcciones o cadenas), sus ciclos de vida
|
||
|
dependerían del padre y se requeriría ninguna otra acción para el tratamiento
|
||
|
en "cascada" de cambios de estado. Cuando el padre es salvado, los objetos hijo
|
||
|
tipificados en valor son salvados también, cuando se borra el padre, se borran
|
||
|
los hijos, etc. Esto funciona incluso para operaciones como el retiro de un
|
||
|
hijo de la colección. Hibernate detectará esto y, ya que los objetos tipificados
|
||
|
en valor no pueden tener referencias compartidas, borrará el hijo de la base
|
||
|
de datos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Ahora considera el mismo escenario con los objetos padre e hijos siendo entidades,
|
||
|
no tipos de valor (por ejemplo, categorías e ítems, o gatos padre e hijos).
|
||
|
Las entidades tienen su propio ciclo de vida, soportan referencias compartidas
|
||
|
(de modo que quitar una entidad de una colección no significa que sea borrada),
|
||
|
y no hay por defecto ningún tratamiento en "cascada" de estado de una entidad
|
||
|
a otras entidades asociadas. Hibernate no implementa <emphasis>persistencia por
|
||
|
alcance</emphasis>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Para cada operación básica de la sesión de Hibernate - incluyendo <literal>persist(), merge(),
|
||
|
saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()</literal> - hay un estilo
|
||
|
de cascada correspondiente. Respectivamente, los estilos de cascada se llaman <literal>create,
|
||
|
merge, save-update, delete, lock, refresh, evict, replicate</literal>. Si quieres que una
|
||
|
operación sea tratada en cascada a lo largo de una asociación, debes indicar eso en el
|
||
|
documento de mapeo. Por ejemplo:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<one-to-one name="person" cascade="persist"/>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Los estilos de cascada pueden combinarse:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<one-to-one name="person" cascade="persist,delete,lock"/>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes incluso usar <literal>cascade="all"</literal> para especificar que <emphasis>todas</emphasis>
|
||
|
las operaciones deben ser tratadas en cascada a lo largo de la asociación. El por defecto
|
||
|
<literal>cascade="none"</literal> especifica que ninguna operación será tratada en cascada.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Un estilo de cascada especial, <literal>delete-orphan</literal>, se aplica sólo a
|
||
|
asociaciones uno-a-muchos, e indica que la operación <literal>delete()</literal> debe
|
||
|
aplicarse a cualquier objeto hijo que sea quitado de la asociación.
|
||
|
</para>
|
||
|
|
||
|
|
||
|
<para>
|
||
|
Recomendaciones:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Usualmente no tiene sentido habilitar el tratamiento en cascada a una asociación
|
||
|
<literal><many-to-one></literal> o <literal><many-to-many></literal>.
|
||
|
El tratamiento en cascada es frecuentemente útil para las asociaciones
|
||
|
<literal><one-to-one></literal> y <literal><one-to-many></literal>.
|
||
|
associations.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si la esperanza de vida de los objetos hijos está ligada a la eesperanza de
|
||
|
vida del objeto padre, házlo un <emphasis>objeto de ciclo de vida</emphasis>
|
||
|
especificando <literal>cascade="all,delete-orphan"</literal>.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
En otro caso, puede que no necesites tratamiento en cascada en absoluto. Pero
|
||
|
si piensas que estarás trabajando frecuentemente con padre e hijos juntos en la
|
||
|
misma transacción, y quieres ahorrarte algo de tipeo, considera usar
|
||
|
<literal>cascade="persist,merge,save-update"</literal>.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Mapear una asociación (ya sea una asociación monovaluada, o una colección) con
|
||
|
<literal>cascade="all"</literal> marca la asociación como una relación del estilo
|
||
|
<emphasis>padre/hijo</emphasis> donde save/update/delete en el padre resulta
|
||
|
en save/update/delete del hijo o hijos.
|
||
|
</para>
|
||
|
<para>
|
||
|
Además, una mera referencia a un hijo desde un padre persistente resultará en
|
||
|
un save/update del hijo. Esta metáfora está incompleta, sin embargo. Un hijo
|
||
|
que deje de ser referenciado por su padre <emphasis>no</emphasis> es borrado
|
||
|
automáticamente, excepto en el caso de una asociación <literal><one-to-many></literal>
|
||
|
mapeada con <literal>cascade="delete-orphan"</literal>. La semántica precisa de
|
||
|
las operaciones en cascada para una relación padre/hijo es:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un padre le es pasado a <literal>persist()</literal>, todos los hijos le son
|
||
|
pasados a <literal>persist()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un padre le es pasado a <literal>merge()</literal>, todos los hijos le son
|
||
|
pasados a <literal>merge()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un padre le es pasado a <literal>save()</literal>, <literal>update()</literal> o
|
||
|
<literal>saveOrUpdate()</literal>, todos los hijos le son pasados a <literal>saveOrUpdate()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un hijo transitorio o separado se vuelve referenciado por un padre
|
||
|
persistente, le es pasado a <literal>saveOrUpdate()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un padre es borrado, todos los hijos le son pasados a <literal>delete()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Si un hijo deja de ser referenciado por un padre persistente,
|
||
|
<emphasis>no ocurre nada especial</emphasis> - la aplicación debe
|
||
|
borrar explícitamente el hijo de ser necesario - a menos que
|
||
|
<literal>cascade="delete-orphan"</literal>, en cuyo caso el hijo
|
||
|
"huérfano" es borrado.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-metadata">
|
||
|
<title>Usando metadatos</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate requiere de un modelo de meta-nivel muy rico de todas las entidades y tipos de valor.
|
||
|
De vez en cuando, este modelo es muy útil para la aplicación misma. Por ejemplo, la aplicación
|
||
|
podría usar los metadatos de Hibernate para implementar un algoritmo "inteligente" de copia
|
||
|
en profundidad que entienda qué objetos deben ser copiados (por ejemplo, tipo de valor mutables)
|
||
|
y cuáles no (por ejemplo, tipos de valor inmutables y, posiblemente, entidades asociadas).
|
||
|
</para>
|
||
|
<para>
|
||
|
Hibernate expone los metadatos vía las interfaces <literal>ClassMetadata</literal> y
|
||
|
<literal>CollectionMetadata</literal> y la jerarquía <literal>Type</literal>. Las instancias
|
||
|
de las interfaces de metadatos pueden obtenerse de <literal>SessionFactory</literal>.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Cat fritz = ......;
|
||
|
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
|
||
|
|
||
|
Object[] propertyValues = catMeta.getPropertyValues(fritz);
|
||
|
String[] propertyNames = catMeta.getPropertyNames();
|
||
|
Type[] propertyTypes = catMeta.getPropertyTypes();
|
||
|
|
||
|
// get a Map of all properties which are not collections or associations
|
||
|
Map namedValues = new HashMap();
|
||
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
||
|
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
|
||
|
namedValues.put( propertyNames[i], propertyValues[i] );
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|
||
|
|