hibernate-orm/reference/es/modules/session_api.xml

1228 lines
56 KiB
XML
Raw Normal View History

<chapter id="objectstate">
<title>Trabajando con objetos</title>
<para>
Hibernate es una soluci&#x00f3;n completa de mapeo objeto/relacional que no s&#x00f3;lo
abstrae al desarrollador de los detalles del sistema de manejo de base datos
subyacente, sino que adem&#x00e1;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&#x00f3;n de sentencias SQL. Esta parte es cuidada por Hibernate y es
s&#x00f3;lo relevante para el desarrollador de la aplicaci&#x00f3;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&#x00e9;n instanciado usando el operador <literal>new</literal>, y no est&#x00e1;
asociado a una <literal>Session</literal> de Hibernate. No tiene una
representaci&#x00f3;n persistente en la base de datos y no se le ha asignado un
valor identificador. Las instancias transitorias ser&#x00e1;n destru&#x00ed;das por el
recolector de basura si la aplicaci&#x00f3;n no mantiene m&#x00e1;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&#x00f3;n).
</para>
</listitem>
<listitem>
<para>
<emphasis>Persistente</emphasis> - una instancia persistente tiene una
representaci&#x00f3;n en la base de datos y un valor identificador. Puede haber
sido salvado o cargado, sin embargo, est&#x00e1; por definici&#x00f3;n en el &#x00e1;mbito de
una <literal>Session</literal>. Hibernate detectar&#x00e1; cualquier cambio hecho
a un objeto en estado persistentey sincronizar&#x00e1; 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&#x00ed;a es v&#x00e1;lida, por supuesto, y la instancia separada
podr&#x00ed;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&#x00e9;ndola persistente de nuevo (con todas las modificaciones). Este aspecto
habilita un modelo de programaci&#x00f3;n para unidades de trabajo de ejecuci&#x00f3;n larga
que requieren tiempo-para-pensar del usuario. Las llamamos <emphasis>transaccciones
de aplicaci&#x00f3;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&#x00e9;todos de Hibernate que
disparan una transici&#x00f3;n) en m&#x00e1;s detalle:
</para>
</sect1>
<sect1 id="objectstate-makingpersistent" revision="1">
<title>Haciendo los objetos persistentes</title>
<para>
Las instancias reci&#x00e9;n instanciadas de una clase persistente son consideradas
<emphasis>transitorias</emphasis> por Hibernate. Podemos hacer una instancia
transitoria <emphasis>persistente</emphasis> asoci&#x00e1;ndola con una sesi&#x00f3;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&#x00e9;n
usar <literal>persist()</literal> en vez de <literal>save()</literal>, con la sem&#x00e1;ntica
definida en el temprano borrador de EJB3.
</para>
<para>
Alternativamente, puedes asignar el identificador usando una versi&#x00f3;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&#x00f3;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&#x00f3;n <literal>NOT NULL</literal> sobre una
columna clave for&#x00e1;nea. Nunca hay riesgo de violar restricciones de clave
for&#x00e1;nea. Sin embargo, podr&#x00ed;as violar una restricci&#x00f3;n <literal>NOT NULL</literal>
si llamas a <literal>save()</literal> sobre objetos en orden err&#x00f3;neo.
</para>
<para>
Usualmente no te preocupas con este detalle, pues muy probablemente usar&#x00e1;s
la funcionalidad de <emphasis>persistencia transitiva</emphasis> de Hibernate
para salvar los objetos asociados autom&#x00e1;ticamente. Entonces, ni siquiera ocurren
violaciones de restricciones <literal>NOT NULL</literal> - Hibernate cuidar&#x00e1; de todo.
La persistencia transitiva se discute m&#x00e1;s adelante en este cap&#x00ed;tulo.
</para>
</sect1>
<sect1 id="objectstate-loading">
<title>Cargando un objeto</title>
<para>
Los m&#x00e9;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&#x00e1; el estado dentro de
una instancia reci&#x00e9;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&#x00e1; una excepci&#x00f3;n irrecuperable si no
hay una fila correspondiente en base de datos. Si la clase es mapeada con un
proxy, <literal>load()</literal> s&#x00f3;lo devuelve un proxy no inicializado y no
llamar&#x00e1; realmente a la base de datos hasta que invoques un m&#x00e9;todo del proxy.
Este comportamiento es muy &#x00fa;til si deseas crear una asociaci&#x00f3;n a un objeto
sin cargarlo realmente de la base de datos. Permite adem&#x00e1;s que m&#x00fa;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&#x00e9;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&#x00f3;n de la API para m&#x00e1;s
informaci&#x00f3;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&#x00f3;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&#x00f3;n.
</para>
<para>
Es posible volver a cargar un objeto y todas sus colecciones en cualquier momento,
usando el m&#x00e9;todo <literal>refresh()</literal>. Esto es &#x00fa;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&#x00f3;n importante aparece usualmente en este punto: ¿Cu&#x00e1;nto carga Hibernate de
la base de datos y cu&#x00e1;ntos <literal>SELECT</literal>s de SQL usar&#x00e1;? Esto depende de la
<emphasis>estrategia de recuperaci&#x00f3;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&#x00e1;s buscando,
necesitas una consulta. Hibernate soporta un lenguaje de consulta
orientado a objetos (HQL) f&#x00e1;cil de usar pero potente. Para la creaci&#x00f3;n
de consultas program&#x00e1;ticas, Hibernate soporta una funcionalidad sofisticada
de consulta de Criteria y Example (QBC and QBE). Tambi&#x00e9;n puedes expresar tu
consulta en el SQL nativo de tu base de datos, con soporte opcional de Hibernate
para la conversi&#x00f3;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&#x00e9;todos para
la ligaci&#x00f3;n de par&#x00e1;metros, manejo del conjunto resultado, y para la
ejecuci&#x00f3;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&#x00e1; cargado completamente dentro de una
colecci&#x00f3;n en memoria. Las instancias de entidad tra&#x00ed;das por una consulta
est&#x00e1;n en estado persistente. El m&#x00e9;todo <literal>uniqueResult()</literal>
ofrece un atajo si sabes que tu consulta devolver&#x00e1; s&#x00f3;lo un objeto.
</para>
<sect3 id="objectstate-querying-executing-iterate">
<title>Iterando los resultados</title>
<para>
Ocasionalmente, podr&#x00ed;as ser capaz de lograr mejor rendimiento al ejecutar la consulta
usando el m&#x00e9;todo <literal>iterate()</literal>. Esto s&#x00f3;lo ser&#x00e1; en el caso que esperes
que las instancias reales de entidad devueltas por la consulta est&#x00e9;n ya en la sesi&#x00f3;n
o cach&#x00e9; de segundo nivel. Si todav&#x00ed;a no est&#x00e1;n en cach&#x00e9;, <literal>iterate()</literal>
ser&#x00e1; m&#x00e1;s lento que <literal>list()</literal> y podr&#x00ed;a requerir muchas llamadas a la
base de datos para una consulta simple, usualmente <emphasis>1</emphasis> para la
selecci&#x00f3;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&#x00e1;usula
<literal>select</literal>. Pueden incluso llamar a funciones de agregaci&#x00f3;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&#x00f3;n de par&#x00e1;metros</title>
<para>
Se proveen m&#x00e9;todos en <literal>Query</literal> para ligar valores a
par&#x00e1;metros con nombre o par&#x00e1;metros <literal>?</literal> de estilo JDBC.
<emphasis>Al contrario de JDBC, Hibernate numera los par&#x00e1;metros desde cero.</emphasis>
Los par&#x00e1;metros con nombre son identificadores de la forma <literal>:name</literal>
en la cadena de la consulta. Las ventajas de los par&#x00e1;metros con nombre son:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
los par&#x00e1;metros con nombre son insensibles al orden en que aparecen
en la cadena de consulta
</para>
</listitem>
<listitem>
<para>
pueden aparecer m&#x00fa;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&#x00f3;n</title>
<para>
Si necesitas especificar l&#x00ed;mites sobre tu conjunto resultado (el n&#x00fa;mero m&#x00e1;ximo de filas
que quieras traer y/o la primera fila que quieras traer) debes usar los m&#x00e9;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&#x00f3;mo traducir este l&#x00ed;mite de consulta al SQL nativo de tu
DBMS.
</para>
</sect3>
<sect3 id="objectstate-querying-executing-scrolling">
<title>Iteraci&#x00f3;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&#x00f3;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&#x00f3;n de base de datos abierta (y cursor) para esta
funcionalidad, usa <literal>setMaxResult()</literal>/<literal>setFirstResult()</literal>
si necesitas la funcionalidad de paginaci&#x00f3;n fuera de l&#x00ed;nea.
</para>
</sect3>
<sect3 id="objectstate-querying-executing-named">
<title>Externalizando consultas con nombre</title>
<para>
Puedes adem&#x00e1;s definir consultas con nombre en el documento de mapeo.
(Recuerda usar una secci&#x00f3;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&#x00f3;n de par&#x00e1;metros y ejecuci&#x00f3;n se hace program&#x00e1;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&#x00f3;digo real del programa es independiente del lenguaje de consulta
usado; puedes adem&#x00e1;s definir consultas SQL nativas en metadatos, o migrar
consultas existentes a Hibernate coloc&#x00e1;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&#x00f3;n es un tipo especial de consulta que puede ser
aplicado a una colecci&#x00f3;n persistente o array. La cadena de consulta puede referirse a
<literal>this</literal>, significando el elemento de colecci&#x00f3;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&#x00f3;n devuelta es considerada un bag, y es una copia de la colecci&#x00f3;n
dada. La colecci&#x00f3;n original no es modificada (esto es contrario a la implicaci&#x00f3;n
del nombre "filtro", pero consistente con el comportamiento esperado).
</para>
<para>
Observa que los filtros no requieren una cl&#x00e1;usula <literal>from</literal> (aunque pueden
tener uno si se requiere). Los filtros no est&#x00e1;n limitados a devolver los elementos de
colecci&#x00f3;n por s&#x00ed; 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&#x00ed;o es &#x00fa;til, por ejemplo, para cargar un
subconjunto de elementos en una colecci&#x00f3;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&#x00e1;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&#x00e1;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&#x00e1;metros con nombre y posicionales, al igual que
las consultas de Hibernate. Puede encontrarse m&#x00e1;s informaci&#x00f3;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&#x00f3;n y cualquier cambio al estado persistente ser&#x00e1; persistido cuando la <literal>Session</literal>
sea <emphasis>limpiada (flushed)</emphasis> (discutido m&#x00e1;s adelante en este cap&#x00ed;tulo). No hay
necesidad de llamar un m&#x00e9;todo en particular (como <literal>update()</literal>, que tiene un
prop&#x00f3;sito diferente) para hacer persistentes tus modificaciones. De modo que la forma m&#x00e1;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&#x00e1; 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&#x00f3;n es ineficiente pues podr&#x00ed;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&#x00f3;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&#x00f3;n directa de
sentencias <literal>UPDATE</literal> o <literal>DELETE</literal>. Hibernate es un
servicio de <emphasis>gesti&#x00f3;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&#x00e1;s, la noci&#x00f3;n de
operaciones masivas entra en conflicto con el mapeo objeto/relacional en aplicaciones
en l&#x00ed;nea orientadas al procesamiento de transacciones. Versiones futuras de Hibernate
pueden, sin embargo, proveer funciones de operaci&#x00f3;n masiva especiales. Ver
<xref linkend="batch"/> por algunos trucos de operaci&#x00f3;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&#x00f3;n, enviarla
a la capa de UI para su manipulaci&#x00f3;n, y entonces salvar los cambios en una nueva
transacci&#x00f3;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&#x00f3;n de instancias separadas usando
los m&#x00e9;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&#x00f3;n intent&#x00f3;
volver a unirlo, se habr&#x00ed;a lanzado una excepci&#x00f3;n.
</para>
<para>
Usa <literal>update()</literal> si no est&#x00e1;s seguro que la sesi&#x00f3;n tenga
una instancia ya persistente con el mismo identificador, y <literal>merge()</literal>
si quieres fusionar tus modificaciones en cualquier momento sin consideraci&#x00f3;n del
estado de la sesi&#x00f3;n. En otras palabras, <literal>update()</literal> es usualmente
el primer m&#x00e9;todo que llamar&#x00ed;as en una sesi&#x00f3;n fresca, asegurando que la re-uni&#x00f3;n de
tus instancias separadas es la primera operaci&#x00f3;n que se ejecuta.
</para>
<para>
La aplicaci&#x00f3;n debe actualizar individualmente las instancias separadas alcanzables
por la instancia separada dada llamando a <literal>update()</literal>, si y
<emphasis>s&#x00f3;lo</emphasis> si quiere que sus estados sean tambi&#x00e9;n actualizados.
Esto puede, por supuesto, ser automatizado usando <emphasis>persistencia transitiva</emphasis>,
ver <xref linkend="objectstate-transitive"/>.
</para>
<para>
El m&#x00e9;todo <literal>lock()</literal> tambi&#x00e9;n permite a una aplicaci&#x00f3;n reasociar
un objeto con una sesi&#x00f3;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&#x00f3;n de la API y el cap&#x00ed;tulo sobre manejo de transacciones para m&#x00e1;s
informaci&#x00f3;n. La re-uni&#x00f3;n no es el &#x00fa;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&#x00f3;n autom&#x00e1;tica de estado</title>
<para>
Los usuarios de Hibernate han pedido un m&#x00e9;todo de prop&#x00f3;sito general que bien
salve una instancia transitoria generando un identificador nuevo, o bien
actualice/re&#x00fa;na las instancias separadas asociadas con su identificador actual.
El m&#x00e9;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&#x00e1;ntica de <literal>saveOrUpdate()</literal> parece ser confuso para
usuarios nuevos. Primeramente, en tanto no est&#x00e9;s intentando usar instancias de una
sesi&#x00f3;n en otra sesi&#x00f3;n nueva, no debes necesitar usar <literal>update()</literal>,
<literal>saveOrUpdate()</literal>, o <literal>merge()</literal>. Algunas aplicaciones
enteras nunca usar&#x00e1;n ninguno de estos m&#x00e9;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&#x00f3;n carga un objeto en la primera sesi&#x00f3;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&#x00f3;n hace estas modificaciones persistentes llamando
a <literal>update()</literal> en una segunda sesi&#x00f3;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&#x00f3;n, no hace nada
</para>
</listitem>
<listitem>
<para>
si otro objeto asociado con la sesi&#x00f3;n tiene el mismo identificador,
lanza una excepci&#x00f3;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&#x00e9;n
instanciado, lo salva llamando a <literal>save()</literal>
</para>
</listitem>
<listitem>
<para>
si el objeto est&#x00e1; versionado (por un <literal>&lt;version&gt;</literal> o
<literal>&lt;timestamp&gt;</literal>), y el valor de la propiedad de versi&#x00f3;n
es el mismo valor asignado a una objeto reci&#x00e9;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&#x00f3;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&#x00f3;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&#x00f3;n, permanece separada
</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="objectstate-deleting" revision="1">
<title>Borrando objetos persistentes</title>
<para>
<literal>Session.delete()</literal> quitar&#x00e1; el estado de un objeto de la base de datos.
Por supuesto, tu aplicaci&#x00f3;n podr&#x00ed;a tener a&#x00fa;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&#x00f3;n de clave for&#x00e1;nea. A&#x00fa;n es posible violar una restricci&#x00f3;n
<literal>NOT NULL</literal> sobre una columna clave for&#x00e1;nea borrando objetos
en un orden err&#x00f3;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&#x00e9;nes de datos diferentes</title>
<para>
Es ocasionalmente &#x00fa;til ser capaz de tomar un grafo de instancias persistentes
y hacerlas persistentes en un almac&#x00e9;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&#x00f3;mo <literal>replicate()</literal>
tratar&#x00e1; 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&#x00f3;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&#x00fa;mero
de versi&#x00f3;n es anterior al n&#x00fa;mero de versi&#x00f3;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&#x00f3;n de configuraci&#x00f3;n de
sistema durante actualizaciones de producto, deshacer cambios producidos durante
transacciones no-ACID y m&#x00e1;s.
</para>
</sect1>
<sect1 id="objectstate-flushing">
<title>Limpiando (flushing) la sesi&#x00f3;n</title>
<para>
Cada tanto, la <literal>Session</literal> ejecutar&#x00e1; las sentencias SQL necesarias para
sincronizar el estado de la conexi&#x00f3;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&#x00f3;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&#x00f3;n es que los objetos que usan generaci&#x00f3;n de ID <literal>native</literal>
se insertan cuando son salvados.)
</para>
<para>
Excepto cuando llamas expl&#x00ed;citamente a <literal>flush()</literal>, no hay en absoluto
garant&#x00ed;as sobre <emphasis>cu&#x00e1;ndo</emphasis> la <literal>Session</literal> ejecuta las
llamadas JDBC. s&#x00f3;lo sobre el <emphasis>orden</emphasis> en que son ejecutadas. Sin embargo,
Hibernate garantiza que los m&#x00e9;todos <literal>Query.list(..)</literal> nunca devolver&#x00e1;n datos
a&#x00f1;ejos o err&#x00f3;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&#x00f3;lo en tiempo de compromiso (y s&#x00f3;lo cuando se use la API de
<literal>Transaction</literal> de Hibernate), limpieza autom&#x00e1;tica usando la rutina
explicada, o nunca limpiar a menos que se llame a <literal>flush()</literal>
expl&#x00ed;citamente. El &#x00fa;ltimo modo es &#x00fa;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&#x00f3;n (por ejemplo, si una operaci&#x00f3;n DML
violase una restricci&#x00f3;n). Ya que el manejo de excepciones implica alguna comprensi&#x00f3;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&#x00f3;modo dalvar, borrar, o reunir objetos individuales,
especialmente si tratas con un grafo de objetos asociados. Un caso com&#x00fa;n es
una relaci&#x00f3;n padre/hijo. Considera el siguiente ejemplo:
</para>
<para>
Si los hijos en una relaci&#x00f3;n padre/hijo pudieran ser tipificados en valor
(por ejemplo, una colecci&#x00f3;n de direcciones o cadenas), sus ciclos de vida
depender&#x00ed;an del padre y se requerir&#x00ed;a ninguna otra acci&#x00f3;n para el tratamiento
en "cascada" de cambios de estado. Cuando el padre es salvado, los objetos hijo
tipificados en valor son salvados tambi&#x00e9;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&#x00f3;n. Hibernate detectar&#x00e1; esto y, ya que los objetos tipificados
en valor no pueden tener referencias compartidas, borrar&#x00e1; 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&#x00ed;as e &#x00ed;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&#x00f3;n no significa que sea borrada),
y no hay por defecto ning&#x00fa;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&#x00f3;n b&#x00e1;sica de la sesi&#x00f3;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&#x00f3;n sea tratada en cascada a lo largo de una asociaci&#x00f3;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&#x00f3;n. El por defecto
<literal>cascade="none"</literal> especifica que ninguna operaci&#x00f3;n ser&#x00e1; tratada en cascada.
</para>
<para>
Un estilo de cascada especial, <literal>delete-orphan</literal>, se aplica s&#x00f3;lo a
asociaciones uno-a-muchos, e indica que la operaci&#x00f3;n <literal>delete()</literal> debe
aplicarse a cualquier objeto hijo que sea quitado de la asociaci&#x00f3;n.
</para>
<para>
Recomendaciones:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Usualmente no tiene sentido habilitar el tratamiento en cascada a una asociaci&#x00f3;n
<literal>&lt;many-to-one&gt;</literal> o <literal>&lt;many-to-many&gt;</literal>.
El tratamiento en cascada es frecuentemente &#x00fa;til para las asociaciones
<literal>&lt;one-to-one&gt;</literal> y <literal>&lt;one-to-many&gt;</literal>.
associations.
</para>
</listitem>
<listitem>
<para>
Si la esperanza de vida de los objetos hijos est&#x00e1; ligada a la eesperanza de
vida del objeto padre, h&#x00e1;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&#x00e1;s trabajando frecuentemente con padre e hijos juntos en la
misma transacci&#x00f3;n, y quieres ahorrarte algo de tipeo, considera usar
<literal>cascade="persist,merge,save-update"</literal>.
</para>
</listitem>
</itemizedlist>
<para>
Mapear una asociaci&#x00f3;n (ya sea una asociaci&#x00f3;n monovaluada, o una colecci&#x00f3;n) con
<literal>cascade="all"</literal> marca la asociaci&#x00f3;n como una relaci&#x00f3;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&#x00e1;s, una mera referencia a un hijo desde un padre persistente resultar&#x00e1; en
un save/update del hijo. Esta met&#x00e1;fora est&#x00e1; incompleta, sin embargo. Un hijo
que deje de ser referenciado por su padre <emphasis>no</emphasis> es borrado
autom&#x00e1;ticamente, excepto en el caso de una asociaci&#x00f3;n <literal>&lt;one-to-many&gt;</literal>
mapeada con <literal>cascade="delete-orphan"</literal>. La sem&#x00e1;ntica precisa de
las operaciones en cascada para una relaci&#x00f3;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&#x00f3;n debe
borrar expl&#x00ed;citamente el hijo de ser necesario - a menos que
<literal>cascade="delete-orphan"</literal>, en cuyo caso el hijo
"hu&#x00e9;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 &#x00fa;til para la aplicaci&#x00f3;n misma. Por ejemplo, la aplicaci&#x00f3;n
podr&#x00ed;a usar los metadatos de Hibernate para implementar un algoritmo "inteligente" de copia
en profundidad que entienda qu&#x00e9; objetos deben ser copiados (por ejemplo, tipo de valor mutables)
y cu&#x00e1;les no (por ejemplo, tipos de valor inmutables y, posiblemente, entidades asociadas).
</para>
<para>
Hibernate expone los metadatos v&#x00ed;a las interfaces <literal>ClassMetadata</literal> y
<literal>CollectionMetadata</literal> y la jerarqu&#x00ed;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>