Mejorando el rendimiento Estrategias de recuperación Una estrategia de recuperación 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 Criteria en particular. Hibernate3 define las siguientes estrategias de recuperación: Recuperación por unión (join fetching) - Hibernate recupera la instancia asociada o colección en la misma SELECT, usando una OUTER JOIN. Recuperación por selección (select fetching) - se usa una segunda SELECT para recuperar la entidad asociada o colección. A menos que deshabilites explícitamente la recuperación perezosa especificando lazy="false", la segunda selección sólo será ejecutada cuando realmente accedas a la asociación. Recuperación por subselección (subselect fetching) - se usa una segunda SELECT 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 lazy="false", esta segunda selección sólo será ejecutada cuando realmente accedas a la asociación. Recuperación en lote - 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 SELECT, especificando una lista de claves primarias o de claves foráneas. Hibernate también distingue entre: Recuperación inmediata - una asociación, colección o atributo es recuperado inmediatamente, cuando el dueño es cargado. Recuperación perezosa de colecciones - se recupera una colección cuando la aplicación invoca una operación sobre la colección. (Esto es por defecto para las colecciones.) Recuperación por proxy - se recupera una asociación monovaluada cuando se invoca un método que no sea el getter del identificador sobre el objeto asociado. Recuperación perezosa de atributos - 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. Aquí tenemos dos nociones ortogonales: cuándo se recupera la aplicación, y cómo es recuperada (qué SQL es usado). ¡No las confundas! Usamos fetch para afinar el rendimiento. Podemos usar lazy para definir un contrato sobre qué datos están siempre disponibles en cualquier instancia separada de una clase en particular. Trabajando con asociaciones perezosas 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. Nota: si estableces hibernate.default_batch_fetch_size, 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). 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: Ya que la colección de permisos no fue inicializada cuando se cerró la Session, la colección no será capaz de cargar su estado. Hibernate no soporta la inicialización perezosa de objetos separados. La solución es mover el código que lee de la colección a justo antes que la transacción sea comprometida. Alternativamente, podríamos usar una colección no perezosa o asociación, especificando lazy="false" 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! 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. Afinando las estrategias de recuperación 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: ]]> La estrategia de recuperación definida en el documento de mapeo afecta a: las recuperaciones vía get() o load() las recuperaciones que ocurren implícitamente cuando se navega una asociación (recuperación perezosa) las consultas de Criteria 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 left join fetch 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 Criteria, usarías setFetchMode(FetchMode.JOIN). Si acaso lo deseases, podrías cambiar la estrategia de recuperación usada por get() or load(); simplemente usa una consulta Criteria, por ejemplo: (Esto es el equivalente de Hibernate de lo que otras soluciones ORM llaman un "plan de recuperación".) Una forma completamente diferente de evitar problemas con selecciones N+1 es usar el caché de segundo nivel. Proxies de asociaciones de un solo extremo 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). Por defecto, Hibernate3 genera proxies (en el arranque) para todas las clases persistentes y los usa para habilitar la recuperación perezosa de asociaciones muchos-a-uno y uno-a-uno. El fichero de mapeo puede declarar una interface a usar como interface de proxy para esa clase, con el atributo proxy. Por defecto, Hibernate usa una subclase de la clase. 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! Hay algunos puntos a tener en cuenta al extender este enfoque a clases polimórficas, por ejemplo. ...... ..... ]]> Primero, las instancias de Cat nunca serán objeto de un cast a DomesticCat, incluso aunque la instancia subyacente sea instancia de DomesticCat: Segundo, es posible romper con el operador == de un proxy. 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: Tercero, no debes usar un proxy CGLIB para una clase final o una clase con algún método final. 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. 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: ...... ..... ]]> donde CatImpl implementa la interface Cat y DomesticCatImpl implementa la interface DomesticCat. Entonces load() o iterate() pueden devolver instancias de Cat y DomesticCat. (Nota que list() usualmente no devuelve proxies.) Las relaciones también son inicializadas perezosamente. Esto significa que debes declarar cualquier propiedad como de tipo Cat, no CatImpl. Ciertas operaciones no requieren inicialización de proxies. equals(), si la clase persistente no sobrescribe equals() hashCode(), si la clase persistente no sobrescribe hashCode() El método getter del identificador Hibernate detectará las clase persistentes que sobrescriban equals() o hashCode(). Inicializando colecciones y proxies Una LazyInitializationException será lanzada por Hibernate si una colección o proxy sin inicializar es accedido fuera del ámbito de la Session, es decir, cuando la entidad que posee la colección o que tiene la referencia al proxy esté en el estado separada. A veces necesitamos asegurarnos que un proxy o colección esté inicializado antes de cerrar la Session. Por supuesto, siempre podemos forzar la inicialización llamando a cat.getSex() o cat.getKittens().size(), por ejemplo. Pero esto es confuso a lectores del código y no es conveniente para código genérico. Los métodos estáticos Hibernate.initialize() y Hibernate.isInitialized() proveen a la aplicación de una forma conveniente de trabajar con colecciones o proxies inicializados perezosamente. Hibernate.initialize(cat) forzará la inicialización de un proxy, cat, en tanto su Session esté todavía abierta. Hibernate.initialize( cat.getKittens() ) tiene un efecto similar para la colección de gatitos. Otra opción es mantener la Session 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 Session esté abierta cuando se inicializa una colección. Existen dos formas básicas de tratar este tema: En una aplicación basada web, puede usarse un filtro de servlets para cerrar la Session sólo bien al final de una petición de usuario, una vez que el rendering de la vista esté completa (el patrón Sesión Abierta en Vista (Open Session in View)). 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 Session 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 Session. Recomendamos que se use una variable ThreadLocal para tener la Session actual (ver el capítulo 1, , para una implementación de ejemplo). 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 Hibernate.initialize() 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 FETCH o una FetchMode.JOIN en Criteria. Esto es usualmente más fácil si adoptas el patrón Comando en vez de un Fachada de Sesión. Puedes también adjuntar un objeto cargado previamente a una nueva Session con merge() o lock() antes de acceder a colecciones no inicializadas (u otros proxies). ¡No, Hibernate no, y ciertamente no debe hacer esto automáticamente, ya que introduciría semánticas de transacción ad hoc! 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. Puedes usar un filtro de colecciones para obtener el tamaño de una colección sin inicializarla: El método createFilter() se usa también para recuperar eficientemente subconjuntos de una colección sin necesidad de inicializar toda la colección: Usando recuperación en lotes 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. 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 Cat cargadas en una Session, cada Cat tiene una referencia a su owner, una Person. La clase Person está mapeada con un proxy, lazy="true". Si ahora iteras a través de todos los gatos y llamas a getOwner() para cada uno, Hibernate por defecto ejecutará 25 sentencias SELECT para traer los dueños tratados con proxies. Puedes afinar este comportamiento especificando un batch-size en el mapeo de Person: ...]]> Hibernate ahora ejecutará sólo tres consultas, el patrón es 10, 10, 5. También puedes habilitar la recuperación en lotes para colecciones. Por ejemplo, si cada Person tiene una colección perezosa de Cats, y hay 10 personas actualmente cargadas en la Session, iterar a través de las 10 personas generará 10 SELECTs, una para cada llamada a getCats(). Si habilitas la recuperación en lotes para la colección de cats en el mapeo de Person, Hibernate puede recuperar por adelantado las colecciones: ... ]]> Con un batch-size de 3, Hibernate cargará 3, 3, 3, 1 colecciones en cuatro SELECTs. Una vez más, el valor del atributo depende del número esperado de colecciones sin inicializar en una Session en particular. 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 conjunto anidado o una ruta materializada podría ser una mejor opción para árboles que sean de lectura en la mayoría de los casos.) Usando recuperación por subselección 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. Usando recuperación perezosa de propiedades Hibernate3 soporta la recuperación perezosa de propiedades individuales. Esta técnica de optimización es también conocida como grupos de recuperación (fetch groups). 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 habilitar la carga perezosa de propiedades, establece el atributo lazy en tus mapeos de propiedades: ]]> ¡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 la instrumentación del bytecode, usa la siguiente tarea Ant: ]]> 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. Puedes forzar la usual recuperación temprana de propiedades usando fetch all properties en HQL. El Caché de Segundo Nivel Una Session 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 SessionFactory) 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é). 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 org.hibernate.cache.CacheProvider usando la propiedad hibernate.cache.provider_class. Proveedores de Caché Caché clase del Provedor Tipo Cluster Seguro Caché de Consultas Soportado Hashtable (no pensado para uso en producción) org.hibernate.cache.HashtableCacheProvider memoria EHCache org.hibernate.cache.EhCacheProvider memoria, disco OSCache org.hibernate.cache.OSCacheProvider memoria, disco SwarmCache org.hibernate.cache.SwarmCacheProvider clusterizado (ip multicast) sí (invalidación en cluster) TreeCache de JBoss org.hibernate.cache.TreeCacheProvider clusterizado (ip multicast), transaccional sí (replicación) sí (requiere sincronización de reloj)
Mapeos de caché El elemento <cache> de una mapeo de clase o colección tiene la siguiente forma: ]]> usage especifica la estrategia de caching: transactional, read-write, nonstrict-read-write o read-only Alternativamente (¿preferiblemente?), puedes especificar los elementos <class-cache> y <collection-cache> en hibernate.cfg.xml. El atributo usage especifica una estrategia de concurrencia al caché. Estrategia: sólo lectura (read only) Si tu aplicación necesita leer pero nunca modificar las instancias de una clase persistente, puede usarse un caché read-only. Esta es la mejor y más simple estrategia. Es incluso perfectamente segura de usar en un cluster. .... ]]> Estrategia: lectura/escritura (read/write) Si la aplicación necesita actualizar datos, un caché read-write 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 hibernate.transaction.manager_lookup_class, mencionando una estrategia para obtener el TransactionManager de JTA. En otros entornos, debes asegurarte que la transacción esté completada cuando se llame a Session.close() o Session.disconnect(). 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 no no lo soportan. .... .... ]]> Estrategia: lectura/escritura no estricta (nonstrict read/write) 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é nonstrict-read-write podría ser apropiado. Si se usa el caché en un entorno JTA, debes especificar hibernate.transaction.manager_lookup_class. En otros entornos, debes asegurarte que la transacción se haya completado cuando se llame a Session.close() o Session.disconnect(). Estrategia: transaccional La estrategia de caché transactional 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 hibernate.transaction.manager_lookup_class. 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. Soporte a Estrategia de Concurrencia a Caché Caché read-only nonstrict-read-write read-write transactional Hashtable (no pensado para uso en producción) EHCache OSCache SwarmCache JBoss TreeCache
Gestionando los cachés Siempre que pases un objeto a save(), update() o saveOrUpdate() y siempre que recuperes un objeto usando load(), get(), list(), iterate() o scroll(), ese objeto es agregado al caché interno de la Session. Cuando subsecuentemente se llame a flush(), 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 evict() para quitar el objeto y sus colecciones del caché de primer nivel. La Session también provee un método contains() para determinar si una instancia pertenece al caché de la sesión. Para desahuciar (evict) todos los objetos del caché de sesión, llama a Session.clear(). Para el caché de segundo nivel, hay métodos definidos en SessionFactory para desahuciar el estado en caché de una instancia, clase entera, instancia de colección o rol enter de colección. El CacheMode controla cómo una sesión en particular interactúa con el caché de segundo nivel. CacheMode.NORMAL - lee ítems desde y escribe ítems hacia el caché de segundo nivel CacheMode.GET - lee ítems del caché de segundo nivel, pero no escribe al caché de segundo nivel excepto al actualizar datos CacheMode.PUT - escribe ítems al caché de segundo nivel, pero no lee del caché de segundo nivel CacheMode.REFRESH - escribe ítems al caché de segundo nivel, pero no lee del caché de segundo nivel, saltándose el efecto de hibernate.cache.use_minimal_puts, forzando un refresco del caché de segundo nivel para todos los ítems leídos de la base de datos Para navegar por los contenidos de una región de caché de segundo nivel o de consultas, usa la API de Statistics: 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: El Caché de Consultas 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: Esta configuración causa la creación de dos nuevas regiones de caché - una teniendo en caché conjuntos resultado de consulta (org.hibernate.cache.StandardQueryCache), el otro teniendo timestamps de las actualizaciones más recientes a tablas consultables (org.hibernate.cache.UpdateTimestampsCache). 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. 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 Query.setCacheable(true). Esta llamada permite a la consulta buscar resultados existentes en caché o agregar sus resultados al caché cuando se ejecuta. 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 Query.setCacheRegion(). Si la consulta debe forzar un refresco de si región del caché de consultas, debes llamar a Query.setCacheMode(CacheMode.REFRESH). 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 SessionFactory.evictQueries(). Entendiendo el rendimiento de Colecciones 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. Taxonomia Hibernate define tres tipos básicos de colecciones: colecciones de valores asociaciones uno a muchos asociaciones muchos a muchos 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: colecciones indexadas conjuntos (sets) bolsas (bags) Todas las colecciones indexadas (mapas, listas, arrays) tienen una clave primaria consistente de las columnas <key> y <index>. 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. Los conjuntos (sets) tienen una clave primaria consistente en <key> 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 SchemaExport realmente cree la clave primaria de un <set> por ti, debes declarar todas las columnas como not-null="true".) Los mapeos de <idbag> definen una clave delegada, de modo que siempre resulten eficientes de actualizar. De hecho, son el mejor caso. 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 DELETE) y recreando la colección siempre que cambia. Esto podría ser muy ineficiente. 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.) Las listas, mapas, idbags y conjuntos son las colecciones más eficientes de actualizar 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. 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 Set, Hibernate ni siquiera actualiza una fila con UPDATE cuando se "cambia" un elemento. Los cambios a un Set siempre funcionan por medio de INSERT y DELETE (de filas individuales). Una vez más, esta consideración no se aplica a las asociaciones uno a muchos. 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. 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 inverse="true". 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. Los Bags y las listas son las colecciones inversas más eficientes 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 inverse="true" (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 Collection.add() o Collection.addAll() siempre deben devolver true para un bag o List (no como un Set). Esto puede hacer el siguiente código común mucho más rápido. Borrado de un solo tiro 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 list.clear(), por ejemplo). En este caso, Hibernate publicará una sola DELETE, ¡y listo! 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 INSERT y dos sentencias DELETE (a menos que la colección sea un bag). Esto es ciertamente deseable. Sin embargo, supón que quitamos dieciocho elementos, dejando dos y luego añadimos tres nuevos elementos. Hay dos formas posibles de proceder borrar dieciocho filas una a una y luego insertar tres filas quitar toda la colección (en un solo DELETE de SQL) e insertar todos los cinco elementos actuales (uno a uno) 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.) 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. Por supuesto, el borrado-de-un-solo-tiro no se aplica a colecciones mapeadas inverse="true". Monitoreando el rendimiento 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 SessionFactory. Monitoreando una SessionFactory Puedes acceder a las métricas de SessionFactory de dos formas. Tu primera opción es llamar a sessionFactory.getStatistics() y leer o mostrar por pantalla la Statistics por ti mismo. Hibernate puede también usar JMX para publicar las métricas si habilitas el MBean StatisticsService. Puede habilitar un solo MBean para todas tus SessionFactory o una por fábrica. Mira el siguiente código para ejemplos de configuración minimalistas: 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 hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") Puedes (des)activar el monitoreo de una SessionFactory en tiempo de configuración, establece hibernate.generate_statistics a false en tiempo de ejecución: sf.getStatistics().setStatisticsEnabled(true) o hibernateStatsBean.setStatisticsEnabled(true) Las estadísticas pueden ser reajustadas programáticamente usando el método clear(). Puede enviarse un resumen a un logger (nivel info) usando el método logSummary(). Métricas 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 Statistics, en tres categorías: Métricas relacionadas al uso general de Session usage, tales como número de sesiones abiertas, conexiones JDBC recuperadas, etc, Métricas relacionadas a las entidades, colecciones, consultas, y cachés como un todo. (también conocidas como métricas globales). Métricas detalladas relacionadas a una entidad, colección, consulta o región de caché en particular. 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. 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 Statistics, EntityStatistics, CollectionStatistics, SecondLevelCacheStatistics, y QueryStatistics para más información. El siguiente código muestra un ejemplo sencillo: 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: getQueries(), getEntityNames(), getCollectionRoleNames(), y getSecondLevelCacheRegionNames().