miércoles, 9 de junio de 2010

ManyToMany en Hibernate con anotaciones

Muy buenas a todos, ya se que me echabais de menos, así que he decicido volver a escribir por aquí. En esta ocasión os voy a contar como representar con Hiberante una tabla Many To Many, pues ha sido una de las cosas que más me han dado la lata en el nuevo curro.
Para entrar en situación voy a exponer un pequeño ejemplo con el que todos los labos y ex-labos estaremos familiarizados:
Supongamos que por una extraña razón a Geno se le juntan muchos marrones encima (cosa que no pasa casi nunca ;)) y por otro lado están (estabamos) todos los precarios, entonces le encarga a Rafa que los reparta de una forma sencilla. Rafa como está apuntito de casarse piensa que lo mejor es hacer una aplicación sencilla para que Geno la pueda utilizar y le deje escribir su tesis, que aunque él cree que la lleva bien aún le queda por escribir unas 100 páginas.
La aplicación será llamada Brown Dispatching y su modelo de datos generaría unas 3 tablas:
  • Una tabla para cada miembro del labo, donde podríamos incluir el nombre del precario, el nombre de la máquina, las horas que está o cualquier otra información que pudiese resultar útil para esta situación. En el caso del ejemplo, creo que con el nombre bastaría, además que somos pocos y nos conocemos perfectamente.
  • Otra tabla para cada marrón, indicando el nombre, una descripción y una fecha límite por poner unos campos de ejemplo.
  • La tercera tabla sería la que finalmente enlazaría a los marrones con los precarios. Por lo que tendría unicamente el identificador único de marrón y el identificador único de usuario.
Gracias a Hibernate, esta tercera tabla no habría que implementarla, pero si que deberíamos implementar las otras 2 entidades. A partir de la versión 1.5 existe una opción para meter anotaciones en el propio código del Bean y éste ha sido el método que he elegido yo para realizar el ejemplo.
imports ...
@Entity
@Table(name = "T_PRECARIOS")
public class Precario implements IGwtSerializableParameter {

 private Long id;
 private String name;
 private Set browns;

 // ID SETTER AND GETTER
 @Id
 @GeneratedValue(strategy=GenerationType.AUTO)
 @Column(name="ID")
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }

 // NAME SETTER AND GETTER
 @Column(name = "NAME", nullable = false)
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }

 // BROWNS SETTER AND GETTER
 @ManyToMany(
   cascade = { CascadeType.MERGE,
      CascadeType.PERSIST },
   fetch = FetchType.EAGER
   )
 @JoinTable(
   name = "T_PRECARIOS_BROWNS",
   joinColumns = { @JoinColumn(name = "PRECARIO_ID") },
   inverseJoinColumns = { @JoinColumn(name = "BROWN_ID") }
   )
 public Set getBrowns() {
  return browns;
 }
 public void setBrowns(Set browns) {
  this.browns = browns;
 }
 ...
}
import ...
@Entity
@Table(name = "T_BROWNS")
public class Brown implements IGwtSerializableParameter {

 private Long id; // Unique identifier for brown
 private String name; // Brown's name
 private String description; // Brown's description
 private Date finalDate; // Brown's final date
 private Set precarios; // Set with the "precarios" associated

 // SETTER AND GETTER FOR ID
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @Column(name = "ID")
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }

 // SETTER AND GETTER FOR NAME
 @Column(name = "NAME", nullable = false)
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }

 // SETTER AND GETTER FOR DESCRIPTION
 @Column(name = "DESCRIPTION", nullable = false)
 public String getDescription() {
  return description;
 }
 public void setDescription(String description) {
  this.description = description;
 }

 // SETTER AND GETTER FOR FINAL DATE
 @Column(name = "FINAL_DATE", nullable = false)
 @Temporal(TemporalType.DATE)
 public Date getFinalDate() {
  return finalDate;
 }
 public void setFinalDate(Date finalDate) {
  this.finalDate = finalDate;
 }

 // SETTER AND GETTER FOR "PRECARIOS"
 @ManyToMany(
   mappedBy = "browns",
   cascade = { CascadeType.MERGE,
      CascadeType.PERSIST },
   fetch = FetchType.EAGER
   )
 public Set getPrecarios() {
  return precarios;
 }
 public void setPrecarios(Set precarios) {
  this.precarios = precarios;
 }
 ...
}
En modo de anotaciones de Hibernate, las notaciones se pueden poner en la definición del atributo o en el Getter del mismo, como se puede ver yo he elegido ponerlas en el getter.
Las anotaciones más importantes para el ejemplo que he traído son las asociadas a los Set (de java.util). En estas se representa que tipo de unión se quiere entre las entidades.
En la entidad Precario, se define por un lado el tipo de unión que se va hacer entre las tablas, con los atributos cascade y fetch en la anotación ManyToMany, mientras que en la anotación JoinTable se indica el nombre de la tabla resultante de hacer el join de las entidades y el nombre de las columnas. En la entidad Brown, se define simplemente el ManyToMany indicando que atributo de la otra clase modela está relación, y se indica el tipo de unión que se realiza.
Creo que esto es suficiente clase sobre Hibernate de momento, pero si alguien tiene alguna duda/sugerencia al respecto soy todo oídos. Por cierto, puede que haya alguna manera más eficiente para modelar esto, pero no se me ha ocurrido un ejemplo que modele mejor las necesidades de crear un ManyToMany, y también puede ser que haya forma mejor de realizar el ManyToMany dado que apenas llevo unos meses con Hibernate y no soy ni mucho menos un experto. Aqui solo he tratado de plasmar mi experiencia con esto. Un saludo!

miércoles, 2 de junio de 2010

Abajo esas etiquetas

Yo creo que a todos nosotros, a modo de introducción, nos dijeron que el HTML/XML es un formato de texto plano fácilmente legible y editable por humanos en el que se puede formatear cualquier documento. Nos mintieron. Si editar HTML cumpliese esas promesas no utilizaríamos procesadores de texto ni se editaría la Wikipedia en wikicode, ni las entradas de los foros en BBCode.

Cuando preparamos un fichero README al distribuir software, la tradición manda que esté escrito en texto plano y a no más de 80 columnas para que pueda ser utilizado en el mayor rango posible de situaciones (adicionalmente se pueden incluir versiones en HTML o PDF). Hoy me gustaría recomendaros el formato markdown para este tipo de tareas porque tiene una sintaxis simple, existen herramientas para traducir los documentos a otros formatos y por encima de todo, es un formato directamente legible.

Existen muchos ejemplos en github, una web que se merece un apunte propio, puesto que soporta markdown y permite visualizar en texto plano o el resultado de procesarlo.

Como ventaja adicional, el hecho de adoptar markdown nos facilitará el ser consistentes con la notación de los documentos.

Algunas herramientas que podemos usar son el propio paquete markdown (debian | script en perl), pandoc (diversas conversiones), htmldoc (para pasar a pdf un html) y muchas otras utilidades. A la hora de editar markdown desde vim, podemos utilizar un poco de magia de vim.org: http://www.vim.org/scripts/script.php?script_id=2882

viernes, 21 de mayo de 2010

Git en tu prompt

Ultimamente, GIT está causando furor entre los miembros del labo. Aunque el cambio desde Subversion puede ser algo traumático si no se digieren bien los nuevos conceptos, el cambio de paradigma hacia el control de versiones distribuidos merece mucho la pena. Una buena introducción es el libro online Pro Git (recomendación vía Álvaro).

Hoy quiero compartir un snippet que permite personalizar el PROMTP de bash para que muestre en que rama de un repositorio GIT estamos trabajando y un asterisco si tenemos cambios pendientes. Para usarlo, basta con que lo copiéis a vuestro home y lo llaméis desde el .bashrc.

Como característica extra, el prompt cambia de color si abrís una shell de root para que no se os olvide cerrarla.


Actualización: si utilizáis el git desde los ports de mac, echadle un vistazo a este hilo para habilitar el comando __git_ps1

martes, 11 de mayo de 2010

Remontadas

Los sistemas de ficheros compartidos por red son muy útiles a mi me simplifican mucho la tarea de almacenar copias de seguridad en una máquina diferente. Basta con tener montada la partición y usarla como si fuese local.

Sin embargo, cuando hay problemas de red o cortes de luz puede ser necesario montar o remontar esas particiones y se puede tardar bastante tiempo en detectar si no se tiene cuidado. Una forma de atacar el problema es tener a mano un script en el cron. A continuación pego el que acabamos de poner en funcionamiento en el labo:

Podéis probarlo con la opción --verbose para ver qué hace paso a paso y que puede resumirse en comprobar si el montaje esta ido o desmontado y actuar en consecuencia. No olvidéis sustituir los puntos de montaje al principio del script!

lunes, 26 de abril de 2010

Limpiar ficheros en Python

¿No os molestan los ficheros, en general de código fuente, que están llenos de espacios al final de las líneas (los llamados trailing spaces) y otras degeneraciones varias: tabuladores mezclados con espacios normales, fines de línea estilo MS-DOS (retornos de carro) mezclados con otros UNIX, etc.? A nosotros sí.

Este es un problema que surge cuando un equipo trabajan sobre los mismos ficheros de texto, cada uno con su editor, en su sistema operativo... y entre Álvaro, Sebas y yo nos hemos propuesto solucionarlo.

Para ello, hemos creado un script en Python que toma como argumentos una lista indeterminada de ficheros y los "limpia". En concreto:

  • Elimina espacios y tabuladores al final de todas las líneas.
  • Elimina los retornos de carro estilo MS-DOS, para homogeneizarlos y que se vean "bonitos" en todos los entornos.
  • Opcionalmente, por medio del paso de un parámetro extra, se sustituyen los tabuladores de principio de línea por 4 espacios.
El uso es bien sencillo: 
python sanitize_nl.py [--retab] <fichero1> [<fichero2> ...]
...y listo. Ficheros limpitos y sin problemas.

Podeis descargarlo de aqui

jueves, 8 de abril de 2010

JSON con comentarios

JSON es un formato que permite representar estructuras de datos de una manera muy sencilla y compacta para su uso en aplicaciones web. El formato es deliberadamente simple y austero para evitar problemas, por ejemplo, sólo se utilizan comillas dobles y no se permiten comentarios.

Pero, ¿qué sucede si queremos utilizar JSON para representar configuraciones? Los comentarios son importantísimos y los parsers existentes no lo contemplan. Podríamos meternos en un infierno de expresiones regulares con los problemas que eso puede traer (las regex se quedan cortas para este tipo de trabajos) pero es mejor disponer de un parser adaptado a esta tarea.

Partiendo de la última versión del parser escrito en JavaScript de JSON.org hemos añadido los comentarios de línea (//) y bloque (/* */). Esta opción es más lenta que los parsers integrados en el navegador pero, a fin de cuentas, los ficheros de configuración no deberían ser enormes con lo que es un buen compromiso.

El resultado lo podéis descargar aquí: commented json parser. Para usarlo basta con incluir la biblioteca e invocarlo así:

var object = cjson_parse('{ "hola": /* adios */ "3", \n\
 "cumpleaños": "2009-12-12" // fecha temporal\n\
}');

miércoles, 7 de abril de 2010

El arte de programar

Leído en Watch What I Do: Programming by Demonstration


This urge to create something living is common among artists. Michelangelo is said to have struck with his mallet the knee of perhaps the most beautiful statue ever made, the Pieta, when it would not speak to him. And then there's the story of Frankenstein. Artists have consistently reported an exhilaration during the act of creation, followed by depression when the work is completed. "For it is then that the painter realizes that it is only a picture he is painting. Until then he had almost dared to hope that the picture might spring to life." (Lucien Freud, in [Gombrich 60], p.94) This is also the lure of programming, except that unlike other forms of art, computer programs do "come to life" in a sense.