Algunos consejos sobre Hibernate


En un par de años he trabajado con varios proyectos que utilizan Hibernate y siempre veo los mismos errores, o mejor dicho, costumbres que pueden perjudicar el rendimiento de una aplicación. Todos estamos propensos a cometer errores y más cuando estás empezando, sin embargo con el tiempo, la experiencia y la investigación activa, podemos intuir sobre que podemos mejorar y evitar estos contratiempos.

Por ello escribo este post, tanto para aconsejar como para recordarme a mi mismo sobre los pequeños detalles que necesitamos tomar en cuenta a la hora de crear nuestra aplicación. Son cosas simples que a la larga nos puede resultar problemático en aplicaciones grandes o cuando manejamos grandes registros.

Recuperar registros con el modo Eager/Lazy

No saben la cantidad de veces que he visto como se perjudica el rendimiento en los grandes listados que muestra una aplicación debido al uso o desconocimiento del modo Eager de Hibernate. Cuando mapeamos las relaciones en los Entity, nos permite indicar que tipo de recuperación podemos utilizar.

Hibernate nos provee de Lazy y Eager, el Eager indica que traiga todos los registros asociados en la relación, mientras que Lazy no, solo lo traerá al momento de utilizar el objeto relacionado.

Esto es importante al momento de realizar una sección de la aplicación, por ejemplo, si tenemos una tabla de Autores y otro de Libros, si solo vamos a mostrar un gran listado de autores con las columnas principales del autor y no necesitamos detalle del libro, es ideal utilizar Lazy y evitar esa consulta adicional. Si en cambio debemos mostrar también detalles de cada libro en el mismo listado se puede dejar el Eager. Lo mismo se tomaría en cuenta si queremos mostrar un listado de libros.

Ejemplo usando Lazy:

Tomar en cuenta el valor por defecto

Cuando no se indica el tipo de recuperación de registros, Hibernate utiliza Eager para relaciones «ToOne» y al contrario utiliza Lazy, es una buena práctica que nosotros mismos establezcamos el tipo de recuperación y sea fácilmente reconocible.

Ejemplo con ToOne cambiando a Lazy:

Usar query directos y Lazy en subconsultas

Cuando trabajamos en grandes reportes o necesitamos obtener los detalles de los registros para luego mostrarlos, se puede realizar simplemente llamando la relación «autor.getLibros()» y entonces Hibernate se encargará de realizar la subconsulta. Esto puede ser muy contraproducente en grandes listados o reportes, porque con cada subquery estamos sobrecargando de consultas a la base de datos.

Ejemplo subconsulta:

Al indicar getLibros, Hibernate realiza una subconsulta para obtener el detalle, en estos casos es común utilizarlo, no es ningún problema este modo de trabajo pero para grandes reportes de cientos de registros, lo mejor es inicializar con la relación en el query usado Fetch de la siguiente manera:

Con ello nos trae de un vez los detalles en una sola consulta, aumentando enormemente el rendimiento.

Usar limite de registros

Cómo sabemos en ciertas consultas JPQL y en ciertas base de datos, no existe las palabras claves «Limit» o incluso «Offset» y es común que nadie los tome en cuenta con aplicaciones en Hibernate, pero a medida que se incrementan los registros se generan problemas de rendimiento, lentitud, al traer todos los registros de una vez. Para ello o cuando queremos realizar paginación, Hibernate nos provee de varios métodos para realizar esto.

Ejemplo usando limites y offset:

Usar Bind Parameters en los query

Al crear consultas manuales donde nosotros indicamos el query, muchas personas tienden a concatenar en un string «WHERE id = » + id y esto es considerado una mala práctica, Hibernate nos provee de parámetros enlazados, una forma mas sencilla y la cual nos trae varias ventajas, fácil de usar, conversiones automáticas, escapa las variables para prevenir inyecciones SQL y permite un mejor rendimiento al optimizar los query.

Ejemplo usando Bind Parameters:

Usar la lógica de negocio en la base de datos

En java puede ser tedioso realizar todos los procesos en la aplicación y luego estar compilando, realizando deploy, verificando y cambiando al servidor desarrollo/producción, y si luego desean realizar la misma operación pero desde otro sistema o utilizar una aplicación móvil, se tendría que volver a codificar la lógica en cada app. Para evitar esto es costumbre pasar la mayoría de lógica de negocio a la base de datos, creando nuestras propias consultas, vistas, funciones y procedimientos almacenados.

Centralizamos en la base de datos y todas estos procesos pueden ser llamados fácilmente desde Hibernate. JPA nos brinda varias funciones por defecto, como por ejemplo «size» que totaliza lo contenido en un Collection.

Ejemplo llamando función simple:

Por ejemplo suponiendo que tenemos varias funciones propias o procedimientos almacenados.

Ejemplo llamando función con JPA function:

Usar funciones propias en el WHERE se pueden realizar sin inconvenientes porque se detecta el tipo de valor de retorno. Sin embargo si es una función que vas a usar en la parte de SELECT debes registrarla previamente en el dialect de Hibernate.

En cuanto a los procedimientos almacenados, es similar al de las funciones y se registran en el entity con la anotación NamedStoredProcedureQuery y se llamaría como un método del objeto.

Estos procesos son largos para explicarlos a detalle en este post, solo es para que tomen en cuenta que si se pueden utilizar.

Realizar update o delete masivos

Cuando debemos eliminar grandes registros o actulizarlos, mucha gente recurre a realizar un select de todos los registros y en un while eliminar uno por uno, esto no es problema con pocos registros, pero si se deben realizar en toda la tabla, se convierte en algo contraproducente y muy lento.

Por ejemplo, queremos actualizar los precios de todos los libros un 20%:

Grandes y complejas consultas SQL

Aunque Hibernate se puede utilizar prácticamente en todo, hay que reconocer cuando no debe ser utilizado, evitar usar métodos automáticos y realizar tu mismo las consultas. Por ejemplo, los Named Query, nos permite realizar consultas personalizas y obtener lo que necesitamos, aun así tiene sus limitaciones y en ese caso podemos recurrir a Native Query.

La idea de esto es evitar que al trabajar con todos los registros, luego al llamar un entity manager y usar sus funciones propias tipo «find», se trae todo en conjunto a los relacionados, ocasionando lentitud y consultas adicionales para información que tal vez no requerimos, en especial en ciertos reportes. Al trabajar con tanta información, tantas tablas y entities, hay ocasiones que puede ocurrir loops infinitos y son cosas que hay que preveer. Incluso no se recomienda usar Hibernate si solo va a realizar puras consultas para mostrar información, solo selects, Hibernate es adecuado para la manipulación de registros.

Trabajar con vistas

Para no realizar complejos SQL, o al trabajar con grandes reportes, lo mejor es hacerlo en base de datos, realizar toda la lógica en una vista y que quede lista solo para mostrar los datos. Hibernate no solo permite trabajar con tablas sino con vistas también y si la base de datos tiene la opción, se puede modificar registros desde una vista.

Algo que me encuentro en cada trabajo, las vistas pueden ser lentas dependiendo de como se hallan realizado. Son consultas muy útiles pero se debe verificar que tantos procesos llama, si provienen de otras vistas, si llama procedimientos almacenados, etc.

Evita usar vistas de vistas que llaman a otras vistas, verifica si son simples o complejas y toma el tiempo de consulta para verificar que no dañe el rendimiento de la aplicación. Evitar complejos subquery, donde la vista aparte de todos los datos de la tabla a mostrar, también ejecutará el subquery en cada uno de los registros, algunos se pueden cambiar por simples join y así evitar múltiples consultas.

Por ejemplo, me ocurría con una vista que al buscar un detalle por id o rango de fechas era super rápido pero luego al realizar la consulta más amplias, rangos de fechas más grandes o traer todos los registros, se podía tardar horas! Todo esto se modificó y se optimizó enormemente verificando los subquery uno a uno.

Usa «EXPLAIN» de la base de datos para verificar el proceso que realiza una consulta y determinar donde está tomando tanto tiempo y así ver que se puede mejorar.

 

Espero les sea de utilidad y no solo para estos casos de Java, también pueden aplicar para otros lenguajes y bases de datos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *