Internacionalizacion de base de datos con Java e Hibernate Criteria

By | 11 enero 2012

Hace tiempo os hable en esta entrada de como internacionalizar una aplicación web con JavaEE basada en Spring, en dicha entrada tratábamos la necesidad de obtener el lenguaje utilizado por el cliente para mostrar nuestra aplicación en un idioma u otro.

Una vez solucionada esta necesidad, teniendo claro cual es el lenguaje del cliente, Spring nos provee todo lo necesario para internacionalizar textos fijos e imágenes, pero nos quedaba en el aire la necesidad de implementar un buen sistema de internacionalización para la base de datos.

He de reconocer que he estado leyendo mucho sobre este tema para tratar de buscar una solución fácil, ágil y eficaz en el desarrollo de aplicaciones internacionalizadas sobre JavaEE, pero no me convencen, ni tablas intermedias, ni campos de lenguaje, etc… Por su simplicidad tanto a la hora del desarrollo como del mantenimiento y ampliación siempre me ha gustado el sistema que utilizaba para internacionalizar en PHP, tablas prefijadas.

Si tengo dos lenguajes, castellano e inglés por ejemplo, y un sistema de noticias, tendré dos tablas, es_noticias y en_noticias, así de fácil, y si quiero en un momento determinado ampliar al francés, añadiré fr_noticias y solucionado, sin cambios en tablas, ni nada mas.

Pero este sistema en Java con Hibernate Criteria no resulta tan trivial, ya que la necesidad del mapeo de clases nos obliga a enredarnos en ifs, switchs y demás códigos redundantes y poco flexibles que ensucian demasiado el código y resultan poco mantenibles.

La solución requiere de la API Reflection de Java para instanciar clases que implementen la misma interface, y así poder establecer (en este caso mediante una anotación de Hibernate) el mapeo tabla-clase del respectivo idioma. ¡¡Manos a la obra!!

Lo primero es crear nuestras tablas en la base de datos, su estructura será así.

CREATE TABLE `xx_noticias` (
  `id_xx_noticias` int(11) NOT NULL AUTO_INCREMENT,
  `noticia` text COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id_xx_noticias`)
)

Lo siguiente sera crear nuestras clases para el mapeo sobre objetos de Hibernate Criteria, con una particularidad, deberán implementar una misma interface, de esta forma nos aseguramos de que los objetos son iguales y se implementan de la misma manera para asegurar que Hibernate Criteria pueda utilizarlos sin problema. Primero creamos nuestra interface.

public interface Noticias {

    Integer getIdNoticia();

    void setIdNoticia(Integer idNoticia);

    String getNoticia();

    void setNoticia(String noticia);

}

Ahora nuestras clases para el mapeo a objetos de la base de datos. Crearemos una tabla por cada lenguaje, su estructura será la siguiente.

@Entity
@Table(name="xx_noticias")
public class xx_noticias implements Noticias, Serializable{

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="id_xx_noticias")
    private Integer idNoticia;
    private String noticia;

    @Override
    public Integer getIdNoticia() {
        return idNoticia;
    }

    @Override
    public void setIdNoticia(Integer idNoticia) {
        this.idNoticia = idNoticia;
    }

    @Override
    public String getNoticia() {
        return noticia;
    }

    @Override
    public void setNoticia(String noticia) {
        this.noticia = noticia;
    }

}

Ahora creamos una clase abstracta para inyectar nuestro objeto hibernateTemplate perfectamente inicializado y almacenado en el contexto de nuestra aplicación.

public abstract class BaseDAO {

    private HibernateTemplate hibernateTemplate;

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public HibernateTemplate getHibernateTemplate() {
        return this.hibernateTemplate;
    }
}

Después la implementamos, ya con los métodos finales para interactuar con la base de datos.

public class DataSource extends BaseDAO {

     // RECOGER NOTICIAS
    public List recogerListaNoticias(String lang) {

        List noticias = null;

        String classname = "class.package.dao." + lang + "_noticias";
        Noticias nts = null;
        try {
            nts = (Noticias) Class.forName(classname).newInstance();

            DetachedCriteria crit = DetachedCriteria.forClass(nts.getClass());

            noticias = this.getHibernateTemplate().findByCriteria(crit);
        } catch (Exception ex) {
            // Manejo de excepciones.
        }

        return noticias;
    }

}

Como veis hemos creado un nuevo método para obtener un listado de todas las noticias. Que recibe un parámetro para especificar el lenguaje de la aplicación. Después mediante Java Reflection instanciamos dinámicamente la clase del lenguaje especificando mediante su prefijo haciendo uso de la interface.

Comparte esta entrada enShare on LinkedIn0Tweet about this on Twitter0Share on Facebook0Share on Google+0

2 thoughts on “Internacionalizacion de base de datos con Java e Hibernate Criteria

  1. Son Picard

    La verdad es que no lo veo muy elegante. ¿Qué ocurre si tengo una entidad de bdd con descripción diferente según el idioma?

  2. admin Post author

    Y tienes razón, no es elegante, es un recurso para situaciones parecidas a la que me he encontrado en el proyecto que estoy desarrollando ahora.

    El escenario para el que te sirve este procedimiento, es una base de datos que debe ser internacionalizada, y que cada una de sus entidades implemente un idioma distinto, la opción que se nos ocurre a todos de primeras es implementar un campo idioma discriminatorio, pero esto aumenta multiplicativamente el tamaño de una tabla en la base de datos, y si dicha tabla tiene un probabilidad elevada de ir aumentando con el tiempo, lo suyo es idear una forma diferente en el que cada tabla implemente un idioma, también esta la opción de la tabla intermedia, pero estamos en lo mismo.

    Si con descripción te refieres a la estructura de la entidad, evidentemente no te sirve, ya que para eso implementan Interface.

    De todas maneras, es solo una idea mas, y todas aportaciones son bienvenidas, mas en temas que brindan tantas posibilidades y diferentes formas de implementación como este.

Deja un comentario

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