Entradas con la etiqueta ‘hibernate’
-
6 comentarios »
Tres personas me han preguntado últimamente porque ORM (Object-Relational Mapping) escoger para sus proyectos Java. A los tres les he contestado lo mismo, así que por si sirve de algo a alguien, dejaré aquí la respuesta.
Siendo honestos (en términos de uso, perspectiva de futuro, etc.) las opciones son pocas en este caso, al menos a día de hoy. Básicamente dos:
- Hibernate
- JDO 2.0
Hibernate es sin duda el rey. El más usado, existe muchísima documentación, probado y más que probado, etc. Lo que quieras. Sin embargo mi recomendación es para JDO 2, por estas tres razones básicamente:
- Estándar: quieras que no, Hibernate sólo hay uno, implementaciones de JDO, no. Puedes pasar de una a otra (incluso libres) sin grandes problemas.
- Sencillez: opinión personal, pero JDO 2.0 es mucho más sencillo. Quieras que no es más moderno, se ha aprendido de errores, incluso Gavin King (el creador de Hibernate) ha puesto su granito de arena para que esto sea así. Te puede gustar más o menos la forma, pero es más sencilla.
- Futuro: el API de JDO 2.0 es casi exacto al API de EJB 3 (para los despistados: olvidar todo lo que sabéis de EJBs), aprender JDO 2.0 es aprender EJB 3.0. De hecho, el propio Hibernate va a soportar este API en futuras versiones.
Esas son mis razones a favor de JDO. En contra… sólo una…. la escasez de documentación. Es suficiente para hacer de todo (relaciones, attach/detach complejos, queries de todo tipo, cachés, etc), pero debido a su juventud la cantidad de artículos es bastante más reducida. ¿Comentarios?.
-
No hay comentarios »
Sigo con mi serie de posts sobre el desarrollo de OCAS. En este intentaré describir mi problema, y en el próximo lo que creo que va a ser la resolución de esta apasionante serie :-P. El objetivo que nos hemos marcado en OCAS es proporcionar una capa de servicios que ayuden a la programación en tareas típicas a los que nos enfrentamos proyecto a proyecto, abstrayendo la complejidad de las librerías a los programadores. De la misma forma nuestra idea es dar diferentes implementaciones a esos servicios para que el desarrollador elija la que más le guste. En breve el wiki contendrá más información. En estos momentos me encuentro desarrollando la implementación del servicio de workflows con la librería open source JBPM, que a su vez usa como mecanismo de persistencia hibernate. Y es aqui donde empezó la paja mental causante de esta serie de posts ;-) Veamos, jbpm ofrece en su API unas “sesiones” jbpm, que no son más que un wrapper de las sesiones hibernate. A partir de una sesión jbpm se obtienen unos objetos sesión (GraphSession, TaskMgmtSession…), que yo calificaría como una especie de DAO’s (para entendernos ;-)) para trabajar (CRUD) con determinados aspectos de un proceso. El tema es que en jbpm hacen aaaampliooooo uso del lazy loading (las variables de un proceso son lazy por ejemplo). Hasta aquí puedo leer ;-), que me reservo un post sobre jbpm cuando lo tenga acabado. Y aquí es donde empezamos a chocar con OCAS: nuestra pretensión es facilitar la vida al programador, no decirle que tiene que aprender hibernate para usar un servicio de workflows, y que tiene que tener claro que es una sesión y que es una transacción en hibernate. Y aquí es donde me parece que pecan algunas librerías que usan hibernate, simplemente transportan la interfaz de hibernate hacia arriba y le cuentan al programador que se mire la documentación. Por ejemplo, en jbpm hace falta conocer hibernate y su manejo de transacción/sesión y los patrones involucrados (ver post anterior) para entender porqué narices sale esa LazyInitializationException cuando pido las variables de un proceso fuera de la sesión jbpm que lo cargó. Para mí no tiene mucho sentido. Generalmente el código que usa hibernate adquiere una fuerte dependencia con él, lo cual tiene mucho sentido en DAO’s de acceso a BD, pero que deja de tenerlo cuando simplemente son librerías que hacen otra cosa además de usar hibernate ;-)
En mi implementación del servicio de OCAS he barajado varias soluciones para no molestar al usuario programador con los detalles de la implementación concreta en jbpm:
- Modificar el código de jbpm para que determinadas relaciones no sean lazy: no sigue la filosofía de OCAS, en la que damos la implementación de nuestros servicios sin tocar el código fuente de las librerías.
- Abrir y cerrar sesión en cada uno de los métodos de mi servicio de workflow. Por ejemplo así:
public Map getVariables(){ JbpmSession sesion = wfService.getJbpmSession(); ProcessInstance process = sesion.getGraphSession().loadProcessInstance(processInstance.getId()); Map result = process.getContextInstance().getVariables(); wfService.closeJbpmSession(sesion); return result; }Buff, para mí apesta :-), se podría ver como una sesión por petición, pero el problema es que aquí no se trata la petición de un usuario web, sino de una llamada a un método de una librería, y en un request de una aplicación web puede haber varias decenas de llamadas a métodos. No se aprovecha el caché de primer nivel y además me obliga a cargar de nuevo el proceso desde la BD (porque antes lo cargué en una sesión jbpm/hibernate distinta) aunque ya lo tenga en memoria. Como ventaja lo único que puedo decir es que en la interfaz ofrecida del servicio al programador el no ve ni sesiones ni transacciones de hibernate, lo cual es mi objetivo.- Alguien podría decirme:
Bueno pues cargate toda la info del proceso en memoria cuando se cargue el proceso y montate tu propia caché
Puede ser, pero creo que he encontrado una solución mejor –> más productiva y eficiente (y sin escribir mucho más código) Pero la solución (que desvelaré en un próximo post para mantener la intriga, y poder hacer los tests :-)) me vino el día de la hispanidad (como buen programador yo no tengo fiestas :-() mientras fregaba mi taza del café. Sólo os adelanto que tiene que ver con uno de los patrones que describen en hibernate, y que los usuarios de OCASWF sólo deben conocer el significado de una transacción (el general, el que conoce todo el mundo vaya). Os dejo con este atroz suspense, para que estos posts sirvan para ver las ventajas de usar OCAS y los sudores que nos cuestan. Salu2 PD: Sólo espero que hibernate 3 haga todo lo que promete :-D
-
16 comentarios »
Embarcado en el desarrollo del servicio de workflow de OCAS y en su implementación con JBPM me he tropezado (de nuevo) con un iceberg: las sesiones de Hibernate y el cómodo/incómodo lazy loading. Existe en javahispano un estupendo artículo de Martín sobre las sesiones y las transacciones de Hibernate que recomiendo a todo el mundo; pero intentaré resumir aquí cuál es el problema (a ver si lo consigo). El lazy loading o carga perezosa es un mecanismo que hibernate implementa para tratar algunas relaciones. Una entidad (POJO que se persiste en una tabla de la BD) puede tener múltiples relaciones con otras entidades (por ejemplo, un producto puede tener una Collection de los comerciales que lo venden). A veces cuando nos traemos datos de la BD no nos interesa cargar toda la información relacionada ya que sólo vamos a usar o mostrar una parte. Así que, siguiendo con el ejemplo, si queremos mostrar el número de referencia de un producto y su nombre en una interfaz de usuario, cargar los datos de los veinte comerciales que lo venden en memoria ocupa espacio innecesariamente. Usando el lazy loading, si definimos una relación como lazy (poniendo el atributo de la relación lazy a true en los mappings de hibernate), Sólo se cargará la información de los vendedores CUANDO accedamos a la Collection vendedores. Así por ejemplo:
producto.getVendedores(2).getName();
lo que provocará es un nuevo acceso a la base de datos para cargar la información de los vendedores. Llamarlo carga perezosa obedece a que no hemos tenido que escribir las líneas de código para traerlo de la base de datos: hasta que no hagamos uso de esa relación no se cargará en memoria, y cuando hagamos uso de ella “automágicamente” se traerá la información desde la base de datos. Por tanto para el programador resulta un mecanismo muy cómodo que permite realizar aplicaciones que aprovechan muy bien la memoria, sin cargar datos innecesarios hasta el momento que son necesarios.EL PRECIO DEL LAZY LOADING Claro está que cuando he escrito automágicamente nadie habrá pensado en Gandalf trayendo nuestros datos. Lo que hace hibernate es crear una clase wrapper de nuestra Collection y asociarla al objeto (por ejemplo un Set de vendedores). Estas clases wrapper actúan como un proxy: son clases herederas de las correspondientes Collection cuyos métodos están sobreescritos por los chicos de hibernate y lo que hacen es traer de la base de datos la entidad correspondiente. Pero para traer algo de una base de datos en Hibernate ES NECESARIO que la misma sesión de Hibernate en la que te has cargado el objeto original esté abierta. Y no se te ocurra pedir a los de hibernate que se pueda hacer abriendo una nueva sesión (sino quieres que se mofen de ti) porque como explican aquí se cargarían el concepto de transacción.
Hibernate sólo puede asegurar que el objeto original existe dentro de la misma transacción/sesión, por lo que si abres una nueva sesión deberás volver a cargar el objeto original y luego su Collection de entidades relacionadas. Y aquí es cuando empiezan los problemas ;-)
ERRORES DE NOVATO EN HIBERNATE Hace un año, para un proyecto que me tocó decidí utilizar hibernate para matar dos pájaros de un tiro (el proyecto era pequeño y así yo aprendía a usar hibernate). Tranquilos, pagué mi audacia con muchas horas de lectura de manuales y artículos :-D
Todo ufano cree un montón de relaciones lazy porque así mi aplicación web iba a ser la leche en verso (sólo en memoria lo que necesitara). Cuando llegué a mi primer jsp en el que además de datos de un producto de un almacén debía mostrar el nombre de su encargado (relación lazy por supuesto, ya que el encargado igual llevaba 700 piezas) me saltó la bonita LazyLoadingException. Vale, oh que tonto soy, el nombre del encargado viene a través de una carga perezosa y yo he cerrado la sesión de Hibernate al traer el producto. Pero…. glups!, si el único sitio en el que hago
producto.getEncargado().getNombre()es en la página JSP!! Y yo abro la sesión en mi DAO y allí debería cerrarla (como chico aplicado y cumplidor que soy ;-)). ¿Que hago? ¿Le paso a la página jsp la sesión para que la cierre en cuanto me traiga el nombre del encargado? ¿Me tiro en postura fetal y espero que Dios arregle mi código? :-DTras secar el sudor frío de mi frente, me agencié “Hibernate in Action” el libro escrito por los desarrolladores de Hibernate y me puse a indagar cómo solucionaban ellos este problema.
Patrones, anti-patrones y opiniones Vale, pues en Hibernate 2.1 (luego hablamos de la 3 que esto va para largo) explican claramente los distintos patrones que hay (y que Martín explica excelentemente en su artículo, leedlo!), recomendando el patrón session-per-request: por cada petición del cliente se abre una sesión, se trae de la BD lo que se tenga que traer y se cierra la sesión.
El tema está en ¿cómo lo implementamos en una aplicación web? Bueno pues en Hibernate 2.1 proponen una clase (HibernateUtil) que usando variables ThreadLocal (explicación de las poco conocidas variables ThreadLocal ) consiguen la magia, haciendo que cada petición (que en una aplicación web genera un thread) tenga a su disposición la creación de sesiones. Un pequeño resumen de la clase a continuación:
public class HibernateUtil { private static Configuration configuration; private static SessionFactory sessionFactory; private static final ThreadLocal threadSession = new ThreadLocal(); private static final ThreadLocal threadTransaction = new ThreadLocal(); private static final ThreadLocal threadInterceptor = new ThreadLocal(); (...)/** * Retrieves the current Session local to the thread. * * If no Session is open, opens a new Session for the running thread. * * @return Session */public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { if (getInterceptor() != null) { s = getSessionFactory().openSession(getInterceptor()); } else { s = getSessionFactory().openSession();
} threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; } /** * Closes the Session local to the thread. */ public static void closeSession()throws InfrastructureException { try { Session s = (Session) threadSession.get(); threadSession.set(null); if (s != null && s.isOpen()) { s.close(); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } }(...) /** * Register a Hibernate interceptor with the current thread. * * Every Session opened is opened with this interceptor after * registration. Has no effect if the current Session of the * thread is already open, effective on next close()/getSession(). */ public static void registerInterceptor(Interceptor interceptor) {
threadInterceptor.set(interceptor); } private static Interceptor getInterceptor() { Interceptor interceptor = (Interceptor) threadInterceptor.get(); return interceptor; }</pre>No he posteado la creación de la SessionFactory (se crea en un bloque de código estático), pero creo que el código se explica sólo ;-) Con este código es posible definir un Interceptor en Hibernate, que será llamado al abrir o cerrar una sesión, para hacer lo que queramos en esos eventos.
Y un pequeño ejemplo de código donde se usa (por ejemplo dentro de un DAO):
HibernateUtil.beginTransaction() Query query = HibernateUtil.getSession().getNamedQuery("query1"); result = query.setEntity("usuario", usuario).list(); return result;Uopss!! ¿Y donde has dejado el closeTransaction y el closeSession? Pues como decía antes, NO puedo cerrar la sesión en el DAO porque eso me quitaría la posibilidad de acceder a las relaciones lazy desde mi página jsp. Según el patrón sesion-per-request, la sesión se cerrará cuando toda la información se haya enviado y dibujado en la interfaz del usuario. Y en una aplicación web el sitio para hacerlo es un filtro de Servlet, que se ejecuta cuando la página jsp ya ha terminado de dibujar la pantalla. Como muestra un botón:public class HibernateFilter implements Filter {private static Log log = LogFactory.getLog(HibernateFilter.class); public void init(FilterConfig filterConfig) throws ServletException { log.info("Servlet filter init, now opening/closing a Session for each request."); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // There is actually no explicit "opening" of a Session, the // first call to HibernateUtil.beginTransaction() in control // logic (e.g. use case controller/event handler) will get // a fresh Session. try { chain.doFilter(request, response); // Commit any pending database transaction. HibernateUtil.commitTransaction(); } finally { // No matter what happens, close the Session. HibernateUtil.closeSession(); } }</pre>De esta forma cuando se haya dibujado la página jsp, se realizará un commit de todos los cambios a la base de datos y se cerraran la sesión que haya abierto el cliente (una sesión por petición). Existe otra variante que en lugar de cerrar sesiones, las conecta y las desconecta (de la conexión JDBC subyacente). Esta forma de trabajo, en la que abrimos sesiones en un lado y las cerramos en otro no acabó de convencer a todo el mundo, ni a mí, ya que hace a tu aplicación dependiente de Hibernate, no sólo en los DAO's sino incluso en la capa de la aplicación web. Con Hibernate 3 el modelo cambió.
HIBERNATE 3: Donde dije Diego..
Como aparece aquí , todo ha cambiado en Hibernate 3, en el que han ampliado la clase SessionFactory con el método getCurrentSession(). Así que ahora podemos tener:
try { factory.getCurrentSession().beginTransaction();No he mirado el código (el tiempo, ese escaso bien) pero "huele" bastante a que la implementación de ese método usará ThreadLocal internamente como el HibernateUtil.// Do some work factory.getCurrentSession().load(...); factory.getCurrentSession().persist(...); factory.getCurrentSession().getTransaction().commit();} catch (RuntimeException e) { factory.getCurrentSession().getTransaction().rollback();
throw e; // or display error message}
Para librarnos de abrir y cerrar transacciones, de nuevo el filtro de servlet. Al ejemplo puesto antes ahora lo llaman patrón Open Session in View (¿verdad que es chulo llamar patrón de diseño a toda solución? ;-)) y está descrito aquí . La discusión de porque no crea una "dependencia real" (sic) con Hibernate aquilicuá . Que cada uno saque sus conclusiones, pero ya os aviso del tono de los Hibernate Boys: "si no estás de acuerdo conmigo es porque no tienes ni pajolera idea". Aunque a veces resulta divertido escuchar a un prepotente, a la larga cansa :-D
En Hibernate 3, y como cada vez se acercan más al estándar EJB 3.0 (¿porqué será? ;-)), también existe la posibilidad de marcar las transacciones no sólo con el filtro de servlet, sino también con JTA. Así definiremos en un descriptor de despliegue (EJB2) o con annotations (EJB3) que determinados métodos que llamamos, requieren el inicio de una transacción, así nuestro código quedará más "bonito":
// Do some work factory.getCurrentSession().load(...); factory.getCurrentSession().persist(...);
Y tendremos una sesión por transacción (tal y como recomiendan en el patrón sesion-per-request). Recapitulando: - O usamos JTA, en un entorno EJB - O usamos el open session in View, con un filtro de servlet que nos abra transacciones.Y en eso que llega la "primavera" y a todos la sangre altera ;-) SPRING/HIBERNATE Los desarrolladores de Spring en su afan de hacer la vida más fácil a los programadores proporcionaban desde Hibernate 2 unas clases de apoyo para integrar Hibernate en una filosofía IoC. EL SessionFactory de Hibernate se define como un bean en el contexto de la aplicación. Usando XML el bean se define así:
Y para que nuestros DAO's tengan acceso a ese SessionFactory:product.hbm.xml </property>net.sf.hibernate.dialect.MySQLDialect </props> </property>
Así, el código quedaría:... public class ProductDaoImpl implements ProductDao { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; }Primera gran diferencia: NO necesitamos HibernateUtil. Es más en el manual de referencia lo indican:We strongly recommend such an instance-based setup over the old-school static HibernateUtil class from Hibernate's CaveatEmptor sample application! (In general, do not keep any resources in static variables unless absolutely necessary!)
Toma torpedo a la línea de flotación de Gavin King. Y claro luego veremos como se toma las cosas el Gavin ;-D Para acceder a las sesiones (y por tanto empezar a usar las posibilidades de hibernate) ofrecen varias alternativas: - Usar la clase HibernateTemplate, que además de tener implementados ciertos métodos útiles (find, load, saveOrUpdate, o delete) permite "callback implementation" (una forma de realizar los punteros a funciones en Java, info ):
public Collection loadProductsByCategory(final String category) throws DataAccessException { HibernateTemplate ht = new HibernateTemplate(this.sessionFactory); return (Collection) ht.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Query query = session.createQuery("from test.Product product where product.category=?"); query.setString(0, category); return query.list(); } }); }- Usar como base de tus DAO's la clase HibernateDAOSupport, y dentro de esta usar el HibernateTemplate, o la API de Hibernate directamente:public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { return getHibernateTemplate().find( "from test.Product product where product.category=?", category);- Usar la API de HIbernate 3 para obtener las sesiones:}}
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException, MyException {
Session session = getSession(getSessionFactory(), false); try { List result = session.find( "from test.Product product where product.category=?", category, Hibernate.STRING); if (result == null) { throw new MyException("invalid search result"); } return result; } catch (HibernateException ex) { throw convertHibernateAccessException(ex); } }}
public class ProductDaoImpl implements ProductDao {En los dos primeros casos la ventaja de usarlos en Hibernate 2 es clara: no nos tenemos que ocupar de abrir o cerrar sesiones, lo hace el HibernateTemplate por nosotros, además de hacer que nuestras sesiones entren a formar parte de una transacción (de Spring, de la que luego hablaré). Claro está que para solucionar el tema del LazyLoading con el que empezé este ladrillo la solución es semejante a la de Hibernate, usar un filtro o un interceptor . Lo bueno es que es el HibernateTemplate el que comprueba si tenemos una sesión asociada al thread o no y cierra la sesión si no existe este filtro (Alguno podría decir que eso también se puede implementar en un HibernateUtil: pues sí, pero con Spring "viene de fábrica" ;-) En la tercera alternativa el código es el mismo que el que usaríamos en Hibernate (salvo que obtenemos el SessionFactory por IoC), con la salvedad de que ahora podemos usarlo sin necesidad de definir transacciones con JTA, sino con los mecanismos de Spring. Y aquí es donde empiezan las leches entre las divas del bel canto ;-Dprivate SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public Collection loadProductsByCategory(String category) { return this.sessionFactory.getCurrentSession() .createQuery("from test.Product product where product.category=?") .setParameter(0, category) .list();} }
DEPRECA LO QUE ME APETECE, INSUSTANCIAL! Más o menos es lo que parece querer Gavin King respecto a Spring. A raíz de que de la página de hibernate "desaparecieran" las referencias a Spring la polémica estalló, y cómo no, en un blog
Más o menos, Gavin King viene a decir que HibernateTemplate es una bazofia poco elegante y que los admiradores de Hibernate no deberían usarla ya que en Hibernate 3 está tirado obtener sesiones y el manejo de transacciones (con JTA). Y que la "deprequéis" chavalillos de Spring. A lo que los de Spring responden: es mi código y hago lo que quierto con él, sino quieres usar HibernateTemplate NO LO USES, pero no molestes :-D
Vale, pero es una bazofia tan poco elegante como HibernateUtil propuesto en Hibernate 2 (yo diría que menos). Vamos que ha sido una bazofia que ha habido que usar bastante con Hibernate 2.
Por otro lado, se le "olvida" comentar un pequeñísimo detalle, que es la madre del cordero del asunto: con Hibernate si quieres usar getCurrentSession() TIENES que usar transacciones JTA (vamos programadores a pasar por la tabla de EJB3, no os preocupéis de los tiburones que veis ahí abajo!), si usas Spring puedes usarlo usando los mecanismos de transacciones de Spring (que puede usar JTA o no). Osea que si quieres esa comodidad en Hibernate 3 a usar un servidor de aplicaciones (¿te he dicho ya que hibernate pertenece al stack de aplicaciones de Jboss, y que Spring rechazó la oferta de pertenecer a ella?), en cambio Spring te deja la libertad de usarlo dentro o fuera de un servidor de aplicaciones. Ahora que cada uno tenga su opinión, que yo ya he escrito demasiado ladrillo. En un post siguiente mis problemas con aquellas librerías que usan Hibernate por debajo, y si lo consigo ;-) cómo lo soluciono. Salu2
-
Sobre LinkedLinked es el blog de Linking Paths, la empresa aventurera e innovadora formada por Aitor Garcia, Alberto Molpeceres y Roberto Salicio. En él hablamos de nuestros productos, ideas, y de compañías que nos sirven como guía y ejemplo. Si quieres conocernos un poco mejor puedes revisar lo que hemos escrito en los archivos.
-
Proyectos, ideas, etc.



