1334 lines
69 KiB
XML
1334 lines
69 KiB
XML
<chapter id="performance">
|
|
<title>Mejorando el rendimiento</title>
|
|
|
|
<sect1 id="performance-fetching">
|
|
<title>Estrategias de recuperación</title>
|
|
|
|
<para>
|
|
Una <emphasis>estrategia de recuperación</emphasis> es la estrategia que usará Hibernate para recuperar
|
|
los objetos asociados cuando la aplicación necesite navegar la asociación. Las estrategias de recuperació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ón:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación por unión (join fetching)</emphasis> - Hibernate recupera la
|
|
instancia asociada o colección en la misma <literal>SELECT</literal>, usando una
|
|
<literal>OUTER JOIN</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación por selección (select fetching)</emphasis> - se usa una segunda
|
|
<literal>SELECT</literal> para recuperar la entidad asociada o colección. A menos que
|
|
deshabilites explícitamente la recuperación perezosa especificando <literal>lazy="false"</literal>,
|
|
la segunda selección sólo será ejecutada cuando realmente accedas a la asociación.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación por subselecció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ón previa. A menos que deshabilites explícitamente la
|
|
recuperación perezosa especificando <literal>lazy="false"</literal>, esta segunda selección sólo
|
|
será ejecutada cuando realmente accedas a la asociación.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación en lote</emphasis> - una estrategia de optimización para la recuperación
|
|
por selecció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áneas.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Hibernate también distingue entre:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación inmediata</emphasis> - una asociación, colección o atributo es recuperado
|
|
inmediatamente, cuando el dueño es cargado.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación perezosa de colecciones</emphasis> - se recupera una colección cuando la
|
|
aplicación invoca una operación sobre la colección. (Esto es por defecto para las colecciones.)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación por proxy</emphasis> - se recupera una asociación monovaluada cuando se
|
|
invoca un método que no sea el getter del identificador sobre el objeto asociado.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Recuperación perezosa de atributos</emphasis> - se recupera un atributo o una asociación
|
|
monovaluada cuando se accede a la variable de instancia (requiere instrumentación del bytecode en
|
|
tiempo de ejecución). Este enfoque es raramente necesario.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Aquí tenemos dos nociones ortogonales: <emphasis>cuándo</emphasis> se recupera la aplicación,
|
|
y <emphasis>cómo</emphasis> es recuperada (qué SQL es usado). ¡No las confundas! Usamos
|
|
<literal>fetch</literal> para afinar el rendimiento. Podemos usar <literal>lazy</literal> para
|
|
definir un contrato sobre qué datos está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ón perezosa por selección para colecciones
|
|
y una recuperación por proxy perezosa para asociaciones monovaluadas. Estas polí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á la optimización de recuperación en lotes para recuperación perezosa (esta optimización también puede
|
|
ser habilitada a un nivel más granularizado).
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, la recuperación perezosa plantea un problema del que tienes que estar al tanto. Acceder
|
|
a una asociación perezosa fuera del contexto de una sesión de Hibernate abierta resultará en una
|
|
excepció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ón de permisos no fue inicializada cuando se cerró la <literal>Session</literal>,
|
|
la colección no será capaz de cargar su estado. <emphasis>Hibernate no soporta la inicialización
|
|
perezosa de objetos separados</emphasis>. La solución es mover el código que lee de la colección
|
|
a justo antes que la transacción sea comprometida.
|
|
</para>
|
|
|
|
<para>
|
|
Alternativamente, podríamos usar una colección no perezosa o asociación, especificando
|
|
<literal>lazy="false"</literal> para el mapeo de asociación. Sin embargo, está pensado
|
|
que la inicialización perezosa sea usada para casi todas las colecciones y asociaciones.
|
|
¡Si defines demasiadas asociaciones no perezosas en tu modelo de objetos, Hibernate terminará
|
|
necesitando recuperar la base de datos entera en cada transacción!
|
|
</para>
|
|
|
|
<para>
|
|
Por otro lado, frecuentemente necesitamos elegir la recuperación por unión (que es no perezosa
|
|
por naturaleza) en vez de la recuperación por selección en una transacción en particular. Veremos
|
|
ahora cómo personalizar la estrategia de recuperación. En Hibernate3, los mecanismos para elegir una
|
|
estrategia de recuperación son idénticas a las de las asociaciones monovaluadas y colecciones.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-fetching-custom" revision="3">
|
|
<title>Afinando las estrategias de recuperación</title>
|
|
|
|
<para>
|
|
La recuperación por selección (la preestablecida) es extremadamente vulnerable a problemas de
|
|
selección N+1, de modo querríamos habilitar la recuperación por unió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ón definida en el documento de mapeo afecta a:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
las recuperaciones vía <literal>get()</literal> o <literal>load()</literal>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
las recuperaciones que ocurren implícitamente cuando se navega una asociación
|
|
(recuperació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ón. En cambio, mantenemos el
|
|
comportamiento por defecto, y lo sobrescribimos para una transacción en particular, usando
|
|
<literal>left join fetch</literal> en HQL. Esto le dice a Hibernate que recupere la asociación
|
|
tempranamente en la primera selección, usando una unión externa. En la API de consulta de
|
|
<literal>Criteria</literal>, usarías <literal>setFetchMode(FetchMode.JOIN)</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Si acaso lo deseases, podrías cambiar la estrategia de recuperació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ón".)
|
|
</para>
|
|
|
|
<para>
|
|
Una forma completamente diferente de evitar problemas con selecciones N+1 es usar el caché de
|
|
segundo nivel.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-fetching-proxies" revision="2">
|
|
<title>Proxies de asociaciones de un solo extremo</title>
|
|
|
|
<para>
|
|
La recuperación perezosa de colecciones está implementada usando la implementació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ón debe ser tratada
|
|
con proxies. Hibernate implementa proxies de inicialización perezosa para objetos persistentes usando
|
|
mejora del bytecode en tiempo de ejecució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ó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. ¡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ó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á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ón no en absoluta tan mala como parece. Aunque tenemos ahora dos referencias
|
|
a objetos proxy diferentes, la instancia subyacente será aú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ún método <literal>final</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Finalmente, si tu objeto persistente adquiere cualquier recurso bajo instanciación
|
|
(por ejemplo, en inicializadores o constructores por defecto), entonces esos recursos
|
|
serán adquiridos tambié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 única de Java.
|
|
Si deseas evitar estos problemas cada una de tus clases persistentes deben implementar una
|
|
interface que declare sus mé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é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ó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étodo getter del identificador
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Hibernate detectará 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á lanzada por Hibernate si una colección
|
|
o proxy sin inicializar es accedido fuera del ámbito de la <literal>Session</literal>, es decir,
|
|
cuando la entidad que posee la colección o que tiene la referencia al proxy esté en el estado
|
|
separada.
|
|
</para>
|
|
|
|
<para>
|
|
A veces necesitamos asegurarnos que un proxy o colección esté inicializado antes de cerrar la
|
|
<literal>Session</literal>. Por supuesto, siempre podemos forzar la inicialización llamando a
|
|
<literal>cat.getSex()</literal> o <literal>cat.getKittens().size()</literal>, por ejemplo.
|
|
Pero esto es confuso a lectores del código y no es conveniente para código genérico.
|
|
</para>
|
|
|
|
<para>
|
|
Los métodos estáticos <literal>Hibernate.initialize()</literal> y
|
|
<literal>Hibernate.isInitialized()</literal> proveen a la aplicación de una forma conveniente de
|
|
trabajar con colecciones o proxies inicializados perezosamente.
|
|
<literal>Hibernate.initialize(cat)</literal> forzará la inicialización de un proxy,
|
|
<literal>cat</literal>, en tanto su <literal>Session</literal> esté todavía abierta.
|
|
<literal>Hibernate.initialize( cat.getKittens() )</literal> tiene un efecto similar para la colección
|
|
de gatitos.
|
|
</para>
|
|
|
|
<para>
|
|
Otra opción es mantener la <literal>Session</literal> abierta hasta que todas las colecciones
|
|
y proxies necesarios hayan sido cargados. En algunas arquitecturas de aplicación, particularmente
|
|
en aquellas donde el código que accede a los datos usando Hibernate, y el código que los usa están
|
|
en capas de aplicación diferentes o procesos físicos diferentes, puede ser un problema asegurar que
|
|
la <literal>Session</literal> esté abierta cuando se inicializa una colección. Existen dos formas
|
|
básicas de tratar este tema:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
En una aplicación basada web, puede usarse un filtro de servlets para cerrar la
|
|
<literal>Session</literal> sólo bien al final de una petición de usuario, una
|
|
vez que el rendering de la vista esté completa (el patrón <emphasis>Sesión Abierta en
|
|
Vista (Open Session in View)</emphasis>). Por supuesto, estos sitios requieren una
|
|
fuerte demanda de corrección del manejo de excepciones de tu infraestructura de
|
|
aplicación. Es de una importancia vital que la <literal>Session</literal> esté
|
|
cerrada y la transacción terminada antes de volver al usuario, incluso cuando ocurra
|
|
una excepción durante el rendering de la pá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ítulo 1, <xref linkend="quickstart-playingwithcats"/>, para una implementación de ejemplo).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
En una aplciación con una grada de negocios separada, la ló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ón web todos los datos que se requieran para un caso de uso en particular
|
|
ya inicializados. Usualmente, la aplicación llama a <literal>Hibernate.initialize()</literal>
|
|
para cada colección que se necesitará en la grada web (esta llamada debe ocurrir antes que la
|
|
sesión sea cerrada) o recupera la colección tempranamente usando una consulta de Hibernate con una
|
|
cláusula <literal>FETCH</literal> o una <literal>FetchMode.JOIN</literal> en
|
|
<literal>Criteria</literal>. Esto es usualmente más fácil si adoptas el patrón
|
|
<emphasis>Comando</emphasis> en vez de un <emphasis>Fachada de Sesión</emphasis>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Puedes tambié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). ¡No, Hibernate no, y ciertamente <emphasis>no
|
|
debe</emphasis> hacer esto automáticamente, ya que introduciría semánticas de transacción ad hoc!
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
A veces no quieres inicializar una colección grande, pero necesitas aún alguna informacion sobre
|
|
ella (como su tamaño) o un subconjunto de los datos.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes usar un filtro de colecciones para obtener el tamaño de una colección sin inicializarla:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()]]></programlisting>
|
|
|
|
<para>
|
|
El método <literal>createFilter()</literal> se usa también para recuperar eficientemente subconjuntos
|
|
de una colección sin necesidad de inicializar toda la colección:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-fetching-batch">
|
|
<title>Usando recuperación en lotes</title>
|
|
|
|
<para>
|
|
Hibernate puede hacer un uso eficiente de la recuperación en lotes, esto es, Hibernate puede cargar
|
|
muchos proxies sin inicializar si se accede a un proxy (o colecciones). La recuperación en lotes es una
|
|
optimización de la estrategia de recuperación por selección perezosa. Hay dos formas en que puedes afinar
|
|
la recuperación en lotes: a nivel de la clase o de la colección.
|
|
</para>
|
|
|
|
<para>
|
|
La recuperación en lotes para clases/entidades es más fácil de entender. Imagina que tienes la siguiente
|
|
situación en tiempo de ejecució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á mapeada con un proxy,
|
|
<literal>lazy="true"</literal>. Si ahora iteras a través de todos los gatos y llamas a
|
|
<literal>getOwner()</literal> para cada uno, Hibernate por defecto ejecutará 25 sentencias
|
|
<literal>SELECT</literal> para traer los dueñ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á sólo tres consultas, el patrón es 10, 10, 5.
|
|
</para>
|
|
|
|
<para>
|
|
También puedes habilitar la recuperación en lotes para colecciones. Por ejemplo, si cada
|
|
<literal>Person</literal> tiene una colección perezosa de <literal>Cat</literal>s, y hay 10
|
|
personas actualmente cargadas en la <literal>Session</literal>, iterar a través de las 10 personas
|
|
generará 10 <literal>SELECT</literal>s, una para cada llamada a <literal>getCats()</literal>.
|
|
Si habilitas la recuperación en lotes para la colecció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á 3, 3, 3, 1 colecciones en cuatro
|
|
<literal>SELECT</literal>s. Una vez más, el valor del atributo depende del número esperado de
|
|
colecciones sin inicializar en una <literal>Session</literal> en particular.
|
|
</para>
|
|
|
|
<para>
|
|
La recuperación de coleccione en lotes es particularmente útil si tienes un árbol anidado de
|
|
ítems, es decir, el típico patrón de cuenta de materiales. (Aunque un <emphasis>conjunto
|
|
anidado</emphasis> o una <emphasis>ruta materializada</emphasis> podría ser una mejor opción
|
|
para árboles que sean de lectura en la mayoría de los casos.)
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-fetching-subselect">
|
|
<title>Usando recuperación por subselección</title>
|
|
|
|
<para>
|
|
Si una colección perezosa o proxy monovaluado tiene que ser recuperado, Hibernate los carga a todos,
|
|
volviendo a ejecutar la consulta original en una subselección. Esto funciona de la misma forma que
|
|
la recuperación en lotes, sin carga fragmentaria.
|
|
</para>
|
|
|
|
<!-- TODO: Write more about this -->
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-fetching-lazyproperties">
|
|
<title>Usando recuperación perezosa de propiedades</title>
|
|
|
|
<para>
|
|
Hibernate3 soporta la recuperación perezosa de propiedades individuales. Esta técnica de optimización
|
|
es también conocida como <emphasis>grupos de recuperación (fetch groups)</emphasis>. Por favor, nota
|
|
que éste es mayormente un aspecto de marketing, ya que en la práctica, optimizar lecturas de filas es
|
|
mucho más importante que la optimización de lectura de columnas. Sin embargo, cargar sólo algunas
|
|
propiedades de una clase podría ser ú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>
|
|
¡La carga perezosa de propiedades requiere la instrumentación del bytecode en tiempo
|
|
de construcción! Si tus clases persistentes no son mejoradas, Hibernate ignorará silenciosamente
|
|
la configuración perezosa de propiedades y caerá en recuperación inmediata.
|
|
</para>
|
|
|
|
<para>
|
|
Para la instrumentació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 (¿mejor?) de evitar lecturas innecesarias de columnas, al menos para
|
|
transacciones de sólo lectura es usar las funcionalidades de proyección de consultas HQL o Criteria.
|
|
Esto evita la necesidad de procesar el bytecode en tiempo de construcción y ciertamente es una solución
|
|
preferida.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes forzar la usual recuperación temprana de propiedades usando <literal>fetch all properties</literal>
|
|
en HQL.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="performance-cache" revision="1">
|
|
<title>El Caché de Segundo Nivel</title>
|
|
|
|
<para>
|
|
Una <literal>Session</literal> de Hibernate es una caché de datos persistentes a nivel de transacción.
|
|
Es posible configurar un cluster o caché a nivel de JVM (a nivel de <literal>SessionFactory</literal>)
|
|
sobre una base de clase-a-clase o colección-a-colección. Puedes incluso enchufar una caché en cluster.
|
|
Sé cuidadoso. Las cachés nunca están al tanto de los cambios hechos por otra aplicación al almacén persistente
|
|
(aunque pueden ser configurados para expirar regularmente los datos en caché).
|
|
</para>
|
|
|
|
<para>
|
|
Por defecto, Hibernate usa EHCache para caching a nivel de JVM. (El soporte a JCS ahora está despreciado
|
|
y será quitado en una futura versión de Hibernate.) Puedes elegir una implementació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é</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é</entry>
|
|
<entry>clase del Provedor</entry>
|
|
<entry>Tipo</entry>
|
|
<entry>Cluster Seguro</entry>
|
|
<entry>Caché de Consultas Soportado</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>Hashtable (no pensado para uso en producción)</entry>
|
|
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
|
|
<entry>memoria</entry>
|
|
<entry></entry>
|
|
<entry>sí</entry>
|
|
</row>
|
|
<row>
|
|
<entry>EHCache</entry>
|
|
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
|
|
<entry>memoria, disco</entry>
|
|
<entry></entry>
|
|
<entry>sí</entry>
|
|
</row>
|
|
<row>
|
|
<entry>OSCache</entry>
|
|
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
|
|
<entry>memoria, disco</entry>
|
|
<entry></entry>
|
|
<entry>sí</entry>
|
|
</row>
|
|
<row>
|
|
<entry>SwarmCache</entry>
|
|
<entry><literal>org.hibernate.cache.SwarmCacheProvider</literal></entry>
|
|
<entry>clusterizado (ip multicast)</entry>
|
|
<entry>sí (invalidació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í (replicación)</entry>
|
|
<entry>sí (requiere sincronización de reloj)</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
|
|
<sect2 id="performance-cache-mapping">
|
|
<title>Mapeos de caché</title>
|
|
|
|
<para>
|
|
El elemento <literal><cache></literal> de una mapeo de clase o colecció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 (¿preferiblemente?), puedes especificar los elementos
|
|
<literal><class-cache></literal> y <literal><collection-cache></literal> en
|
|
<literal>hibernate.cfg.xml</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
El atributo <literal>usage</literal> especifica una <emphasis>estrategia de concurrencia al
|
|
caché</emphasis>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-cache-readonly">
|
|
<title>Estrategia: sólo lectura (read only)</title>
|
|
|
|
<para>
|
|
Si tu aplicación necesita leer pero nunca modificar las instancias de una clase persistente,
|
|
puede usarse un caché <literal>read-only</literal>. Esta es la mejor y má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ón necesita actualizar datos, un caché <literal>read-write</literal> podría ser apropiado.
|
|
Esta estrategia de caché nunca debe ser usada si se requiere nivel de aislamiento serializable de
|
|
transacciones. Si el caché 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ón
|
|
esté 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ón de caché subyacente soporta bloqueos. Los provedores de caché 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ón necesita sólo ocasionalmente actualizar datos (es decir, es extremadamente inprobable
|
|
que dos transacciones intenten actualizar el mismo ítem simultáneamente) y no se requiere de un
|
|
aislamiento de transacciones estricto, un caché <literal>nonstrict-read-write</literal> podría ser
|
|
apropiado. Si se usa el caché en un entorno JTA, debes especificar
|
|
<literal>hibernate.transaction.manager_lookup_class</literal>. En otros entornos, debes asegurarte que la
|
|
transacció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é <literal>transactional</literal> brinda soporte a provedores de cachés
|
|
completamente transaccionales como TreeCache de JBoss. Un caché así, puede só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é soporta todas las estrategias de concurrencia al caché. La siguiente
|
|
tabla muestra qué provedores son compatibles con qué estrategias de concurrencia.
|
|
</para>
|
|
|
|
<table frame="topbot">
|
|
<title>Soporte a Estrategia de Concurrencia a Caché</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é</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ón)</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry></entry>
|
|
</row>
|
|
<row>
|
|
<entry>EHCache</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry></entry>
|
|
</row>
|
|
<row>
|
|
<entry>OSCache</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry></entry>
|
|
</row>
|
|
<row>
|
|
<entry>SwarmCache</entry>
|
|
<entry>sí</entry>
|
|
<entry>sí</entry>
|
|
<entry></entry>
|
|
<entry></entry>
|
|
</row>
|
|
<row>
|
|
<entry>JBoss TreeCache</entry>
|
|
<entry>sí</entry>
|
|
<entry></entry>
|
|
<entry></entry>
|
|
<entry>sí</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="performance-sessioncache" revision="2">
|
|
<title>Gestionando los caché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é interno de la <literal>Session</literal>.
|
|
</para>
|
|
<para>
|
|
Cuando subsecuentemente se llame a <literal>flush()</literal>, el estado de ese objeto será
|
|
sincronizado con la base de datos. Si no quieres que ocurra esta sincronización o si estás
|
|
procesando un número enorme de objetos y necesitas gestionar la memoria eficientemente,
|
|
puede usarse el método <literal>evict()</literal> para quitar el objeto y sus colecciones
|
|
del caché 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én provee un método <literal>contains()</literal> para determinar
|
|
si una instancia pertenece al caché de la sesión.
|
|
</para>
|
|
|
|
<para>
|
|
Para desahuciar (evict) todos los objetos del caché de sesión, llama a <literal>Session.clear()</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Para el caché de segundo nivel, hay métodos definidos en <literal>SessionFactory</literal> para
|
|
desahuciar el estado en caché de una instancia, clase entera, instancia de colección o rol
|
|
enter de colecció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ómo una sesión en particular interactúa con el caché de segundo
|
|
nivel.
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<literal>CacheMode.NORMAL</literal> - lee ítems desde y escribe ítems hacia el caché de segundo nivel
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>CacheMode.GET</literal> - lee ítems del caché de segundo nivel, pero no escribe al caché de
|
|
segundo nivel excepto al actualizar datos
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>CacheMode.PUT</literal> - escribe ítems al caché de segundo nivel, pero no lee del caché de segundo
|
|
nivel
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>CacheMode.REFRESH</literal> - escribe ítems al caché de segundo nivel, pero no lee del caché de
|
|
segundo nivel, saltándose el efecto de <literal>hibernate.cache.use_minimal_puts</literal>, forzando
|
|
un refresco del caché de segundo nivel para todos los ítems leídos de la base de datos
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Para navegar por los contenidos de una región de caché 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ás habilitar las estadísticas y, opcionalmente, forzar a Hibernate para que guarde las
|
|
entradas del caché en un formato má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é de Consultas</title>
|
|
|
|
<para>
|
|
Los conjuntos resultado de consultas también pueden tratarse en caché. Esto sólo es útil para
|
|
consultas que se ejecutan frecuentemente con los mismos parámetros. Para usar el caché de consultas
|
|
primero debes habilitarlo:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
|
|
|
|
<para>
|
|
Esta configuración causa la creación de dos nuevas regiones de caché - una teniendo en caché
|
|
conjuntos resultado de consulta (<literal>org.hibernate.cache.StandardQueryCache</literal>),
|
|
el otro teniendo timestamps de las actualizaciones más recientes a tablas consultables
|
|
(<literal>org.hibernate.cache.UpdateTimestampsCache</literal>). Nota que el caché de consultas
|
|
no pone en caché el estado de las entidades reales en el conjunto resultado; sólo tiene en caché
|
|
valores indentificadores y resultados de tipo de valor. De modo que el caché de consultas siempre
|
|
debe ser usado en conjunción con el caché de segundo nivel.
|
|
</para>
|
|
|
|
<para>
|
|
La mayoría de consultas no se benefician del tratamiento en caché, de modo que por defecto las
|
|
consultas no son tratadas en caché. Para habilitar el tratamiento en caché, llama a
|
|
<literal>Query.setCacheable(true)</literal>. Esta llamada permite a la consulta buscar
|
|
resultados existentes en caché o agregar sus resultados al caché cuando se ejecuta.
|
|
</para>
|
|
|
|
<para>
|
|
Si requieres un control finamente granularizado sobre las políticas de expiración del caché de
|
|
consultas, puedes especificar una región de caché 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ón del caché de consultas, debes llamar a
|
|
<literal>Query.setCacheMode(CacheMode.REFRESH)</literal>. Esto es particularmente útil en casos donde
|
|
los datos subyacentes pueden haber sido actualizados por medio de un proceso separado (es decir,
|
|
no modificados a través de Hibernate) y permite a la aplicación refrescar selectivamente conjuntos
|
|
resultado de consultas en particular. Esto es una alternativa más eficient al desahuciamiento de una
|
|
región del caché de consultas ví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ón resaltaremos un par de temas más sobre cómo las colecciones
|
|
se comportan en tiempo de ejecución.
|
|
</para>
|
|
|
|
<sect2 id="performance-collections-taxonomy">
|
|
<title>Taxonomia</title>
|
|
|
|
<para>Hibernate define tres tipos bá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ón distingue las varias tablas y relaciones de clave foránea pero no nos
|
|
dice absolutamente todo lo que necesitamos saber sobre el modelo relacional. Para entender
|
|
completamente la estructura relacional y las características de rendimiento, debemos considerar
|
|
la estructura de la clave primaria que es usada por Hibernate para actualizar o borrar filas de
|
|
colección. Esto sugiere la siguiente clasificació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><key></literal> y <literal><index></literal>.
|
|
En este caso las actualizaciones de colecciones son usualmente extremadamente eficientes.
|
|
La clave primaria puede ser indexada fá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><key></literal>
|
|
y columnas de elemento. Esto puede ser menos eficiente para algunos tipos de elemento de
|
|
colecció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éticos, es probable que sólo sea tan eficiente. (Nota al márgen: si quieres que
|
|
<literal>SchemaExport</literal> realmente cree la clave primaria de un <literal><set></literal>
|
|
por ti, debes declarar todas las columnas como <literal>not-null="true"</literal>.)
|
|
</para>
|
|
|
|
<para>
|
|
Los mapeos de <literal><idbag></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 í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ón siempre que cambia.
|
|
Esto podría ser muy ineficiente.
|
|
</para>
|
|
|
|
<para>
|
|
Nota que para una asociación uno-a-muchos, la "clave primaria" puede no ser la clave
|
|
primaria física de la tabla de base de datos; pero incluso en este caso, la clasificación
|
|
anterior es útil todavía. (Aún refleja cómo Hibernate "localiza" filas individuales de la
|
|
colección.)
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-collections-mostefficientupdate">
|
|
<title>Las listas, mapas, idbags y conjuntos son las colecciones más eficientes de actualizar</title>
|
|
|
|
<para>
|
|
Desde la discusión anterior, debe quedar claro que las colecciones indexadas y
|
|
(usualmente) los conjuntos permiten la operación más eficiente en términos de añadir,
|
|
quitar y actualizar elementos.
|
|
</para>
|
|
|
|
<para>
|
|
Hay, discutiblemente, una ventaja má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ás, esta consideración no se aplica a las asociaciones
|
|
uno a muchos.
|
|
</para>
|
|
|
|
<para>
|
|
Después de observar que los arrays no pueden ser perezosos, podríamos concluir que las
|
|
listas, mapas e idbags son los tipos más eficientes de colecciones (no inversas), con los
|
|
conjuntos (sets) no muy por detrás. Se espera que los sets sean el tipo más común de colección
|
|
en las aplicaciones de Hibernate. Esto es debido a que la semántica de los sets es la más
|
|
natural en el modelo relacional.
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, en modelos de dominio de Hibernate bien dieñados, usualmente vemos que la mayoría
|
|
de las colecciones son de hecho asociaciones uno-a-muchos con <literal>inverse="true"</literal>.
|
|
Para estas asociaciones, la actualización es manejada por el extremo muchos-a-uno de la asociación,
|
|
y las consideraciones de este tipo sobre el rendimiento de actualizació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á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ás eficientes que los conjuntos. Para una colección con
|
|
<literal>inverse="true"</literal> (el idioma estándar de relaciones uno-a-muchos bidireccionales,
|
|
por ejemplo) ¡podemos añ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ódigo común mucho más rá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ón uno a uno puede ser extremadamente ineficiente.
|
|
Hibernate no es completamente estúpido, de modo que sabe no hacer eso, en el caso de una colección
|
|
nueva-vacía (si has llamado a <literal>list.clear()</literal>, por ejemplo). En este caso, Hibernate
|
|
publicará una sola <literal>DELETE</literal>, ¡y listo!
|
|
</para>
|
|
|
|
<para>
|
|
Supón que añadimos un solo elemento a una colección de tamaño veinte y luego quitamos dos elementos.
|
|
Hibernate publicará una sentencia <literal>INSERT</literal> y dos sentencias <literal>DELETE</literal>
|
|
(a menos que la colección sea un bag). Esto es ciertamente deseable.
|
|
</para>
|
|
|
|
<para>
|
|
Sin embargo, supón que quitamos dieciocho elementos, dejando dos y luego añ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ó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ón es probablemente más
|
|
rápida en este caso. (Y que sería probablemente indeseable para Hibernate ser tan inteligente;
|
|
este comportamiento podrí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ón original y devolviendo una colección
|
|
nuevamente instanciada con todos los elementos actuales. Esto puede ser muy ú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ón no es de mucho uso sin el monitoreo y el acceso a números de rendimiento. Hibernate provee
|
|
un rango completo de figuras sobre sus operaciones internas. Las estadísticas en Hibernate está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étricas de <literal>SessionFactory</literal> de dos formas.
|
|
Tu primera opció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én usar JMX para publicar las métricas si habilitas el MBean
|
|
<literal>StatisticsService</literal>. Puede habilitar un solo MBean para todas tus
|
|
<literal>SessionFactory</literal> o una por fábrica. Mira el siguiente código para
|
|
ejemplos de configuració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á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ón, establece <literal>hibernate.generate_statistics</literal> a
|
|
<literal>false</literal>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
en tiempo de ejecución: <literal>sf.getStatistics().setStatisticsEnabled(true)</literal>
|
|
o <literal>hibernateStatsBean.setStatisticsEnabled(true)</literal>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Las estadísticas pueden ser reajustadas programáticamente usando el método <literal>clear()</literal>.
|
|
Puede enviarse un resumen a un logger (nivel info) usando el método <literal>logSummary()</literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="performance-monitoring-metrics" revision="1">
|
|
<title>Métricas</title>
|
|
|
|
<para>
|
|
Hibernate provee un número de métricas, desde información muy básica a la especializada
|
|
sólo relevante en ciertos escenarios. Todos los contadores disponibles se describen en la
|
|
API de la interface <literal>Statistics</literal>, en tres categorías:
|
|
</para>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Métricas relacionadas al uso general de <literal>Session</literal> usage, tales como
|
|
número de sesiones abiertas, conexiones JDBC recuperadas, etc,
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Métricas relacionadas a las entidades, colecciones, consultas, y cachés como un todo.
|
|
(también conocidas como métricas globales).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Métricas detalladas relacionadas a una entidad, colección, consulta o región de caché
|
|
en particular.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Por ejemplo, puedes comprobar el acceso, pérdida, y radio de colocación de entidades, colecciones
|
|
y consultas en el caché, y el tiempo promedio que necesita una consulta. Ten en cuenta que el número
|
|
de milisegundos está sujeto a aproximación en Java. Hibernate está pegado a la precisión de la JVM,
|
|
en algunas plataformas esto podría incuso ser tener sólo una exactitud de 10 segundos.
|
|
</para>
|
|
|
|
<para>
|
|
Se usan getters simples para acceder a las métricas globales (es decir, no pegadas a una entidad,
|
|
colección, región de caché, etc, en particular). Puedes acceder a las métricas de una entidad,
|
|
colección, región de caché en particular a través de su nombre, y a través de su representación HQL
|
|
o SQL para las consultas. Por favor refié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ás información.
|
|
El siguiente có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és, puedes recuperar la
|
|
lista de nombres de entidades, colecciones, consultas y regiones de cachés con los siguientes métodos:
|
|
<literal>getQueries()</literal>, <literal>getEntityNames()</literal>,
|
|
<literal>getCollectionRoleNames()</literal>, y <literal>getSecondLevelCacheRegionNames()</literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |