Mostrando entradas con la etiqueta hibernate. Mostrar todas las entradas
Mostrando entradas con la etiqueta hibernate. Mostrar todas las entradas

lunes, febrero 27, 2006

Hibernate Annotations

Introducción
Uno de los problemas mas frecuentes al trabajar con Hibernate, es la generación de los mapping files. (archivos .hbm.xml). Hay varias opciones para generarlos, desde escribirlos a mano hasta usar varios de los generadores automáticos. La manera mas usual es usar Xdoclet, algo que a mi particularmente nunca me gustó, ya que agrega un paso mas a la compilación, obligándome a usar Ant todo el tiempo, y no es algo que me parezca muy productivo. Además Xdoclet 1 no es del todo compatible con Hibernate 3 (la solución sería usar Xdoclet2, pero yo nunca lo probé). Hay otras opciones también, como algunos plugins para Eclipse, pero tampoco me terminaron de convencer.

Presentando Hibernate Annotations

Hibernate Annotations es un proyecto paralelo de Hibernate, a fin de proveer compatibilidad con la nueva especificación de EJB3.
Junto con otro proyecto, HibernateEntityManager , la gente de Hibernate planea tener un framework 100% compatible con EJB3, sin necesidad de un application server.
La especificación de EJB3 aún no esta completa, pero esta muy avanzada, y es de esperar que si hay algunos cambios, sean menores. Igualmente no es necesario usar EntityManager para aprovechar las anotaciones.
EJB3, aunque inspirado en Hibernate, no provee toda la funcionalidad de éste, por lo que Hibernate Annotations ofrece unas extensiones (en el paquete hibernate-annotations.jar) para lograr la misma funcionalidad que con los mappings files, pero es de suponer que esta compatibilidad sera alcanzada recién en la versión definitiva (actualmente esta disponible la 3.1beta8).
Además, Hibernate Annotations es compatible con los mappings, por lo que ambos pueden convivir en una misma aplicación, de forma que se puede ir migrando una aplicación de a poco (por supuesto cada clase individual debe estar mapeada de una u otra manera, no se pueden combinar los dos estilos en una misma clase).

Para los que no estén familiarizados con java annotations, les recomiendo que lean algún artículo en internet, como por ejemplo este.

Cuando se usa Hibernate Annotations, no significa que Hibernate va a crear los mapping files por sí mismo, y luego los va a leer y procesar. Hibernate toma las clases que tiene declaradas en el hibernate.cfg.xml, y lee las anotaciones directamente desde los archivos .class, generando así los mappings en memora en tiempo de ejecución.

Una gran ventaja de usar Hibernate Annotations es que todos los IDE modernos (Eclipse, IntelliJ, etc.), tienen soporte para anotaciones en el editor. Esto significa que es posible usar todas las funciones de los IDE (auto completar, refactoring, etc.) también con las anotaciones.

Por otra parte, todos los atributos de las anotaciones son tipos java (String, int, boolean, etc.), con lo que se reduce aún mas la posibilidad de errores.

Instalación
Es tan simple como agregar los jar (hibernate-annotations.jar y ejb3-persistence.jar) provistos en la distribución al classpath. También, donde sea que se defina el sessionFactory, hay que usar la clase AnnotationConfiguration en vez de Configuration (si se esta usando Spring, se debe usar AnnotationSessionFactoryBean en lugar de LocalSessionFactoryBean).
Por ultimo, en el archivo hibernate.cfg.xml hay que indicar que clases van a ser persistentes, de manera similar a cuando definíamos los mappings:
<mapping class="test.Cliente"/>

Hibernate Annotations requiere Hibernate 3.1.1 o superior.
Breve introducción a las Hibernate Annotations
Esto no pretende ser ni un tutorial ni una referencia completa a Hibernate Annotations, sino simplemente un pantallazo sobre su utilización. Para mas información y detalles, consulten la documentación en el sitio de Hibernate.
Otra de las grandes ventajas de usar annotations, es que podemos ir escribiendo los mapeos a medida que vamos escribiendo nuestra clase. Además, como la especificación provee defaults para casi todas las annotations, en principio hacer que una clase sea persistente es tan simple como agregar una sola anotación:
@Entity
public class Persona {
...
public String getNombre() {
...
}
}
En este caso, la clase mapearia a una tabla en la BD llamada cliente, con un campo de tipo texto llamado nombre.
Por supuesto nunca tendremos tanta suerte, y siempre será necesario agregar algo mas para que las clases mapeen correctamente a la tablas.

Por ejemplo, para que la tabla se llame “PERSONAS”, podemos agregar la siguiente anotación a la clase:
@Table(name=”PERSONAS”)
Todas la propiedades de la clase que tengan un getter van a ser persistentes por default (excepto las marcadas con static o transient), lo que equivaldría a usar la anotación @Basic;
@Basic
public Date getFechaDeNacimiento() {
...
}
Si queremos que algún campo no sea persistente, lo marcamos con @Transient:
@Transient
public int getEdad() {
...
}
Finalmente, si queremos cambiar alguna de las características de la columna en la BD, podemos usar @Column:

@Column(name=”documento”, unique=true, nullable=false, length=10)
public String getNroDocumento() {
...
}
Veamos ahora como mapear algunas asociaciones, empezando por la mas simple @OneToOne:
@Entity
public Class Persona {

@OneToOne(cascade=CascadeType.All)
public Direccion getDireccion() {
...
}
}

@Entity
public Class Direccion {
...
}
Noten como puede especificar el tipo de cascada que quiero. La columna que relacione las dos tablas en la BD se llamará por default direccion_id, en base a la concatenación del nombre de la relación mas underscore, mas el nombre de la primary key de la clase contenida. En caso de querer usar otro nombre de columna, se puede usar @JoinColumn.
Esta es una asociación unidireccional, si quisiera que fuera bidireccional, agrego en la clase Direccion:
@OneToOne(mappedBy=”direccion”)
public Persona getPersona() {
...
}
En vez de repetir el mapeo, simplemente lo especificamos con mappedBy, Hibernate busca el mapeo que se hizo en otro extremo de la asociación y sabe que es la asociación inversa.

Si una empresa tiene muchas personas, uso @ManyToOne:
@Entity
public Class Persona {

@ManyToOne()
@JoinColumn(name=”empresa_id”)
public Empresa getEmpresa() {
...
}
}

@Entity
public Class Empresa {
...
}
Para el caso de colecciones (por ejemplo si una persona tiene mas de una dirección), se usa @OneToMany/@ManyToOne o @ManyToMany:
@Entity
public Class Persona {

@OneToMany(mappedBy=”persona”)
public List<Direccion> getDirecciones() {
...
}
}

@Entity
public Class Direccion {

@ManyToOne
public Persona getPersona() {
...
}
}
Si usamos colecciones genéricas, Hibernate deduce cual es el otro extremo de la asociación y no es necesario indicarlo. En el caso que no usemos genéricas, o bien usemos interfaces, debemos indicar explícitamente hacia adonde apunta la asociación mediante el atributo targetEntity.


Desventajas de Hibernate Annotations

  • La especificación no es definitiva: esto implica que aun puede haber cambios a las anotaciones, y en caso de que eso suceda, habrá que modificarlas en nuestras clases. Igualmente, creo que a esta altura, los cambios serian mínimos y no costaría mucho realizarlos.
  • Requiere JVM 1.5: Las anotaciones es una característica del JDK 1.5. Esto no es un problema en un proyecto nuevo, pero si se quiere migrar código preexistente, puede que instalar JVM 1.5 en un entorno de producción sea un problema y no se pueda realizar fácilmente.
  • No todas las posibilidades de Hibernate están soportadas: Hay algunos mapeos que se pueden hacer en los mapping files que no se pueden hacer con anotaciones (por ejemplo ciertos mapeos con Maps, o OneToMany/ManyToOne bidireccional con una tabla intermedia), pero esto es probablemente una situacion temporaria hasta que haya una versióndefinitiva de Hibernate Annotations (la gente de Hibernate apunta a que sea 100% compatible con mapping files).

Conclusión
Hibernate Annotations es una excelente adición a Hibernate, y si no tienen problemas con las desventajas enunciadas anteriormente, en mi opinión es el camino a seguir.

Particularmente acabo de migrar una aplicación mediana (30 entidades) a Hibernate Annotations con un excelente resultado. Además, trabajar con anotaciones me resultó muy cómodo, mas fácil y mas intuitivo que con los mapping files o que con Xdoclet.

viernes, noviembre 05, 2004

Una aproximación básica a Hibernate

Las clases persistentes son aquellas que están relacionadas con tablas en una base de datos. Supongamos una versión simplificada de una clase Project:

public class Project {
  private Long id;
  private String name;
  private Company company;
    
  public Long getId() {
    return id;
  }
  public void setId() {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Company getCompany() {
    return company;
  }
  public void setCompany(Company company) {
    this.company = company;
  }
}


en ella tenemos tres atributos: el identificador, el nombre y una referencia a Company.
El identificador, en este caso un Long, podría haber sido cualquier objeto y nos permitirá acceder a la clave primaria en la base de datos. El resto de los atributos tiene sus respectivos accesores / mutadores como cualquier JavaBean y la clase tiene definida implícitamente el constructor por defecto (sin argumentos) necesario para que Hibernate pueda hacer Constructor.newInstance().
Estas características de nuestras clases persistentes, hacen que se no dependa específicamente de ninguna tecnología de persistencia, clases o interfaces tal y como sucede con EJB y los entity beans. Por lo que podemos instanciar y comprobar el funcionamiento de la clase afuera de cualquier contenedor.
La siguiente clase, Company, la definiremos así:

public class Company {
  private Long id;
  private String name;
    
  public Long getId() {
    return id;
  }
  public void setId() {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}


¿Como hacemos para que Hibernate persista estas clases? Definiendo (manualmente o con ayuda de herramientas) un fichero XML por clase donde especificaremos la información necesaria, y utilizando las interfaces Session y Transaction de Hibernate.
El fichero XML (XML mapping document) define de que manera las propiedades de la clase Project se corresponden con columnas de la tabla projects de la base de datos.
Para Project crearemos Project.hbm.xml en el classpath:
<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="com.example.model.Project" table="projects">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<many-to-one name="company" cascade="all" column="company_id"/>
</class>
</hibernate-mapping>
Y para Company crearemos Company.hbm.xml:
<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="com.example.model.Company" table="companies">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
Asumiendo que existe en nuestro classpath un fichero hibernate.properties con los parámetros de conexión a la base de datos (entre otros posibles), podremos crear un proyecto y persistirlo:

import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;

...

Configuration cfg = new Configuration();
cfg.addResource("Project.hbm.xml");
cfg.setPropertiesSystem.getProperties() );
SessionFactory sf = cfg.buildSessionFactory();

Session s = sf.openSession();
Transaction tx = s.beginTransaction();

Company company = new Company();
company.setName("Mi empresa");
Project project = new Project();
project.setName("MwM");
project.setCompany(company);

s.save(project);
tx.commit();
s.close();


con lo cual Hibernate generará las siguientes sentencias SQL:
insert into companies (id, name) values (1, "Mi empresa");

insert into projects (id, name, id_company) values (1, "MwM", 1);
Como se puede ver, simplemente guardando Project Hibernate guarda también, mediante cascade save, los objetos dependientes como Company.
Aunque haya sido una aproximación inicial muy básica al mundo Hibernate, esta punta del iceberg nos deja entrever muchas de las características que ofrece. Queda para otra ocasión, como hacer que Hibernate genere el esquema de base de datos y explorar en profundidad sus opciones de configuración y su idioma de query (HQL).

martes, julio 13, 2004

Datos internacionalizados en Hibernate

Como resolver mediante UserTypes de Hibernate un típico caso de i18n, donde tenemos en una base de datos las descripciones de ciertos elementos en diferentes idiomas:

Internationalized data in Hibernate

Este ejemplo nos sirve también para pensar mas en los UserTypes para resolver situaciones donde los mappings son insuficientes.