Hibernate, cache, carga de datos en tiempo real.
Hola amigos de JavaMéxico el motivo de este post en este foro es para comentarles algo curioso que me ocurre con hibernate y al mismo tiempo solicitarles su ayuda para resolverlo o sugerencias que ayuden al buen funcionamiento de mi problemita...
Estoy realizando una aplicacion de ejemplo web, con Hibernate y JPA así como MySQL y tengo el siguiente codigo:
la entidad rol mapeada de la siguiente manera:
el siguiente codigo en el DAO realiza el select a la BD y me regresa todos los registros de la entidad rol
bien hasta aquí lógicamente todo funciona, ahora tengo un jsp que ejecuta el metodo getRoles por medo de un backing bean y genera una tabla con el resultado de dicha consulta (no creo conveniente poner el código de jsp y de baean ya que lo debuggie y no parece haber error alguno) el detalle es que cuando ejecuto mi aplicación y pongo la dirección en el navegador de mi jsp me genera la tabla correctamente y con todo el contenido que hasta este momento esta en la tabla de la BD, hasta este punto todo esta genial, pero en teoria se supone que si agrego un nuevo registro (desde phpMyAdmin por ejemplo) a la tabla en la BD, solo con hacer un refresh a la pagina ese registro debe de mostrarse tambien ahi, y esto es lo que no hace, aunque de tantos refresh como mi dedo me lo permita.
Pero si cierro el navegador y vuelvo a hacer "run o server" desde eclipse, esta vez ya me muestra el nuevo dato agregado, y si vuelvo a agregar otro y actualizar espero que lo muestre pero no, lo muestra hasta que vuelvo a "correr todo el proyecto",...
Esta situacion me llevo a depurar todo el proyecto y llegue hasta el bloque de codigo anterior "getRoles()" asi que de lo anterior me salen las siguientes dudas que espero puedan ayudar a esclarecer:
1.- Se supone que no involucra ninguna session dado que ese codigo interactua directamente con la BD cierto??
2.- Al no haber ninguna session cada vez que se ejecute ese codigo, deberia de cagar todo lo que haya en BD actualizando si hay bajas o altas en la tabla, corecto?
3.- Me queda claro que Hibernate tiene cache, mi codigo esta trabajando con la chache de hibernae??
4.- Me comentaron que hibernate al arrancar y hacer una consulta mantiene una session con los datos recuperados, de tal manera que si ago la insercon o baja de un registro con hibernate el cambio si se refleja como deberia, pero que si hago esa inserion o baja por algun otro medio (ejemplo phpMyAdmin) hibernate no va a tomar en cuenta ese cambio, que de cierto tiene esto?
5.- si es cierto este ultimo punto, hago una prueba :
corro mi app
llamo la jsp que muestra la tabla
me muestra todos los registros
ejecuto un test que inserte un nuevo registro
le doy refres a la jsp que muestra la tabla
optimo: muestra el registro insertado
mal: persiste el error y no muestra cambio
el punto 5 no lo he echo, pero espero sus sugerencias y comentarios aclaro que soy nuevo en hibernate y ahi hago mi luchita, si necesian ver algun otro archiv de configuracion porfavor comenteme y gracias de antemando por su ayuda..
Saludos
Roberts..
- Inicie sesión o regístrese para enviar comentarios
algunos aspectos
Todo en hibernate involucra una sesion, en el ejemplo que estas dando estas sacando la session actual, pero, lo que me llama la atencion es como creas el contexto del dao.
Hibernate cachea las entidades, por lo que al tu insertar un registro desde fuera, para hibernate el cache no se ha modificado y por lo tanto te regresa las mismas entidades cacheadas.
Podrias hacer un flush de la sesion para limpiar el cache cuando termines la llamada al metodoy el siguiente query se vuelve a traer los datos de la bd, ya que no hay nada cacheado, o podrias utilizar los famosos dto's, estos son consultas directas a la base sin caches de por medio, un dto es un mapeo entre un objeto y un query que hayas definido que trae información de la base de datos, estos solo son readonly, por lo que no podras hacer nada con ellos, no son persistentes vaya.
sobres
Hola Nopalin, muy
Hola Nopalin, muy interesante tu respuesta, voy a intentar hacer con el flush, me gustaría ver un ejemplo de como se haría con el DTO, me surge una cuestión, según tu respuesta mi app debe funcionar como se espera si todas las operaciones ocurren desde la app originalmente esta pensado que todo va a ocurrir dentro de Hibernate, entonces esto no debería de darme ningún problema es cierto?
transaccion...
Para qué creas una transacción en el método de obtener roles? Yo lo cambiaría un poquito para que sólo fuera así (esto no va a resolver el problema, pero creo que estaría mejor el código así):
Los cambios que esto proponiendo a tu código:
Aún así, no sé si sea la mejor manera. Ese
al final me hace un poco de ruido; si utilizas el patrón open session in view entonces no se deberían cerrar las sesiones en estos métodos (el de
y otros similares que tengas) para que siempre se usa la misma sesión de Hibernate para una ejecución de un JSP.
El cache de Hibernate se configura en la fábrica de sesiones si mal no recuerdo; pero también según recuerdo, por default no se activa, solamente para entidades que tú especifiques. Este problema que dices yo por ejemplo no lo tengo cuando estoy probando JavaMéxico 2.0 y lo hice varias veces porque primero hice páginas que mostraban listas de datos y no tenía manera de insertar en la aplicación, de modo que insertaba directo en la base de datos y al refrescar la página los datos nuevos aparecían tal como lo esperas.
Pusiste algun println o log en la página que obtiene los roles, para estar seguro que cuando refrescas la página, se vuelve a invocar el método? tal vez el cache se hace en otro lado (web server, contenedor, etc) y se devuelve la misma página y por eso ni se invoca tu método de
otra vez, por eso ves los mismos datos. Es decir, tu problema sí parece ser con un cache, pero no necesariamente es de Hibernate. Otra prueba es que pongas la fecha hasta el segundo o milisegundo (incluso un simple
) en la página, para que compruebes que el número cambia cada vez que la recargas.
hola @Zamudio gracias por
hola @Zamudio gracias por las sugerencias,
debugeo con el debug de eclipse para asegurarme de que entra al metodo y tamien llevo un log, por lo cual si estoy seguro que ese metodo se manda a llamar, asi que corrigeme si me equivoco:
esta linea pide directamente los datos a la BD como lo hace? ahi ya se encarga Hibernate o no es asi?
entonces cada que entre a ese metodo "roles" se deberia de llenar con el contenido de la tabla en la BD tomando en cuanta los cambios que se realizen (altas, bajas, edicion, etc) aunque sea desde fuera de mi aplicacion, asi yo lo entiendo y asi deberia funcionar pero no lo hace.
como referencia:
en mi hibernate.cfg.xml tengo desabilitada la chache de la siguiente manera:
problema con sesiones
Entonces es probable que tengas un problema con las sesiones, que siempre se use la misma y nunca se cierre y tal vez ahí sí se hace un cache interno de la sesión y por eso siempre obtienes los mismos datos. Por eso mi sugerencia de cambio al método pero sobre todo que revises bien el diseño de tu app, si implementas open session in view o si tienes un DAO que en cada interacción con Hibernate abre y cierra sesión (y revisar que lo haga bien porque tenías esa variable
como de instancia y la estás tratando como local).
bien te muestro mi
bien te muestro mi DAO:
aqui puse el sesson.flush(); por sugerencia de @Nopalin, pero no funciono, esto es todo lo que utilizo para acceder a los datos.
nota, no he echo las correcciones que mencionas, como vez el DAO me sugieres que las haga?
Sí
Aplica las correcciones. Quita la variable de instancia
. El flush en todo caso lo debería hacer ANTES de obtener la lista de módulos o roles.
al fin pude hacer las
al fin pude hacer las correcciones @Zamudio, y quedo de la siguiente manera:
tambien te comento que session.beginTransaction(); no lo pude quitar porque me marca el siguiente error:
javax.servlet.ServletException: org.hibernate.HibernateException: createCriteria is not valid without active transaction
bueno como quedo lo probe y sigue igual... cosa que se me hace muy extraño.
gracias..
crear una sesion
Has leido alguna documentacion que te diga por que es recomendado usar la linea de codigo que extrae la sesion actual de hibernate?
Lo mas recomendable es que cada llamada cree una nueva sesion, y no te preocupes por accesos concurrentes, hibernate es thread safe.
Por que no haces algo como esto:
sobres
Hola nopalin acabo de probar
Hola nopalin acabo de probar tu codigo, y dejame decirte, que tenemos la misma cosa.. hasta estoy llegando a pensar que es el control de la vista, pero no puedo ir en contra del debug ni del log, si estos me dicen que se carga "roles" y el contenido que veo ahi es el que se muestra por eso ahi no hay problema..
Si tu codigo se nota la diferencia nopalin, porque con el que tenia anterior no tardaba al pedir la session actual ya que e usaba la misma, pero aqui al hacer .openSesson tarda unos segundos en abrirla, pero el problema persiste :(
openSession
Cada vez que llamas openSession, Hibernate pide una conexión al DataSource. Dependiendo cómo tengas configurado tu DataSource, esto puede crear una nueva conexión física al RDBMS. Por eso insisto tanto en el uso de DB pools.
El método de Nopalin está mejor porque trae ese
que a ti te falta, para SIEMPRE cerrar la sesión.
Esa anotación de @Proxy(lazy=false) que le pusiste a la entidad, para qué es? Tal vez eso le indica a Hibernate que cargue todos los registros de la tabla y los guarda en memoria (o sea, a fin de cuentas, un cache), y por eso cuando le pides nuevamente un list, simplemente toma los objetos de la memoria en vez de leer nuevamente la base de datos.
según lo que lei, es para
según lo que lei, es para que hibernate no haga carga lenta, pero sino se la pongo, marca el siguiente error:
javax.servlet.ServletException: java.lang.ClassNotFoundException: net.sf.cglib.proxy.CallbackFilter
Raro
Nosotros no le ponemos eso a las entidades de jm2.0 y cargan sin ningún problema.
sip, yo tapoco se la queria
sip, yo tapoco se la queria poner, pero es la unica manera de que jale y eso compare muchos tutos y lo unico diferente era esa anotacion asi se la puse y jalo, de echo si le pongo true marca el mismo error, y aqui esta una parte del log:
Mas info
Puedes indicarnos como estas inicializando hibernate? Si es atravez de algun framework que utilizes o lo haces a manita y si tambien estas configurando un data source.
Creo que para resolver la excepcion de arriba tienes que buscar una libreria llamada cglib.
sobres
hibernate.cfg.xml <?xml
hibernate.cfg.xml
y lo inicializo de la siguiente manera:
con esto ya en mi DAO puedo perdir la session sino existe crear una nueva y ya empezar a manipular datos..
mal momento
Con razón tienes esas broncas. Estás cargando la session factory en muy mal momento: cuando se carga tu clase HibernateUtil. Seguramente esa clase viene en tu war y por lo tanto se carga antes que cualquier librería. En los contenedor JEE por lo general se tiene un classloader que se carga las clases del war y otro separado que carga las clases de las librerías (así como luego pueden recargarse los JSP y cosas así).
El static de la clase se ejecuta en cuanto se carga la misma. Y en el momento en que se ejecuta, sólo se tiene a la mano el classloader que la está cargando, el cual no puede traerse las clases de CGLIB porque son externas.
Si usas Spring puedes tener una sessionFactory dentro de un application context y se la inyectas a los DAOs que tambien los pones en el application context. Si no quieres hacer eso entonces lee la documentación de Hibernate donde se indica cómo poner una session factory en una aplicación web, porque esa no es la manera de hacerlo.
:o muchas gracias por tu
:o muchas gracias por tu comentario @Zamudio, aqui si por cuestion de tiempo tendre que esperar a mi profe :) tambien gracias a @Nopalin, cuando esto quede les cuento y ya me dan mis regaños saludos..
¿Un ejemplo sin el bloque static?
¿Hola alguien tiene un ejemplo de inicialización de hibernate sin el bloque static?
Saludos y Gracias
Creación al momento
Se puede modificar el código para que simplemente se genere el sessionFactory hasta que se haga la primera llamada al mismo, después que inicia la aplicación.
El doble
es para evitar crearlo doble en caso de una llamada concurrente al método estático. Si el mismo código se ejecuta en dos Threads distintos, ambos pueden llegar a ese primer if y en ambos se cumple la condición; por eso hay inmediatamente un bloque de sincronización, al cual solamente entrará un Thread a la vez, y ahí dentro hay que checar nuevamente si ya existe la sessionFactory; el primer hilo en entrar volverá a obtener null en la comparación y por lo tanto crea la sessionFactory, pero los siguientes hilos esperando al bloque de sincronización ya no van a entrar a esa condición porque ya existe la sessionFactory.
mi estimado @Zamudio mi
mi estimado @Zamudio mi HibernateUtil quedo de la siguiente manera:
pero déjame decirte que probé sin éxito :( aun le sigo dando vueltas al problema, ahora si actualiza, pero de manera intermitente es decir agrego el nuevo registro (ya desde mi aplicación) y le doy f5 a mi pagina, una vez, dos veces tres veces y en algunas aparece el nuevo registro al final de mi tabla luego con el siguiente refresh desaparece, luego con el siguiente refresh aparece y así intermitentemente...
alguna otra sugerencia ?
gracias de antemano..
es lo mismo
Es que no hiciste nada realmente. Sigues intentando crear la SessionFactory en el static de la clase. Mira bien el ejemplo que te puse; el objetivo es crear la SessionFactory en el método getSessionFactory.
solucion temporal...
les comento que recien volvi a retomar esta tarea, y provando, a abrir y serrar la session funciona solo y solo si las modificaciones a la bd se hacen dentro de la aplicacion, sin embargo cuando hago cambios a la bd desde fuera, no los toma en cuenta, por cierto @Zamudio el bloque static de la clase HibernateUtil no se ejecuta cuando se carga la clase como comentas:
El static de la clase se ejecuta en cuanto se carga la misma. Y en el momento en que se ejecuta, sólo se tiene a la mano el classloader que la está cargando, el cual no puede traerse las clases de CGLIB porque son externas.
sino que se ejecuta en la primer petición.
Gracias..
como sigue?
cual es el estado actual del problema?
Mira en realidad yo uso las entidades solo del lado servidor, por que luego tienen relaciones y colecciones y un despapaye que si no inicializas en una sesion de hibernate es muy molesto andar batallando con esos errores, por eso uso los famosos dto's (data transfer object). En hibernate puedes definir un mapeo que solo será de consulta, es decir un dto que se llenara con un subquery que hagas y que nunca será persistente, mas sin embargo sirve para realizar queries como si fuera una entidad.
este es un ejemplo de un dto (el archivo se llama supongamos Alumno.dto.hbm.xml):
Si te das cuenta, en los property name's van las propiedades de tu objeto y en el query las llenas. Este query hibernate lo utiliza como un subquery y bueno.. el hace su chamba. Este archivo obviamente le dices a hibernate que es un mapeo mas, pero el entiende que no es una entidad, y bueno ya lo usas en tus consultas
De esta forma, hibernate siempre realiza el query hacia la base de datos y yo no tengo el problema que mencionas, es decir inserto registro directo a la bd y las aplicaciones de inmediato se ve reflejado.
sobres