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

1334 lines
69 KiB
XML

<chapter id="performance">
<title>Mejorando el rendimiento</title>
<sect1 id="performance-fetching">
<title>Estrategias de recuperaci&#x00f3;n</title>
<para>
Una <emphasis>estrategia de recuperaci&#x00f3;n</emphasis> es la estrategia que usar&#x00e1; Hibernate para recuperar
los objetos asociados cuando la aplicaci&#x00f3;n necesite navegar la asociaci&#x00f3;n. Las estrategias de recuperaci&#x00f3;n
pueden ser declaradas en los metadatos de mapeo O/R, o sobrescritas por una consulta HQL o
<literal>Criteria</literal> en particular.
</para>
<para>
Hibernate3 define las siguientes estrategias de recuperaci&#x00f3;n:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n por uni&#x00f3;n (join fetching)</emphasis> - Hibernate recupera la
instancia asociada o colecci&#x00f3;n en la misma <literal>SELECT</literal>, usando una
<literal>OUTER JOIN</literal>.
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n por selecci&#x00f3;n (select fetching)</emphasis> - se usa una segunda
<literal>SELECT</literal> para recuperar la entidad asociada o colecci&#x00f3;n. A menos que
deshabilites expl&#x00ed;citamente la recuperaci&#x00f3;n perezosa especificando <literal>lazy="false"</literal>,
la segunda selecci&#x00f3;n s&#x00f3;lo ser&#x00e1; ejecutada cuando realmente accedas a la asociaci&#x00f3;n.
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n por subselecci&#x00f3;n (subselect fetching)</emphasis> - se usa una segunda
<literal>SELECT</literal> para recuperar las colecciones asociadas de todas las entidades
recuperadas en una consulta o recuperaci&#x00f3;n previa. A menos que deshabilites expl&#x00ed;citamente la
recuperaci&#x00f3;n perezosa especificando <literal>lazy="false"</literal>, esta segunda selecci&#x00f3;n s&#x00f3;lo
ser&#x00e1; ejecutada cuando realmente accedas a la asociaci&#x00f3;n.
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n en lote</emphasis> - una estrategia de optimizaci&#x00f3;n para la recuperaci&#x00f3;n
por selecci&#x00f3;n - Hibernate recupera un lote de instancias de entidad o colecciones en una sola
<literal>SELECT</literal>, especificando una lista de claves primarias o de claves for&#x00e1;neas.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate tambi&#x00e9;n distingue entre:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n inmediata</emphasis> - una asociaci&#x00f3;n, colecci&#x00f3;n o atributo es recuperado
inmediatamente, cuando el due&#x00f1;o es cargado.
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n perezosa de colecciones</emphasis> - se recupera una colecci&#x00f3;n cuando la
aplicaci&#x00f3;n invoca una operaci&#x00f3;n sobre la colecci&#x00f3;n. (Esto es por defecto para las colecciones.)
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n por proxy</emphasis> - se recupera una asociaci&#x00f3;n monovaluada cuando se
invoca un m&#x00e9;todo que no sea el getter del identificador sobre el objeto asociado.
</para>
</listitem>
<listitem>
<para>
<emphasis>Recuperaci&#x00f3;n perezosa de atributos</emphasis> - se recupera un atributo o una asociaci&#x00f3;n
monovaluada cuando se accede a la variable de instancia (requiere instrumentaci&#x00f3;n del bytecode en
tiempo de ejecuci&#x00f3;n). Este enfoque es raramente necesario.
</para>
</listitem>
</itemizedlist>
<para>
Aqu&#x00ed; tenemos dos nociones ortogonales: <emphasis>cu&#x00e1;ndo</emphasis> se recupera la aplicaci&#x00f3;n,
y <emphasis>c&#x00f3;mo</emphasis> es recuperada (qu&#x00e9; SQL es usado). &#x00a1;No las confundas! Usamos
<literal>fetch</literal> para afinar el rendimiento. Podemos usar <literal>lazy</literal> para
definir un contrato sobre qu&#x00e9; datos est&#x00e1;n siempre disponibles en cualquier instancia separada de
una clase en particular.
</para>
<sect2 id="performance-fetching-lazy">
<title>Trabajando con asociaciones perezosas</title>
<para>
Por defecto, Hibernate3 usa una recuperaci&#x00f3;n perezosa por selecci&#x00f3;n para colecciones
y una recuperaci&#x00f3;n por proxy perezosa para asociaciones monovaluadas. Estas pol&#x00ed;ticas por
defecto tienen sentido para casi todas las asociaciones en casi todas las aplicaciones.
</para>
<para>
<emphasis>Nota:</emphasis> si estableces <literal>hibernate.default_batch_fetch_size</literal>, Hibernate
usar&#x00e1; la optimizaci&#x00f3;n de recuperaci&#x00f3;n en lotes para recuperaci&#x00f3;n perezosa (esta optimizaci&#x00f3;n tambi&#x00e9;n puede
ser habilitada a un nivel m&#x00e1;s granularizado).
</para>
<para>
Sin embargo, la recuperaci&#x00f3;n perezosa plantea un problema del que tienes que estar al tanto. Acceder
a una asociaci&#x00f3;n perezosa fuera del contexto de una sesi&#x00f3;n de Hibernate abierta resultar&#x00e1; en una
excepci&#x00f3;n. Por ejemplo:
</para>
<programlisting><![CDATA[s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></programlisting>
<para>
Ya que la colecci&#x00f3;n de permisos no fue inicializada cuando se cerr&#x00f3; la <literal>Session</literal>,
la colecci&#x00f3;n no ser&#x00e1; capaz de cargar su estado. <emphasis>Hibernate no soporta la inicializaci&#x00f3;n
perezosa de objetos separados</emphasis>. La soluci&#x00f3;n es mover el c&#x00f3;digo que lee de la colecci&#x00f3;n
a justo antes que la transacci&#x00f3;n sea comprometida.
</para>
<para>
Alternativamente, podr&#x00ed;amos usar una colecci&#x00f3;n no perezosa o asociaci&#x00f3;n, especificando
<literal>lazy="false"</literal> para el mapeo de asociaci&#x00f3;n. Sin embargo, est&#x00e1; pensado
que la inicializaci&#x00f3;n perezosa sea usada para casi todas las colecciones y asociaciones.
&#x00a1;Si defines demasiadas asociaciones no perezosas en tu modelo de objetos, Hibernate terminar&#x00e1;
necesitando recuperar la base de datos entera en cada transacci&#x00f3;n!
</para>
<para>
Por otro lado, frecuentemente necesitamos elegir la recuperaci&#x00f3;n por uni&#x00f3;n (que es no perezosa
por naturaleza) en vez de la recuperaci&#x00f3;n por selecci&#x00f3;n en una transacci&#x00f3;n en particular. Veremos
ahora c&#x00f3;mo personalizar la estrategia de recuperaci&#x00f3;n. En Hibernate3, los mecanismos para elegir una
estrategia de recuperaci&#x00f3;n son id&#x00e9;nticas a las de las asociaciones monovaluadas y colecciones.
</para>
</sect2>
<sect2 id="performance-fetching-custom" revision="3">
<title>Afinando las estrategias de recuperaci&#x00f3;n</title>
<para>
La recuperaci&#x00f3;n por selecci&#x00f3;n (la preestablecida) es extremadamente vulnerable a problemas de
selecci&#x00f3;n N+1, de modo querr&#x00ed;amos habilitar la recuperaci&#x00f3;n por uni&#x00f3;n (join fetching) en el
documento de mapeo:
</para>
<programlisting><![CDATA[<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set]]></programlisting>
<programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting>
<para>
La estrategia de recuperaci&#x00f3;n definida en el documento de mapeo afecta a:
</para>
<itemizedlist>
<listitem>
<para>
las recuperaciones v&#x00ed;a <literal>get()</literal> o <literal>load()</literal>
</para>
</listitem>
<listitem>
<para>
las recuperaciones que ocurren impl&#x00ed;citamente cuando se navega una asociaci&#x00f3;n
(recuperaci&#x00f3;n perezosa)
</para>
</listitem>
<listitem>
<para>
las consultas de <literal>Criteria</literal>
</para>
</listitem>
</itemizedlist>
<para>
Usualmente, no usamos el documento de mapeo para personalizar la recuperaci&#x00f3;n. En cambio, mantenemos el
comportamiento por defecto, y lo sobrescribimos para una transacci&#x00f3;n en particular, usando
<literal>left join fetch</literal> en HQL. Esto le dice a Hibernate que recupere la asociaci&#x00f3;n
tempranamente en la primera selecci&#x00f3;n, usando una uni&#x00f3;n externa. En la API de consulta de
<literal>Criteria</literal>, usar&#x00ed;as <literal>setFetchMode(FetchMode.JOIN)</literal>.
</para>
<para>
Si acaso lo deseases, podr&#x00ed;as cambiar la estrategia de recuperaci&#x00f3;n usada por
<literal>get()</literal> or <literal>load()</literal>; simplemente usa una consulta
<literal>Criteria</literal>, por ejemplo:
</para>
<programlisting><![CDATA[User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();]]></programlisting>
<para>
(Esto es el equivalente de Hibernate de lo que otras soluciones ORM llaman un "plan de recuperaci&#x00f3;n".)
</para>
<para>
Una forma completamente diferente de evitar problemas con selecciones N+1 es usar el cach&#x00e9; de
segundo nivel.
</para>
</sect2>
<sect2 id="performance-fetching-proxies" revision="2">
<title>Proxies de asociaciones de un solo extremo</title>
<para>
La recuperaci&#x00f3;n perezosa de colecciones est&#x00e1; implementada usando la implementaci&#x00f3;n de colecciones
persistentes propia de Hibernate. Sin embargo, se necesita un mecanismo diferente para un comportamiento
perezoso en las asociaciones de un solo extremo. La entidad objetivo de la asociaci&#x00f3;n debe ser tratada
con proxies. Hibernate implementa proxies de inicializaci&#x00f3;n perezosa para objetos persistentes usando
mejora del bytecode en tiempo de ejecuci&#x00f3;n (por medio de la excelente biblioteca CGLIB).
</para>
<para>
Por defecto, Hibernate3 genera proxies (en el arranque) para todas las clases persistentes y los usa
para habilitar la recuperaci&#x00f3;n perezosa de asociaciones <literal>muchos-a-uno</literal> y
<literal>uno-a-uno</literal>.
</para>
<para>
El fichero de mapeo puede declarar una interface a usar como interface de proxy para esa clase,
con el atributo <literal>proxy</literal>. Por defecto, Hibernate usa una subclase de la clase.
<emphasis>Nota que la clase tratada con proxies debe implementar un constructor por defecto con al
menos visibilidad de paquete. &#x00a1;Recomendamos este constructor para todas las clases persistentes!</emphasis>
</para>
<para>
Hay algunos puntos a tener en cuenta al extender este enfoque a clases polim&#x00f3;rficas, por ejemplo.
</para>
<programlisting><![CDATA[<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
Primero, las instancias de <literal>Cat</literal> nunca ser&#x00e1;n objeto de un cast a
<literal>DomesticCat</literal>, incluso aunque la instancia subyacente sea instancia de
<literal>DomesticCat</literal>:
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}]]></programlisting>
<para>
Segundo, es posible romper con el operador <literal>==</literal> de un proxy.
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false]]></programlisting>
<para>
Sin embargo, la situaci&#x00f3;n no en absoluta tan mala como parece. Aunque tenemos ahora dos referencias
a objetos proxy diferentes, la instancia subyacente ser&#x00e1; a&#x00fa;n el mismo objeto:
</para>
<programlisting><![CDATA[cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0]]></programlisting>
<para>
Tercero, no debes usar un proxy CGLIB para una clase <literal>final</literal> o una clase
con alg&#x00fa;n m&#x00e9;todo <literal>final</literal>.
</para>
<para>
Finalmente, si tu objeto persistente adquiere cualquier recurso bajo instanciaci&#x00f3;n
(por ejemplo, en inicializadores o constructores por defecto), entonces esos recursos
ser&#x00e1;n adquiridos tambi&#x00e9;n por el proxy. La clase del proxy es una subclase real de la clase
persistente.
</para>
<para>
Estos problemas se deben a limitaciones fundamentales en el modelo de herencia &#x00fa;nica de Java.
Si deseas evitar estos problemas cada una de tus clases persistentes deben implementar una
interface que declare sus m&#x00e9;todos de negocio. Debes especificar estas interfaces en el fichero
de mapeo. Por ejemplo:
</para>
<programlisting><![CDATA[<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
donde <literal>CatImpl</literal> implementa la interface <literal>Cat</literal> y
<literal>DomesticCatImpl</literal> implementa la interface <literal>DomesticCat</literal>.
Entonces <literal>load()</literal> o <literal>iterate()</literal> pueden devolver instancias de
<literal>Cat</literal> y <literal>DomesticCat</literal>. (Nota que <literal>list()</literal>
usualmente no devuelve proxies.)
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();]]></programlisting>
<para>
Las relaciones tambi&#x00e9;n son inicializadas perezosamente. Esto significa que debes declarar
cualquier propiedad como de tipo <literal>Cat</literal>, no <literal>CatImpl</literal>.
</para>
<para>
Ciertas operaciones <emphasis>no</emphasis> requieren inicializaci&#x00f3;n de proxies.
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>equals()</literal>, si la clase persistente no sobrescribe <literal>equals()</literal>
</para>
</listitem>
<listitem>
<para>
<literal>hashCode()</literal>, si la clase persistente no sobrescribe
<literal>hashCode()</literal>
</para>
</listitem>
<listitem>
<para>
El m&#x00e9;todo getter del identificador
</para>
</listitem>
</itemizedlist>
<para>
Hibernate detectar&#x00e1; las clase persistentes que sobrescriban <literal>equals()</literal> o
<literal>hashCode()</literal>.
</para>
</sect2>
<sect2 id="performance-fetching-initialization">
<title>Inicializando colecciones y proxies</title>
<para>
Una <literal>LazyInitializationException</literal> ser&#x00e1; lanzada por Hibernate si una colecci&#x00f3;n
o proxy sin inicializar es accedido fuera del &#x00e1;mbito de la <literal>Session</literal>, es decir,
cuando la entidad que posee la colecci&#x00f3;n o que tiene la referencia al proxy est&#x00e9; en el estado
separada.
</para>
<para>
A veces necesitamos asegurarnos que un proxy o colecci&#x00f3;n est&#x00e9; inicializado antes de cerrar la
<literal>Session</literal>. Por supuesto, siempre podemos forzar la inicializaci&#x00f3;n llamando a
<literal>cat.getSex()</literal> o <literal>cat.getKittens().size()</literal>, por ejemplo.
Pero esto es confuso a lectores del c&#x00f3;digo y no es conveniente para c&#x00f3;digo gen&#x00e9;rico.
</para>
<para>
Los m&#x00e9;todos est&#x00e1;ticos <literal>Hibernate.initialize()</literal> y
<literal>Hibernate.isInitialized()</literal> proveen a la aplicaci&#x00f3;n de una forma conveniente de
trabajar con colecciones o proxies inicializados perezosamente.
<literal>Hibernate.initialize(cat)</literal> forzar&#x00e1; la inicializaci&#x00f3;n de un proxy,
<literal>cat</literal>, en tanto su <literal>Session</literal> est&#x00e9; todav&#x00ed;a abierta.
<literal>Hibernate.initialize( cat.getKittens() )</literal> tiene un efecto similar para la colecci&#x00f3;n
de gatitos.
</para>
<para>
Otra opci&#x00f3;n es mantener la <literal>Session</literal> abierta hasta que todas las colecciones
y proxies necesarios hayan sido cargados. En algunas arquitecturas de aplicaci&#x00f3;n, particularmente
en aquellas donde el c&#x00f3;digo que accede a los datos usando Hibernate, y el c&#x00f3;digo que los usa est&#x00e1;n
en capas de aplicaci&#x00f3;n diferentes o procesos f&#x00ed;sicos diferentes, puede ser un problema asegurar que
la <literal>Session</literal> est&#x00e9; abierta cuando se inicializa una colecci&#x00f3;n. Existen dos formas
b&#x00e1;sicas de tratar este tema:
</para>
<itemizedlist>
<listitem>
<para>
En una aplicaci&#x00f3;n basada web, puede usarse un filtro de servlets para cerrar la
<literal>Session</literal> s&#x00f3;lo bien al final de una petici&#x00f3;n de usuario, una
vez que el rendering de la vista est&#x00e9; completa (el patr&#x00f3;n <emphasis>Sesi&#x00f3;n Abierta en
Vista (Open Session in View)</emphasis>). Por supuesto, estos sitios requieren una
fuerte demanda de correcci&#x00f3;n del manejo de excepciones de tu infraestructura de
aplicaci&#x00f3;n. Es de una importancia vital que la <literal>Session</literal> est&#x00e9;
cerrada y la transacci&#x00f3;n terminada antes de volver al usuario, incluso cuando ocurra
una excepci&#x00f3;n durante el rendering de la p&#x00e1;gina. Para este enfoque, el filtro de servlet tiene
que ser capaz de accceder la <literal>Session</literal>. Recomendamos que se use una variable
<literal>ThreadLocal</literal> para tener la <literal>Session</literal> actual (ver
el cap&#x00ed;tulo 1, <xref linkend="quickstart-playingwithcats"/>, para una implementaci&#x00f3;n de ejemplo).
</para>
</listitem>
<listitem>
<para>
En una aplciaci&#x00f3;n con una grada de negocios separada, la l&#x00f3;gica de negocio debe "preparar"
todas las colecciones que se vayan a necesitar por la grada web antes de volver.
Esto significa que la grada de negocios debe cargar todos los datos y devolver a la grada de
presentaci&#x00f3;n web todos los datos que se requieran para un caso de uso en particular
ya inicializados. Usualmente, la aplicaci&#x00f3;n llama a <literal>Hibernate.initialize()</literal>
para cada colecci&#x00f3;n que se necesitar&#x00e1; en la grada web (esta llamada debe ocurrir antes que la
sesi&#x00f3;n sea cerrada) o recupera la colecci&#x00f3;n tempranamente usando una consulta de Hibernate con una
cl&#x00e1;usula <literal>FETCH</literal> o una <literal>FetchMode.JOIN</literal> en
<literal>Criteria</literal>. Esto es usualmente m&#x00e1;s f&#x00e1;cil si adoptas el patr&#x00f3;n
<emphasis>Comando</emphasis> en vez de un <emphasis>Fachada de Sesi&#x00f3;n</emphasis>.
</para>
</listitem>
<listitem>
<para>
Puedes tambi&#x00e9;n adjuntar un objeto cargado previamente a una nueva <literal>Session</literal>
con <literal>merge()</literal> o <literal>lock()</literal> antes de acceder a colecciones
no inicializadas (u otros proxies). &#x00a1;No, Hibernate no, y ciertamente <emphasis>no
debe</emphasis> hacer esto autom&#x00e1;ticamente, ya que introducir&#x00ed;a sem&#x00e1;nticas de transacci&#x00f3;n ad hoc!
</para>
</listitem>
</itemizedlist>
<para>
A veces no quieres inicializar una colecci&#x00f3;n grande, pero necesitas a&#x00fa;n alguna informacion sobre
ella (como su tama&#x00f1;o) o un subconjunto de los datos.
</para>
<para>
Puedes usar un filtro de colecciones para obtener el tama&#x00f1;o de una colecci&#x00f3;n sin inicializarla:
</para>
<programlisting><![CDATA[( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()]]></programlisting>
<para>
El m&#x00e9;todo <literal>createFilter()</literal> se usa tambi&#x00e9;n para recuperar eficientemente subconjuntos
de una colecci&#x00f3;n sin necesidad de inicializar toda la colecci&#x00f3;n:
</para>
<programlisting><![CDATA[s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();]]></programlisting>
</sect2>
<sect2 id="performance-fetching-batch">
<title>Usando recuperaci&#x00f3;n en lotes</title>
<para>
Hibernate puede hacer un uso eficiente de la recuperaci&#x00f3;n en lotes, esto es, Hibernate puede cargar
muchos proxies sin inicializar si se accede a un proxy (o colecciones). La recuperaci&#x00f3;n en lotes es una
optimizaci&#x00f3;n de la estrategia de recuperaci&#x00f3;n por selecci&#x00f3;n perezosa. Hay dos formas en que puedes afinar
la recuperaci&#x00f3;n en lotes: a nivel de la clase o de la colecci&#x00f3;n.
</para>
<para>
La recuperaci&#x00f3;n en lotes para clases/entidades es m&#x00e1;s f&#x00e1;cil de entender. Imagina que tienes la siguiente
situaci&#x00f3;n en tiempo de ejecuci&#x00f3;n: Tienes 25 instancias de <literal>Cat</literal> cargadas en una
<literal>Session</literal>, cada <literal>Cat</literal> tiene una referencia a su <literal>owner</literal>,
una <literal>Person</literal>. La clase <literal>Person</literal> est&#x00e1; mapeada con un proxy,
<literal>lazy="true"</literal>. Si ahora iteras a trav&#x00e9;s de todos los gatos y llamas a
<literal>getOwner()</literal> para cada uno, Hibernate por defecto ejecutar&#x00e1; 25 sentencias
<literal>SELECT</literal> para traer los due&#x00f1;os tratados con proxies. Puedes afinar este comportamiento
especificando un <literal>batch-size</literal> en el mapeo de <literal>Person</literal>:
</para>
<programlisting><![CDATA[<class name="Person" batch-size="10">...</class>]]></programlisting>
<para>
Hibernate ahora ejecutar&#x00e1; s&#x00f3;lo tres consultas, el patr&#x00f3;n es 10, 10, 5.
</para>
<para>
Tambi&#x00e9;n puedes habilitar la recuperaci&#x00f3;n en lotes para colecciones. Por ejemplo, si cada
<literal>Person</literal> tiene una colecci&#x00f3;n perezosa de <literal>Cat</literal>s, y hay 10
personas actualmente cargadas en la <literal>Session</literal>, iterar a trav&#x00e9;s de las 10 personas
generar&#x00e1; 10 <literal>SELECT</literal>s, una para cada llamada a <literal>getCats()</literal>.
Si habilitas la recuperaci&#x00f3;n en lotes para la colecci&#x00f3;n de <literal>cats</literal> en el mapeo de
<literal>Person</literal>, Hibernate puede recuperar por adelantado las colecciones:
</para>
<programlisting><![CDATA[<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>]]></programlisting>
<para>
Con un <literal>batch-size</literal> de 3, Hibernate cargar&#x00e1; 3, 3, 3, 1 colecciones en cuatro
<literal>SELECT</literal>s. Una vez m&#x00e1;s, el valor del atributo depende del n&#x00fa;mero esperado de
colecciones sin inicializar en una <literal>Session</literal> en particular.
</para>
<para>
La recuperaci&#x00f3;n de coleccione en lotes es particularmente &#x00fa;til si tienes un &#x00e1;rbol anidado de
&#x00ed;tems, es decir, el t&#x00ed;pico patr&#x00f3;n de cuenta de materiales. (Aunque un <emphasis>conjunto
anidado</emphasis> o una <emphasis>ruta materializada</emphasis> podr&#x00ed;a ser una mejor opci&#x00f3;n
para &#x00e1;rboles que sean de lectura en la mayor&#x00ed;a de los casos.)
</para>
</sect2>
<sect2 id="performance-fetching-subselect">
<title>Usando recuperaci&#x00f3;n por subselecci&#x00f3;n</title>
<para>
Si una colecci&#x00f3;n perezosa o proxy monovaluado tiene que ser recuperado, Hibernate los carga a todos,
volviendo a ejecutar la consulta original en una subselecci&#x00f3;n. Esto funciona de la misma forma que
la recuperaci&#x00f3;n en lotes, sin carga fragmentaria.
</para>
<!-- TODO: Write more about this -->
</sect2>
<sect2 id="performance-fetching-lazyproperties">
<title>Usando recuperaci&#x00f3;n perezosa de propiedades</title>
<para>
Hibernate3 soporta la recuperaci&#x00f3;n perezosa de propiedades individuales. Esta t&#x00e9;cnica de optimizaci&#x00f3;n
es tambi&#x00e9;n conocida como <emphasis>grupos de recuperaci&#x00f3;n (fetch groups)</emphasis>. Por favor, nota
que &#x00e9;ste es mayormente un aspecto de marketing, ya que en la pr&#x00e1;ctica, optimizar lecturas de filas es
mucho m&#x00e1;s importante que la optimizaci&#x00f3;n de lectura de columnas. Sin embargo, cargar s&#x00f3;lo algunas
propiedades de una clase podr&#x00ed;a ser &#x00fa;til en casos extremos, cuando tablas heredadas tienen cientos de
columnas y el modelo de datos no puede ser mejorado.
</para>
<para>
Para habilitar la carga perezosa de propiedades, establece el atributo <literal>lazy</literal> en tus
mapeos de propiedades:
</para>
<programlisting><![CDATA[<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>]]></programlisting>
<para>
&#x00a1;La carga perezosa de propiedades requiere la instrumentaci&#x00f3;n del bytecode en tiempo
de construcci&#x00f3;n! Si tus clases persistentes no son mejoradas, Hibernate ignorar&#x00e1; silenciosamente
la configuraci&#x00f3;n perezosa de propiedades y caer&#x00e1; en recuperaci&#x00f3;n inmediata.
</para>
<para>
Para la instrumentaci&#x00f3;n del bytecode, usa la siguiente tarea Ant:
</para>
<programlisting><![CDATA[<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>]]></programlisting>
<para>
Una forma diferente (&#x00bf;mejor?) de evitar lecturas innecesarias de columnas, al menos para
transacciones de s&#x00f3;lo lectura es usar las funcionalidades de proyecci&#x00f3;n de consultas HQL o Criteria.
Esto evita la necesidad de procesar el bytecode en tiempo de construcci&#x00f3;n y ciertamente es una soluci&#x00f3;n
preferida.
</para>
<para>
Puedes forzar la usual recuperaci&#x00f3;n temprana de propiedades usando <literal>fetch all properties</literal>
en HQL.
</para>
</sect2>
</sect1>
<sect1 id="performance-cache" revision="1">
<title>El Cach&#x00e9; de Segundo Nivel</title>
<para>
Una <literal>Session</literal> de Hibernate es una cach&#x00e9; de datos persistentes a nivel de transacci&#x00f3;n.
Es posible configurar un cluster o cach&#x00e9; a nivel de JVM (a nivel de <literal>SessionFactory</literal>)
sobre una base de clase-a-clase o colecci&#x00f3;n-a-colecci&#x00f3;n. Puedes incluso enchufar una cach&#x00e9; en cluster.
S&#x00e9; cuidadoso. Las cach&#x00e9;s nunca est&#x00e1;n al tanto de los cambios hechos por otra aplicaci&#x00f3;n al almac&#x00e9;n persistente
(aunque pueden ser configurados para expirar regularmente los datos en cach&#x00e9;).
</para>
<para>
Por defecto, Hibernate usa EHCache para caching a nivel de JVM. (El soporte a JCS ahora est&#x00e1; despreciado
y ser&#x00e1; quitado en una futura versi&#x00f3;n de Hibernate.) Puedes elegir una implementaci&#x00f3;n diferente estableciendo
el nombre de una clase que implemente <literal>org.hibernate.cache.CacheProvider</literal> usando la propiedad
<literal>hibernate.cache.provider_class</literal>.
</para>
<table frame="topbot" id="cacheproviders" revision="1">
<title>Proveedores de Cach&#x00e9;</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="3*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>Cach&#x00e9;</entry>
<entry>clase del Provedor</entry>
<entry>Tipo</entry>
<entry>Cluster Seguro</entry>
<entry>Cach&#x00e9; de Consultas Soportado</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (no pensado para uso en producci&#x00f3;n)</entry>
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
<entry>memoria</entry>
<entry></entry>
<entry>s&#x00ed;</entry>
</row>
<row>
<entry>EHCache</entry>
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
<entry>memoria, disco</entry>
<entry></entry>
<entry>s&#x00ed;</entry>
</row>
<row>
<entry>OSCache</entry>
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
<entry>memoria, disco</entry>
<entry></entry>
<entry>s&#x00ed;</entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry><literal>org.hibernate.cache.SwarmCacheProvider</literal></entry>
<entry>clusterizado (ip multicast)</entry>
<entry>s&#x00ed; (invalidaci&#x00f3;n en cluster)</entry>
<entry></entry>
</row>
<row>
<entry>TreeCache de JBoss</entry>
<entry><literal>org.hibernate.cache.TreeCacheProvider</literal></entry>
<entry>clusterizado (ip multicast), transaccional</entry>
<entry>s&#x00ed; (replicaci&#x00f3;n)</entry>
<entry>s&#x00ed; (requiere sincronizaci&#x00f3;n de reloj)</entry>
</row>
</tbody>
</tgroup>
</table>
<sect2 id="performance-cache-mapping">
<title>Mapeos de cach&#x00e9;</title>
<para>
El elemento <literal>&lt;cache&gt;</literal> de una mapeo de clase o colecci&#x00f3;n tiene la siguiente
forma:
</para>
<programlistingco>
<areaspec>
<area id="cache1" coords="2 70"/>
</areaspec>
<programlisting><![CDATA[<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
/>]]></programlisting>
<calloutlist>
<callout arearefs="cache1">
<para>
<literal>usage</literal> especifica la estrategia de caching:
<literal>transactional</literal>,
<literal>read-write</literal>,
<literal>nonstrict-read-write</literal> o
<literal>read-only</literal>
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
Alternativamente (&#x00bf;preferiblemente?), puedes especificar los elementos
<literal>&lt;class-cache&gt;</literal> y <literal>&lt;collection-cache&gt;</literal> en
<literal>hibernate.cfg.xml</literal>.
</para>
<para>
El atributo <literal>usage</literal> especifica una <emphasis>estrategia de concurrencia al
cach&#x00e9;</emphasis>.
</para>
</sect2>
<sect2 id="performance-cache-readonly">
<title>Estrategia: s&#x00f3;lo lectura (read only)</title>
<para>
Si tu aplicaci&#x00f3;n necesita leer pero nunca modificar las instancias de una clase persistente,
puede usarse un cach&#x00e9; <literal>read-only</literal>. Esta es la mejor y m&#x00e1;s simple estrategia.
Es incluso perfectamente segura de usar en un cluster.
</para>
<programlisting><![CDATA[<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-readwrite">
<title>Estrategia: lectura/escritura (read/write)</title>
<para>
Si la aplicaci&#x00f3;n necesita actualizar datos, un cach&#x00e9; <literal>read-write</literal> podr&#x00ed;a ser apropiado.
Esta estrategia de cach&#x00e9; nunca debe ser usada si se requiere nivel de aislamiento serializable de
transacciones. Si el cach&#x00e9; es usado en un entorno JTA, debes especificar la propiedad
<literal>hibernate.transaction.manager_lookup_class</literal>, mencionando una estrategia para obtener
el <literal>TransactionManager</literal> de JTA. En otros entornos, debes asegurarte que la transacci&#x00f3;n
est&#x00e9; completada cuando se llame a <literal>Session.close()</literal> o
<literal>Session.disconnect()</literal>. Si deseas usar esta estrategia en un cluster, debes asegurarte
que la implementaci&#x00f3;n de cach&#x00e9; subyacente soporta bloqueos. Los provedores de cach&#x00e9; internos
predeterminados <emphasis>no</emphasis> no lo soportan.
</para>
<programlisting><![CDATA[<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-nonstrict">
<title>Estrategia: lectura/escritura no estricta (nonstrict read/write)</title>
<para>
Si la aplicaci&#x00f3;n necesita s&#x00f3;lo ocasionalmente actualizar datos (es decir, es extremadamente inprobable
que dos transacciones intenten actualizar el mismo &#x00ed;tem simult&#x00e1;neamente) y no se requiere de un
aislamiento de transacciones estricto, un cach&#x00e9; <literal>nonstrict-read-write</literal> podr&#x00ed;a ser
apropiado. Si se usa el cach&#x00e9; en un entorno JTA, debes especificar
<literal>hibernate.transaction.manager_lookup_class</literal>. En otros entornos, debes asegurarte que la
transacci&#x00f3;n se haya completado cuando se llame a <literal>Session.close()</literal> o
<literal>Session.disconnect()</literal>.
</para>
</sect2>
<sect2 id="performance-cache-transactional">
<title>Estrategia: transaccional</title>
<para>
La estrategia de cach&#x00e9; <literal>transactional</literal> brinda soporte a provedores de cach&#x00e9;s
completamente transaccionales como TreeCache de JBoss. Un cach&#x00e9; as&#x00ed;, puede s&#x00f3;lo ser usado en un
entorno JTA y debes especificar <literal>hibernate.transaction.manager_lookup_class</literal>.
</para>
</sect2>
<para>
Ninguno de los provedores de cach&#x00e9; soporta todas las estrategias de concurrencia al cach&#x00e9;. La siguiente
tabla muestra qu&#x00e9; provedores son compatibles con qu&#x00e9; estrategias de concurrencia.
</para>
<table frame="topbot">
<title>Soporte a Estrategia de Concurrencia a Cach&#x00e9;</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="1*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>Cach&#x00e9;</entry>
<entry>read-only</entry>
<entry>nonstrict-read-write</entry>
<entry>read-write</entry>
<entry>transactional</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (no pensado para uso en producci&#x00f3;n)</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry></entry>
</row>
<row>
<entry>EHCache</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry></entry>
</row>
<row>
<entry>OSCache</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry></entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry>s&#x00ed;</entry>
<entry>s&#x00ed;</entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>JBoss TreeCache</entry>
<entry>s&#x00ed;</entry>
<entry></entry>
<entry></entry>
<entry>s&#x00ed;</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="performance-sessioncache" revision="2">
<title>Gestionando los cach&#x00e9;s</title>
<para>
Siempre que pases un objeto a <literal>save()</literal>, <literal>update()</literal>
o <literal>saveOrUpdate()</literal> y siempre que recuperes un objeto usando
<literal>load()</literal>, <literal>get()</literal>, <literal>list()</literal>,
<literal>iterate()</literal> o <literal>scroll()</literal>, ese objeto es agregado
al cach&#x00e9; interno de la <literal>Session</literal>.
</para>
<para>
Cuando subsecuentemente se llame a <literal>flush()</literal>, el estado de ese objeto ser&#x00e1;
sincronizado con la base de datos. Si no quieres que ocurra esta sincronizaci&#x00f3;n o si est&#x00e1;s
procesando un n&#x00fa;mero enorme de objetos y necesitas gestionar la memoria eficientemente,
puede usarse el m&#x00e9;todo <literal>evict()</literal> para quitar el objeto y sus colecciones
del cach&#x00e9; de primer nivel.
</para>
<programlisting><![CDATA[ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}]]></programlisting>
<para>
La <literal>Session</literal> tambi&#x00e9;n provee un m&#x00e9;todo <literal>contains()</literal> para determinar
si una instancia pertenece al cach&#x00e9; de la sesi&#x00f3;n.
</para>
<para>
Para desahuciar (evict) todos los objetos del cach&#x00e9; de sesi&#x00f3;n, llama a <literal>Session.clear()</literal>.
</para>
<para>
Para el cach&#x00e9; de segundo nivel, hay m&#x00e9;todos definidos en <literal>SessionFactory</literal> para
desahuciar el estado en cach&#x00e9; de una instancia, clase entera, instancia de colecci&#x00f3;n o rol
enter de colecci&#x00f3;n.
</para>
<programlisting><![CDATA[sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections]]></programlisting>
<para>
El <literal>CacheMode</literal> controla c&#x00f3;mo una sesi&#x00f3;n en particular interact&#x00fa;a con el cach&#x00e9; de segundo
nivel.
</para>
<itemizedlist>
<listitem>
<para>
<literal>CacheMode.NORMAL</literal> - lee &#x00ed;tems desde y escribe &#x00ed;tems hacia el cach&#x00e9; de segundo nivel
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.GET</literal> - lee &#x00ed;tems del cach&#x00e9; de segundo nivel, pero no escribe al cach&#x00e9; de
segundo nivel excepto al actualizar datos
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.PUT</literal> - escribe &#x00ed;tems al cach&#x00e9; de segundo nivel, pero no lee del cach&#x00e9; de segundo
nivel
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.REFRESH</literal> - escribe &#x00ed;tems al cach&#x00e9; de segundo nivel, pero no lee del cach&#x00e9; de
segundo nivel, salt&#x00e1;ndose el efecto de <literal>hibernate.cache.use_minimal_puts</literal>, forzando
un refresco del cach&#x00e9; de segundo nivel para todos los &#x00ed;tems le&#x00ed;dos de la base de datos
</para>
</listitem>
</itemizedlist>
<para>
Para navegar por los contenidos de una regi&#x00f3;n de cach&#x00e9; de segundo nivel o de consultas, usa la API de
<literal>Statistics</literal>:
</para>
<programlisting><![CDATA[Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();]]></programlisting>
<para>
Necesitar&#x00e1;s habilitar las estad&#x00ed;sticas y, opcionalmente, forzar a Hibernate para que guarde las
entradas del cach&#x00e9; en un formato m&#x00e1;s entendible por humanos:
</para>
<programlisting><![CDATA[hibernate.generate_statistics true
hibernate.cache.use_structured_entries true]]></programlisting>
</sect1>
<sect1 id="performance-querycache" revision="1">
<title>El Cach&#x00e9; de Consultas</title>
<para>
Los conjuntos resultado de consultas tambi&#x00e9;n pueden tratarse en cach&#x00e9;. Esto s&#x00f3;lo es &#x00fa;til para
consultas que se ejecutan frecuentemente con los mismos par&#x00e1;metros. Para usar el cach&#x00e9; de consultas
primero debes habilitarlo:
</para>
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
<para>
Esta configuraci&#x00f3;n causa la creaci&#x00f3;n de dos nuevas regiones de cach&#x00e9; - una teniendo en cach&#x00e9;
conjuntos resultado de consulta (<literal>org.hibernate.cache.StandardQueryCache</literal>),
el otro teniendo timestamps de las actualizaciones m&#x00e1;s recientes a tablas consultables
(<literal>org.hibernate.cache.UpdateTimestampsCache</literal>). Nota que el cach&#x00e9; de consultas
no pone en cach&#x00e9; el estado de las entidades reales en el conjunto resultado; s&#x00f3;lo tiene en cach&#x00e9;
valores indentificadores y resultados de tipo de valor. De modo que el cach&#x00e9; de consultas siempre
debe ser usado en conjunci&#x00f3;n con el cach&#x00e9; de segundo nivel.
</para>
<para>
La mayor&#x00ed;a de consultas no se benefician del tratamiento en cach&#x00e9;, de modo que por defecto las
consultas no son tratadas en cach&#x00e9;. Para habilitar el tratamiento en cach&#x00e9;, llama a
<literal>Query.setCacheable(true)</literal>. Esta llamada permite a la consulta buscar
resultados existentes en cach&#x00e9; o agregar sus resultados al cach&#x00e9; cuando se ejecuta.
</para>
<para>
Si requieres un control finamente granularizado sobre las pol&#x00ed;ticas de expiraci&#x00f3;n del cach&#x00e9; de
consultas, puedes especificar una regi&#x00f3;n de cach&#x00e9; con nombre para una consulta en particular
llamando a <literal>Query.setCacheRegion()</literal>.
</para>
<programlisting><![CDATA[List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();]]></programlisting>
<para>
Si la consulta debe forzar un refresco de si regi&#x00f3;n del cach&#x00e9; de consultas, debes llamar a
<literal>Query.setCacheMode(CacheMode.REFRESH)</literal>. Esto es particularmente &#x00fa;til en casos donde
los datos subyacentes pueden haber sido actualizados por medio de un proceso separado (es decir,
no modificados a trav&#x00e9;s de Hibernate) y permite a la aplicaci&#x00f3;n refrescar selectivamente conjuntos
resultado de consultas en particular. Esto es una alternativa m&#x00e1;s eficient al desahuciamiento de una
regi&#x00f3;n del cach&#x00e9; de consultas v&#x00ed;a <literal>SessionFactory.evictQueries()</literal>.
</para>
</sect1>
<sect1 id="performance-collections">
<title>Entendiendo el rendimiento de Colecciones</title>
<para>
Ya hemos llevado un buen tiempo hablando sobre colecciones.
En esta secci&#x00f3;n resaltaremos un par de temas m&#x00e1;s sobre c&#x00f3;mo las colecciones
se comportan en tiempo de ejecuci&#x00f3;n.
</para>
<sect2 id="performance-collections-taxonomy">
<title>Taxonomia</title>
<para>Hibernate define tres tipos b&#x00e1;sicos de colecciones:</para>
<itemizedlist>
<listitem>
<para>colecciones de valores</para>
</listitem>
<listitem>
<para>asociaciones uno a muchos</para>
</listitem>
<listitem>
<para>asociaciones muchos a muchos</para>
</listitem>
</itemizedlist>
<para>
Esta clasificaci&#x00f3;n distingue las varias tablas y relaciones de clave for&#x00e1;nea pero no nos
dice absolutamente todo lo que necesitamos saber sobre el modelo relacional. Para entender
completamente la estructura relacional y las caracter&#x00ed;sticas de rendimiento, debemos considerar
la estructura de la clave primaria que es usada por Hibernate para actualizar o borrar filas de
colecci&#x00f3;n. Esto sugiere la siguiente clasificaci&#x00f3;n:
</para>
<itemizedlist>
<listitem>
<para>colecciones indexadas</para>
</listitem>
<listitem>
<para>conjuntos (sets)</para>
</listitem>
<listitem>
<para>bolsas (bags)</para>
</listitem>
</itemizedlist>
<para>
Todas las colecciones indexadas (mapas, listas, arrays) tienen una clave primaria
consistente de las columnas <literal>&lt;key&gt;</literal> y <literal>&lt;index&gt;</literal>.
En este caso las actualizaciones de colecciones son usualmente extremadamente eficientes.
La clave primaria puede ser indexada f&#x00e1;cilmente y una fila en particular puede ser localizada
cuando Hibernate intenta actualizarla o borrarla.
</para>
<para>
Los conjuntos (sets) tienen una clave primaria consistente en <literal>&lt;key&gt;</literal>
y columnas de elemento. Esto puede ser menos eficiente para algunos tipos de elemento de
colecci&#x00f3;n, particularmente elementos compuestos o texto largo, o campos binarios. La base de datos
puede no ser capaz de indexar una clave primaria compleja eficientemente. Por otra parte,
para asociaciones uno a muchos o muchos a muchos, particularmente en el caso de identificadores
sint&#x00e9;ticos, es probable que s&#x00f3;lo sea tan eficiente. (Nota al m&#x00e1;rgen: si quieres que
<literal>SchemaExport</literal> realmente cree la clave primaria de un <literal>&lt;set&gt;</literal>
por ti, debes declarar todas las columnas como <literal>not-null="true"</literal>.)
</para>
<para>
Los mapeos de <literal>&lt;idbag&gt;</literal> definen una clave delegada, de modo que siempre
resulten eficientes de actualizar. De hecho, son el mejor caso.
</para>
<para>
Los bags son el peor caso. Ya que un bag permite valores de elementos duplicados y no tiene
ninguna columna &#x00ed;ndice, no puede definirse ninguna clave primaria. Hibernate no tiene forma de
distinguir entre filas duplicadas. Hibernate resuelve este problema quitando completamente
(en un solo <literal>DELETE</literal>) y recreando la colecci&#x00f3;n siempre que cambia.
Esto podr&#x00ed;a ser muy ineficiente.
</para>
<para>
Nota que para una asociaci&#x00f3;n uno-a-muchos, la "clave primaria" puede no ser la clave
primaria f&#x00ed;sica de la tabla de base de datos; pero incluso en este caso, la clasificaci&#x00f3;n
anterior es &#x00fa;til todav&#x00ed;a. (A&#x00fa;n refleja c&#x00f3;mo Hibernate "localiza" filas individuales de la
colecci&#x00f3;n.)
</para>
</sect2>
<sect2 id="performance-collections-mostefficientupdate">
<title>Las listas, mapas, idbags y conjuntos son las colecciones m&#x00e1;s eficientes de actualizar</title>
<para>
Desde la discusi&#x00f3;n anterior, debe quedar claro que las colecciones indexadas y
(usualmente) los conjuntos permiten la operaci&#x00f3;n m&#x00e1;s eficiente en t&#x00e9;rminos de a&#x00f1;adir,
quitar y actualizar elementos.
</para>
<para>
Hay, discutiblemente, una ventaja m&#x00e1;s que las colecciones indexadas tienen sobre otros
conjuntos para las asociaciones muchos a muchos o colecciones de valores. Debido a la
estructura de un <literal>Set</literal>, Hibernate ni siquiera actualiza una fila con
<literal>UPDATE</literal> cuando se "cambia" un elemento. Los cambios a un <literal>Set</literal>
siempre funcionan por medio de <literal>INSERT</literal> y <literal>DELETE</literal>
(de filas individuales). Una vez m&#x00e1;s, esta consideraci&#x00f3;n no se aplica a las asociaciones
uno a muchos.
</para>
<para>
Despu&#x00e9;s de observar que los arrays no pueden ser perezosos, podr&#x00ed;amos concluir que las
listas, mapas e idbags son los tipos m&#x00e1;s eficientes de colecciones (no inversas), con los
conjuntos (sets) no muy por detr&#x00e1;s. Se espera que los sets sean el tipo m&#x00e1;s com&#x00fa;n de colecci&#x00f3;n
en las aplicaciones de Hibernate. Esto es debido a que la sem&#x00e1;ntica de los sets es la m&#x00e1;s
natural en el modelo relacional.
</para>
<para>
Sin embargo, en modelos de dominio de Hibernate bien die&#x00f1;ados, usualmente vemos que la mayor&#x00ed;a
de las colecciones son de hecho asociaciones uno-a-muchos con <literal>inverse="true"</literal>.
Para estas asociaciones, la actualizaci&#x00f3;n es manejada por el extremo muchos-a-uno de la asociaci&#x00f3;n,
y las consideraciones de este tipo sobre el rendimiento de actualizaci&#x00f3;n de colecciones simplemente
no se aplican.
</para>
</sect2>
<sect2 id="performance-collections-mostefficentinverse">
<title>Los Bags y las listas son las colecciones inversas m&#x00e1;s eficientes</title>
<para>
Justo antes que tires a la zanja los bags para siempre, hay un caso en particular en el que
los bags son muchos m&#x00e1;s eficientes que los conjuntos. Para una colecci&#x00f3;n con
<literal>inverse="true"</literal> (el idioma est&#x00e1;ndar de relaciones uno-a-muchos bidireccionales,
por ejemplo) &#x00a1;podemos a&#x00f1;adir elementos a un bag o lista sin necesidad de inicializar (fetch)
los elementos del bag! Esto se debe a que <literal>Collection.add()</literal> o
<literal>Collection.addAll()</literal> siempre deben devolver true para un bag o <literal>List</literal>
(no como un <literal>Set</literal>). Esto puede hacer el siguiente c&#x00f3;digo com&#x00fa;n mucho m&#x00e1;s r&#x00e1;pido.
</para>
<programlisting><![CDATA[Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();]]></programlisting>
</sect2>
<sect2 id="performance-collections-oneshotdelete">
<title>Borrado de un solo tiro</title>
<para>
Ocasionalmente, borrar los elementos de una colecci&#x00f3;n uno a uno puede ser extremadamente ineficiente.
Hibernate no es completamente est&#x00fa;pido, de modo que sabe no hacer eso, en el caso de una colecci&#x00f3;n
nueva-vac&#x00ed;a (si has llamado a <literal>list.clear()</literal>, por ejemplo). En este caso, Hibernate
publicar&#x00e1; una sola <literal>DELETE</literal>, &#x00a1;y listo!
</para>
<para>
Sup&#x00f3;n que a&#x00f1;adimos un solo elemento a una colecci&#x00f3;n de tama&#x00f1;o veinte y luego quitamos dos elementos.
Hibernate publicar&#x00e1; una sentencia <literal>INSERT</literal> y dos sentencias <literal>DELETE</literal>
(a menos que la colecci&#x00f3;n sea un bag). Esto es ciertamente deseable.
</para>
<para>
Sin embargo, sup&#x00f3;n que quitamos dieciocho elementos, dejando dos y luego a&#x00f1;adimos tres nuevos elementos.
Hay dos formas posibles de proceder
</para>
<itemizedlist>
<listitem>
<para>borrar dieciocho filas una a una y luego insertar tres filas</para>
</listitem>
<listitem>
<para>quitar toda la colecci&#x00f3;n (en un solo <literal>DELETE</literal> de SQL) e insertar todos los
cinco elementos actuales (uno a uno)</para>
</listitem>
</itemizedlist>
<para>
Hibernate no es lo suficientemente inteligente para saber que la segunda opci&#x00f3;n es probablemente m&#x00e1;s
r&#x00e1;pida en este caso. (Y que ser&#x00ed;a probablemente indeseable para Hibernate ser tan inteligente;
este comportamiento podr&#x00ed;a confundir a disparadores de base de datos, etc.)
</para>
<para>
Afortunadamente, puedes forzar este comportamiento (es decir, la segunda estrategia) en cualquier
momento descartando (es decir, desreferenciando) la colecci&#x00f3;n original y devolviendo una colecci&#x00f3;n
nuevamente instanciada con todos los elementos actuales. Esto puede ser muy &#x00fa;til y potente de vez en
cuando.
</para>
<para>
Por supuesto, el borrado-de-un-solo-tiro no se aplica a colecciones mapeadas
<literal>inverse="true"</literal>.
</para>
</sect2>
</sect1>
<sect1 id="performance-monitoring" revision="1">
<title>Monitoreando el rendimiento</title>
<para>
La optimizaci&#x00f3;n no es de mucho uso sin el monitoreo y el acceso a n&#x00fa;meros de rendimiento. Hibernate provee
un rango completo de figuras sobre sus operaciones internas. Las estad&#x00ed;sticas en Hibernate est&#x00e1;n disponibles
por <literal>SessionFactory</literal>.
</para>
<sect2 id="performance-monitoring-sf" revision="2">
<title>Monitoreando una SessionFactory</title>
<para>
Puedes acceder a las m&#x00e9;tricas de <literal>SessionFactory</literal> de dos formas.
Tu primera opci&#x00f3;n es llamar a <literal>sessionFactory.getStatistics()</literal> y
leer o mostrar por pantalla la <literal>Statistics</literal> por ti mismo.
</para>
<para>
Hibernate puede tambi&#x00e9;n usar JMX para publicar las m&#x00e9;tricas si habilitas el MBean
<literal>StatisticsService</literal>. Puede habilitar un solo MBean para todas tus
<literal>SessionFactory</literal> o una por f&#x00e1;brica. Mira el siguiente c&#x00f3;digo para
ejemplos de configuraci&#x00f3;n minimalistas:
</para>
<programlisting><![CDATA[// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server]]></programlisting>
<programlisting><![CDATA[// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server]]></programlisting>
<para>
POR HACER: Esto no tiene sentido: En el primer caso, recuperamos y usamos el MBean directamente.
En el segundo, debemos proporcionar el nombre JNDI en el que se guarda la f&#x00e1;brica de sesiones antes
de usarlo. Usa <literal>hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")</literal>
</para>
<para>
Puedes (des)activar el monitoreo de una <literal>SessionFactory</literal>
</para>
<itemizedlist>
<listitem>
<para>
en tiempo de configuraci&#x00f3;n, establece <literal>hibernate.generate_statistics</literal> a
<literal>false</literal>
</para>
</listitem>
</itemizedlist>
<itemizedlist>
<listitem>
<para>
en tiempo de ejecuci&#x00f3;n: <literal>sf.getStatistics().setStatisticsEnabled(true)</literal>
o <literal>hibernateStatsBean.setStatisticsEnabled(true)</literal>
</para>
</listitem>
</itemizedlist>
<para>
Las estad&#x00ed;sticas pueden ser reajustadas program&#x00e1;ticamente usando el m&#x00e9;todo <literal>clear()</literal>.
Puede enviarse un resumen a un logger (nivel info) usando el m&#x00e9;todo <literal>logSummary()</literal>.
</para>
</sect2>
<sect2 id="performance-monitoring-metrics" revision="1">
<title>M&#x00e9;tricas</title>
<para>
Hibernate provee un n&#x00fa;mero de m&#x00e9;tricas, desde informaci&#x00f3;n muy b&#x00e1;sica a la especializada
s&#x00f3;lo relevante en ciertos escenarios. Todos los contadores disponibles se describen en la
API de la interface <literal>Statistics</literal>, en tres categor&#x00ed;as:
</para>
<itemizedlist>
<listitem>
<para>
M&#x00e9;tricas relacionadas al uso general de <literal>Session</literal> usage, tales como
n&#x00fa;mero de sesiones abiertas, conexiones JDBC recuperadas, etc,
</para>
</listitem>
<listitem>
<para>
M&#x00e9;tricas relacionadas a las entidades, colecciones, consultas, y cach&#x00e9;s como un todo.
(tambi&#x00e9;n conocidas como m&#x00e9;tricas globales).
</para>
</listitem>
<listitem>
<para>
M&#x00e9;tricas detalladas relacionadas a una entidad, colecci&#x00f3;n, consulta o regi&#x00f3;n de cach&#x00e9;
en particular.
</para>
</listitem>
</itemizedlist>
<para>
Por ejemplo, puedes comprobar el acceso, p&#x00e9;rdida, y radio de colocaci&#x00f3;n de entidades, colecciones
y consultas en el cach&#x00e9;, y el tiempo promedio que necesita una consulta. Ten en cuenta que el n&#x00fa;mero
de milisegundos est&#x00e1; sujeto a aproximaci&#x00f3;n en Java. Hibernate est&#x00e1; pegado a la precisi&#x00f3;n de la JVM,
en algunas plataformas esto podr&#x00ed;a incuso ser tener s&#x00f3;lo una exactitud de 10 segundos.
</para>
<para>
Se usan getters simples para acceder a las m&#x00e9;tricas globales (es decir, no pegadas a una entidad,
colecci&#x00f3;n, regi&#x00f3;n de cach&#x00e9;, etc, en particular). Puedes acceder a las m&#x00e9;tricas de una entidad,
colecci&#x00f3;n, regi&#x00f3;n de cach&#x00e9; en particular a trav&#x00e9;s de su nombre, y a trav&#x00e9;s de su representaci&#x00f3;n HQL
o SQL para las consultas. Por favor refi&#x00e9;rete al Javadoc de la API de <literal>Statistics</literal>,
<literal>EntityStatistics</literal>, <literal>CollectionStatistics</literal>,
<literal>SecondLevelCacheStatistics</literal>, y <literal>QueryStatistics</literal> para m&#x00e1;s informaci&#x00f3;n.
El siguiente c&#x00f3;digo muestra un ejemplo sencillo:
</para>
<programlisting><![CDATA[Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );]]></programlisting>
<para>
Para trabajar sobre todas las entidades, colecciones, consultas y regiones de cach&#x00e9;s, puedes recuperar la
lista de nombres de entidades, colecciones, consultas y regiones de cach&#x00e9;s con los siguientes m&#x00e9;todos:
<literal>getQueries()</literal>, <literal>getEntityNames()</literal>,
<literal>getCollectionRoleNames()</literal>, y <literal>getSecondLevelCacheRegionNames()</literal>.
</para>
</sect2>
</sect1>
</chapter>