jueves, 6 de diciembre de 2007

Cache en Hibernate

En éste post quiero compartir mi experiencia en el uso y configuración de la funcionalidad de cache en Hibernate, no es una guía paso a paso o una referencia técnica, simplemente voy a tocar algunos items que considero importantes.

Generalmente todos los cache se basan en almacenar objetos en memoria para acelerar accesos posteriores, pero debemos aclarar que en Hibernate hay dos tipos de cache: el First Level y el Second Level.

El First Level Cache es el que mantiene automáticamente Hibernate cuando dentro de una transacción interactuamos con la base de datos, en éste caso se mantienen en memoria los objetos que fueron cargados y si mas adelante en el flujo del proceso volvemos a necesitarlos van a ser retornados desde el cache, ahorrando accesos sobre la base de datos.
Lo podemos considerar como un cache de corta duración ya que es válido solamente entre el begin y el commit de una transacción, en forma aislada a las demás.
Hibernate lo maneja por defecto, no hay que configurar nada, si por alguna razón queremos deshabilitar o evitar el uso del cache, podemos usar un tipo especial de session: StatelessSession, se obtiene de la sessionFactory con el método openStatelessSession(). Yo la uso en el caso de los procesos batch, por ejemplo cuando tengo que hacer inserts o updates masivos, así evito que cada vez que hago el save de un objeto, el mismo me quede en memoria y en el correr del proceso se produzca un error del tipo OutOfMemoryError. La StatelessSession no interactúa con el First Level Cache ni con el Second Level Cache, es casi como si utilizáramos JDBC directamente.

En cambio el Second Level Cache nos permite ir varios pasos mas adelante en la mejora de la performance. La diferencia fundamental es que éste tipo de cache es válido para todas las transacciones y puede persistir en memoria durante todo el tiempo en que el aplicativo esté online, lo podríamos considerar como un cache global.
Para habilitar el Second Level Cache hay que realizar los siguientes pasos:

1. Seleccionar un Proveedor de Cache. Yo utilizo EHCache.
2. Agregar en el hibernate.cfg.xml los siguientes properties:
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_structured_entries">true</property>

3. Poner en el classpath del aplicativo el archivo de configuración ehcache.xml, según las instrucciones del proveedor.
4. Agregar en el mapping de las clases que seleccionamos como "cacheables" la siguiente entrada:
<cache usage="nonstrict-read-write"></cache>

Hibernate define cuatro niveles de cache que determinan el aislamiento:

  1. transactional: Garantiza un nivel de aislamiento hasta repeatable read. Es el nivel más estricto. Solamente se puede utilizar en clusters, es decir, con cachés distribuidas.
  2. read-write: Mantiene un aislamiento hasta el nivel de commited.
  3. nonstrict read-write: No ofrece garantía de consistencia entre el caché y la base de datos. Es una estrategia ideal para almacenar datos que no cambian habitualmente y que no sean demasiado críticos.
  4. read-only: Es la estrategia de concurrencia menos estricta. Recomendada para datos que nunca cambian.
Otro tema importante es definir qué entidades vamos a "cachear", los candidatos naturales son por ejemplo las clases que representan: estados, tipos, países, monedas o similares.
Hay que tener en cuenta que cuando tenemos relaciones one-to-many hacia éstas entidades, debemos configurarlas como fetch=select, no como fetch=join porque si no Hibernate va a "levantar" la relación haciendo un join en lugar de intentar obtener el objeto desde el cache.

He tratado de simplificar al máximo el tema para que pueda ser un punto de partida, les recomiendo los siguientes links si están interesados en incorporar ésta funcionalidad a sus aplicaciones:
Hasta el próximo post!

14 comentarios:

Martín Di Bella dijo...

Muy bueno, claro, simple y consiso.

Federico Varela dijo...

Martín, muchas gracias por el comentario, es un estímulo para seguir escribiendo.

Largo dijo...

Si todos compartiéramos tus conocimientos... nos iría mejor...

muchas gracias, has agilizado mi aplicación
;)

Federico Varela dijo...

Largo, me alegra que el post haya servido y te agradezco por el comentario. Coincido con tu reflexión, a veces los informáticos somos un poco egoístas con nuestros conocimientos, cuando decidí empezar el blog me propuse como objetivo compartir experiencias propias que puedan servir a los demás, espero que sigas siendo lector asiduo ;)

enredado dijo...

Hola Federico.

Muy bueno el post!

En mi caso estoy intentando mejorar el consumo de memoria de las consultas para un servidor de aplicaciones. ¿Que ventajas me aportaría realizar un query.setCacheable(true)? ¿Crees que debería de activar la caché de segundo nivel?

Gracias por todo

Federico Varela dijo...

enredado, gracias por el comentario.
Sobre tu consulta, query.setCacheable(true) sirve para cachear el resultado de una consulta tal como lo traté en el post "Query Cache en Hibernate", puedes encontrar el link en el margen derecho de la página. Te sirve cuando una consulta se repite constantemente y la entidad resultado no cambia frecuentemente, por ejemplo cuando necesitas desplegar en muchos lados la lista de países o monedas.
Con respecto al second level cache, te conviene activarlo pero debes tener en cuenta las recomendaciones que sugerí sobre que entidades seleccionar y las relaciones one-to-many.

Saludos

enredado dijo...

Pues hoy ya me he quedado con la copla. Además estuve realizando unas pruebas y cacheando resultados. La caché de hibernate la veo muy bien para evitar consultas a la base de datos. El problema es que las listas de objetos las tengo que tener en ArrayList para proporcionárselas a las JSP. En fin, que a efectos de consumo de memoria poco lo noto.

Gracias por el coment!.

Saludos,

Anónimo dijo...

tengo una consulta, existe en hibernate como
property name="eclipselink.cache.shared.default" value="false"

tengo ciertos datos que son modificados de forma constante desde el exterior (otras aplicaciones) sobre la BD

y esta instruccion pudo resolver mis problemas.... agradecido de antemano

Daniel dijo...

Muchas gracias, me ha servido de mucho.

Guillermo dijo...

Alguien me podria decir que puedo hacer cuando en mi aplicacion los usuarios tieneden a trabajar paralelamante con las mismas entidades para evitar conflictos a la hora de dar update a la base de datos con Hibernate?

Delawen dijo...

Gracias, sencillo y claro :)

@Guillermo, mira aquí: https://www.hibernate.org/333.html

Federico Varela dijo...

Gracias por los comentarios, hace tiempo que no escribo nada en el blog, voy a ver si le dedico un tiempo.

@Guillermo: Tienes que usar algo que se llama Optimistic Locking, voy a ver si escribo un ejemplo porque el tema se merece un post.

Jorge Luis dijo...

Hola que tal como le puedo hacer para recuperar datos relaes de las Base de datos y no de la Cache con hibernate

Jorge Luis dijo...

Hola que tal como le puedo hacer para recuperar datos relaes de las Base de datos y no de la Cache con hibernate