Observatorio de Grails 
  • Inicio
  • Actualidad
  • Artículos
  • Tutoriales
  • Eventos
  • Foro
  • Acerca de
Informes con JasperReports, iReport y el plugin Jasper Herencia con GORM
jun 02

Cuidado con los mapas y la caché de 2º nivel en las clases del dominio

publicado por Enrique Medina Montenegro

10 comentarios
1.093 visitas
(14 votos, media: 5,00)

Enrique Medina Montenegro

Twitter  |  LinkedIn

Con más de 14 años de experiencia en el mundo de las TI, donde comenzó desarrollando aplicaciones de escritorio en Delphi o Visual Basic, este Ingeniero en Informática por la Universidad de Alicante (1991-1996) ha ido perfilando su actividad profesional hacia las arquitecturas J2EE, donde siempre ha seguido muy atento, e incluso colaborado en ocasiones, con proyectos open-source como MyFaces, Spring, Hibernate, Groovy o Grails. Ocupando puestos desde Programador Junior hasta Arquitecto Senior de Soluciones, Enrique ha sido testigo de cómo ha ido evolucionando la tecnología en torno al desarrollo de aplicaciones web, adquiriendo un conocimiento y experiencia que le permiten evaluar con detalle las necesidades de cada proyecto y aplicar las herramientas que maximizan su productividad. Actualmente, Enrique se ha especializado en el framework de desarrollo Grails, y ejerce la Dirección Técnica de proyectos basados, entre otras, en esta tecnología.

En el día a día, nuestro equipo de desarrollo Grails se encuentra con algunas incidencias que inicialmente no tienen explicación, pero que una vez se analizan y depuran, se convierten en una lección aprendida para futuros proyectos. Y como nuestra filosofía es compartir el conocimiento con todos nuestros amigos del Observatorio, pues aquí os dejamos un consejo que creemos os puede ahorrar muchos quebraderos de cabeza y horas perdidas (Grails 1.2.1 con plugin Hibernate 1.2.1).

El caso es que en una de nuestras clases del dominio habíamos definido un mapa para poder almacenar valores por clave; un ejemplo típico es una clase que representa un usuario, y queremos almacenar su “nick” en función del programa de chat que utiliza:

class Usuario implements Serializable {

	String login
	String passwd
	String nombre
	String apellidos

	// Diferentes 'nicks' por tipo de chat.
	// ['MSN':'emedina_msn', 'GTalk':'e.medina.m', 'Yahoo':'emedinam', ...]
	Map nicks
}

Cuando definimos un campo de tipo Map en nuestra clase del dominio sin especificar ningún tipo de configuración adicional, Grails genera una tabla auxiliar para almacenar los distintos pares ‘clave:valor’ del mapa, como se observa al generar el esquema de BD:

create table usuario (id bigint generated by default as identity (start with 1), version bigint not null, nombre varchar(255) not null, passwd varchar(255) not null, apellidos varchar(255) not null, login varchar(255) not null, primary key (id));
create table usuario_nicks (nicks bigint, nicks_idx varchar(255), nicks_elt varchar(255) not null);

Además, por defecto, Grails siempre indica como tipo para las columnas que almacenarán la clave y el valor (_idx y _elt, respectivamente) un VARCHAR(255), que puede que no sea lo que estamos buscando. En tal caso, es posible indicar el tipo y el tamaño usando algo de configuración adicional en la clase anterior:

class Usuario implements Serializable {

	String login
	String passwd
	String nombre
	String apellidos

	// Diferentes 'nicks' por tipo de chat.
	// ['MSN':'emedina_msn', 'GTalk':'e.medina.m', 'Yahoo':'emedinam', ...]
	Map nicks

	// Redundante, pero para que se vea el efecto.
	static hasMany = [nicks: String]

	static mapping = {
		nicks indexColumn:[length:50], length:100
	}
}

Lo que siempre decimos en nuestros artículos: Grails hace por ti, pero si quieres cambiarlo, es posible y sencillo. Una vez tenemos nuestra clase ‘Usuario’ creada y configurada como nos interesa, vamos a explicar el asunto principal de este artículo, que es advertiros sobre el uso de la ‘caché’ para este tipo de campos.

Si queremos activar la caché de segundo nivel para optimizar el acceso a datos, lo primero que intentamos es cachear las instancias de ‘Usuario’. Para ello, únicamente debemos añadir lo siguiente a la sección ‘mapping’ de la clase:

class Usuario implements Serializable {

	String login
	String passwd
	String nombre
	String apellidos

	// Diferentes 'nicks' por tipo de chat.
	// ['MSN':'emedina_msn', 'GTalk':'e.medina.m', 'Yahoo':'emedinam', ...]
	Map nicks

	// Redundante, pero para que se vea el efecto.
	static hasMany = [nicks: String]

	static mapping = {
		nicks indexColumn:[length:50], length:100

		cache true
	}
}

Ahora, al pedir por primera vez un determinado usuario, se ejecutarán 2 consultas SQL contra la BD: una para obtener el usuario, y otra para obtener los distintos nicks de ese usuario, ya que dichos nicks se encuentran en una tabla separada de la de usuario en BD, como se ha explicado más arriba; sin embargo, en subsecuentes peticiones del mismo usuario, nos ahorraremos una de las consultas SQL, porque Hibernate nos devolverá la instancia cacheada a nivel de JVM del usuario (recordad que estamos hablando de caché de segundo nivel, es decir, a nivel de JVM), pero todavía seguirá ejecutando una consulta SQL para obtener los nicks. Vamos a intentar optimizarlo más aún:

class Usuario implements Serializable {

	String login
	String passwd
	String nombre
	String apellidos

	// Diferentes 'nicks' por tipo de chat.
	// ['MSN':'emedina_msn', 'GTalk':'e.medina.m', 'Yahoo':'emedinam', ...]
	Map nicks

	// Redundante, pero para que se vea el efecto.
	static hasMany = [nicks: String]

	static mapping = {
		nicks indexColumn:[length:50], length:100, fetch:'join', cache:true

		cache true
	}
}

¿Véis la diferencia? Hemos complementado la variable ‘nicks’ de la sección ‘mappings’ con el modo de ‘fetching’ (cómo debe traerse la asociación con ‘nicks’, si a través de varias ‘select’ adicionales o mediante un ‘outer join’ en una única ‘select’) e indicando que queremos que se cachee el mapa de valores ‘nicks’. Si ahora volvemos a realizar el experimento de pedir el usuario, Hibernate sólo generará una consulta SQL para obtener tanto el usuario como sus nicks, y para ello utilizará un ‘outer join’. Además, en teoría, una vez obtenidos los ‘nicks’ del usuario, los cacheará junto con el propio usuario. Pero, si ya estamos cacheando al propio usuario, y además le decimos que cachee al mapa ‘nicks’ indicando el modo ‘fetch’, se produce un comportamiento curioso que no es el esperado: Hibernate sólo cachea el primero de los ‘nicks’ disponibles, y no el resto, de forma que cuando más adelante pedimos un ‘nick’ distinto del que se ha cacheado, pues nos devuelve un ‘null’ y tenemos un problema.

Aunque parece algo enreversado, la explicación es que a Hibernate no le gusta que se le indique para un mapa el modo ‘fetch’ y que además se cachee. Sin embargo, si quitamos el modo ‘fetch’:

class Usuario implements Serializable {

	String login
	String passwd
	String nombre
	String apellidos

	// Diferentes 'nicks' por tipo de chat.
	// ['MSN':'emedina_msn', 'GTalk':'e.medina.m', 'Yahoo':'emedinam', ...]
	Map nicks

	// Redundante, pero para que se vea el efecto.
	static hasMany = [nicks: String]

	static mapping = {
		nicks indexColumn:[length:50], length:100, cache:true

		cache true
	}
}

Ahora todo funciona a la perfección: en la primera petición de un usuario determinado, se ejecutan 2 consultas SQL, pero a partir de ese momento, y mientras el usuario esté en nuestra caché, no se volverá a molestar a la BD para nada, con la consecuente mejora en rendimiento asociada. Interesante, ¿verdad?

Y esto es todo amigos. En futuros artículos seguiremos profundizando en las tripas de GORM para poder sacarle todo el jugo que podamos. Como dicen mis amigos americanos, ¡stay tuned!

Y no os olvidéis de dejarnos vuestros comentarios si alguno de nuestros artículos os ha sido de utilidad, o si encontráis algo incorrecto o que no podéis hacer funcionar desde vuestro lado. Estaremos encantados de echaros una mano.

Del.icio.us Facebook LinkedIn Twitter

  1. Kinisoftware
    02/06/2010 a las 16:56

    Buenas,

    muy explicativo el artículo! Trabajo con Hibernate cada día (no unido con Grails como sería mi gusto) y optimizar las consultas, accesos, rendimiento es algo que me interesa mucho. Hace poco tuvimos un problema de redundancia de objetos por la caché de primer nivel y son de esas cosas que, pasan a veces y que, cuando te pones a investigar, aprendes qué y porqué. Intentaré sacar hueco para mirar estos temas y algunas ideas me han surgido a partir de este artículo. Gracias!

    Genial también Observatorio de Grails :) Felicidades!

    Un saludo :)

  2. Enrique Medina Montenegro
    02/06/2010 a las 16:57

    Encantados de poder ayudar. Y encantados de ver que nuestros artículos llegan a la gente y les son de utilidad. :-)

  3. josem
    02/06/2010 a las 17:25

    Buenas, genial artículo, como siempre!

    El tema de la chacé de segundo nivel es muy interseante, estoy en desarrollo de un proyecto y todavía no lo he utilizado, primero quiero hacer funcionar basicamente toda la aplicación para después pasar a optimizar estas pequeñas cosas, así que, a mis favoritos va el artículo.

    Quería haceros una pregunta, en vuestros desarrollos, cuando hay que clasificar ciertos datos, por eemplo un TipoActividad que se relacionaria con una Actividad, como lo soleis hacer? con un inList:['Actividad de benivenida','actividad x'].
    O directamente, creais la clase TipoActividad aunque sea solo con un String para el nombre y hacer la relación con Actividad por id?
    De esta seguna manera a los usuarios les dá mas libertad, pues sin tener al informático delante pueden añadir categorías etc.
    Pero el problema que estoy teniendo es que el número de clases de dominio aumenta demasiado en mi opinión.
    Como lo solucionais vosotros?
    Creando un tipo List?
    Gracias

  4. Enrique Medina Montenegro
    02/06/2010 a las 17:29

    josem,

    En dichos casos siempre solemos optar por un tipo enumerado, que además Hibernate mapea de forma transparente para Grails, por lo que ni nos damos cuenta de que lo estamos usando :-)

    enum class TipoActividad {

    BIENVENIDA, OTRA

    }

  5. josem
    02/06/2010 a las 17:58

    habría que crear una clase no? no la puedo poner en otra clase de dominio, verdad?

  6. Enrique Medina Montenegro
    02/06/2010 a las 18:00

    Puedes hacerlo como quieras, pero normalmente nosotros creamos un fichero groovy exclusivo para el tipo enumerado… Pero supongo que es cuestión de gustos :-)

  7. josem
    02/06/2010 a las 18:25

    TipoEvento.groovy
    public enum TipoEvento {
    BIENVENIDA, CHARLA, DESPEDIDA
    }
    Evento.groovy
    class Evento {

    String nombre
    TipoEvento tipoEvento

    static constraints = {
    }
    }
    ..
    def e = new Evento(nombre:’Presentación Grails’,tipoEvento:TipoEvento.BIENVENIDA).save()

    así es como me decías, mas o menos? :)

  8. Enrique Medina Montenegro
    02/06/2010 a las 18:28

    ¡Exacto!

  9. josem
    02/06/2010 a las 19:01

    Qué agustito, llevarme esas clases de enum a otra parte, por ejemplo a sources groovy/enums y no tener el domain classess petado :D

  10. Enrique Medina Montenegro
    02/06/2010 a las 19:13

    josem,

    Lleva cuidado con el uso de palabras reservadas como nombres de paquetes… ;-)

    (no estoy 100% seguro de si ‘enums’ lo es, pero pon ‘enumerados’ y te ahorras disgustos)

Escribir un comentario

Clic para cancelar respuesta.

Actualidad  Entradas relacionadas  si te ha interesado la entrada te proponemos otras lecturas relacionadas

  • oct13

    Referenciar clases del dominio desde un script Gant (inglés)

    publicado por Enrique Medina Montenegro en Grails

    0 comentarios
    262 visitas
    (1 votos, media: 3,00)
  • oct13

    Clases del dominio GORM dinámicas (inglés)

    publicado por Enrique Medina Montenegro en Grails

    0 comentarios
    320 visitas
    (1 votos, media: 5,00)
  • mar31

    Migración de 1.1.1 a 1.2.x con clases de dominio anotadas (inglés)

    publicado por Enrique Medina Montenegro en Build

    0 comentarios
    309 visitas
    (1 votos, media: 3,00)

Comentarios recientes  Comentarios recientes

  • pedro luis hola como cargo imágenes de un archivo para mostrarlo en un reporte con los demás datos; en Reportes en Grails con iReport
    17/05/2012 a las 21:03

  • yo 123; en Clusterizar una aplicación Grails con EhCache (inglés)
    24/04/2012 a las 17:33

  • yo muy buebo; en Clusterizar una aplicación Grails con EhCache (inglés)
    24/04/2012 a las 17:21

Twitter  

preload preload preload

Síguenos en Twitter  |  Facebook
Inicio  |  Actualidad  |  Artículos  |  Tutoriales  |  Eventos  |  Foro  |  Acerca de

© 2010 Observatorio de Grails. Todos los derechos reservados.