— PEDRO DELGADO YARZA 2014/01/29 13:50
A la hora de definir una entidad que mapee una tabla deberemos introducir una serie de anotaciones mínimas para que el motor de persistencia utilizado sea capaz de transformar los datos contenidos en esa clase Java en un Insert, Delete o Update en base de datos.
Antes de introducir las anotaciones debemos asegurar que la clase cumple con los requisitos necesarios para que el motor de persistencia pueda utilizarla, para ello dicha clase tiene que:
Una vez que nuestra clase Java cumple los requisitos para poder mapear una Tabla de base de datos podemos comenzar a insertar las anotaciones que le indicarán al motor de persitencia cómo debe mapear la clase.
Supongamos, a modo de ejemplo, que queremos crear una entidad Anuncio que incluye un nombre, una descripción, la fecha de publicación y la posibilidad de agregar comentarios. Esa clase que representará una Tabla, debe ser una clase de persistencia básica conocida comp POJO donde no debe haber código que no sea el relacionado con la persistencia.
Las anotaciones necesarias para transformar esa clase Java en un Bean que mapee una tabla son las siguientes:
@Entity @Table(name = "ANUNCIO", uniqueConstraints = @UniqueConstraint(columnNames = "NOMBRE"), schema="FUNDAWEB") public class Anuncio implements java.io.Serializable { ........ ........ }
En versiones anteriores de Fundeweb (1.2.5 o anterior) usábamos anotaciones de Hibernate como por ejemplo @NotNull para añadir restricciones de validación a nuestros beans.
En la versión 2.0 de Fundeweb, con la nueva API de JPA trabaja conjuntamente con la API de BV (Bean Validation, estándar surgido de Hibernate Validator). Por esa razón, las anotaciones de Bean Validation especificadas en el bean de entidad se ejecutaran en la fase de validación de JPA. La recomendación que hacemos es utilizar las anotación de BV en lugar de las propiedades de la anotación de JPA @Column, de esta manera, tendremos validación tanto en JPA como cuando se utilice el bean de entidad en una página JSF.
En proyectos iniciados con Fundeweb 2.0 no existe ningún problema de adaptación. Pero en proyectos iniciados con Fundeweb 1.2.x podrán surgir problemas de compilación debido a que se importen librerías de Hibernate que ya no se contemplan en la nueva especificación. En ese caso deberemos cambiar la importación para utilizar el paquete javax.validation BV en vez de Hibernate.
Una vez definida la tabla que se mapeará, debemos especificar cómo mapeamos las columnas que contiene. Para ello disponemos de un conjunto de anotaciones que pondremos, preferentemente en el método get de cada variable que represente una columna.
@Id @Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0) public BigDecimal getId() { return this.id; }
@Column(name = "NOMBRE", unique = true, nullable = false, length = 50) public String getNombre() { return this.nombre; }
@Temporal(TemporalType.DATE) @Column(name = "FECHA_PUBLICACION", nullable = false, length = 7) public Date getFechaPublicacion() { return this.fechaPublicacion; }
Para mapear los tipos de datos LOB (BLOB Y CLOB) utilizamos la anotación @Lob. Para mapear un CLOB utilizaremos el tipo String de Java. Para mapear un BLOB utilizaremos el tipo byte[] (array de bytes) de Java. Junto con estas anotaciones, podemos utilizar la anotación @Basic para indicar que tenga un carga Lazy (perezosa), es decir, que los valores solo se cargan cuando se accede a la propiedad.
private byte[] datosBlob; private String datosClob; @Lob @Column(name = "DATOS_BLOB") @Basic(fetch=FetchType.LAZY) public byte[] getDatosBlob() { return this.datosBlob; } public void setDatosBlob(byte[] datosBlob) { this.datosBlob = datosBlob; } @Lob @Column(name = "DATOS_CLOB") public String getDatosClob() { return this.datosClob; } public void setDatosClob(String datosClob) { this.datosClob = datosClob; }
Cuando tenemos en base de datos un valor de tipo NUMBER(1 BYTE), se puede mapear directamente a un campo Booleano del Bean de Entidad.
private Boolean esAlumnoStr; @Column(name = "ES_ALUMNO_STR", precision = 1, scale = 0) public Boolean getEsAlumnoStr() { return this.esAlumnoStr; } public void setEsAlumnoStr(Boolean esAlumnoStr) { this.esAlumnoStr= esAlumnoStr; }
En caso de que en Base de Datos el tipo no sea un NUMBER(1), habría que hacer una transformación a través de un nuevo campo anotado con @Transient de manera manual.
@Column(name = "ES_ALUMNO", length = 1) @Length(max = 1) public String getEsAlumno() { return this.esAlumno; } public void setEsAlumno(String esAlumno) { this.esAlumno = esAlumno; } @Transient public boolean isAlumnoBoolean() { if (esAlumno == null) esAlumno = "N"; return esAlumno.compareTo("S") == 0; } public void setAlumnoBoolean(boolean esAlumnoBoolean) { if (esAlumnoBoolean) { this.esAlumno = "S"; } else this.esAlumno = "N"; }
Otra forma más manejable es usando una anotación que se encarga de hacer la conversión de String de tipo S/N a boolean. Pare ello solo hay que hacer eso:
private boolean esAlumno; @Column( name = "ES_ALUMNO" ) @Type(type = "es.um.atica.hibernate.type.SNBooleanType") public boolean isEsAlumno() { return this.esAlumno; } public void setEsAlumno(boolean esAlumno) { this.esAlumno = esAlumno; }
Cuando una entidad tiene una relación con otra tabla debemos indicar en ambas clases la relación existente para que el motor de persistencia pueda interpretar los valores a la hora de recuperar y persistir. Las anotaciones que podemos incluir a la hora de mapear son:
En la entidad Anuncio incluiremos un atributo comentarios de tipo List para los comentarios simulando una relación OneToMany-ManyToOne, es decir un maestro-detalle.
En el correspondiente método get incluimos la anotación @OneToMany, la cual tiene una propiedad llamada cascade, que define, con qué tipo de operaciones se realizarán operaciones en “cascada, dicha propiedad puede tener los siguientes valores:
La propiedad fetch determina la forma en que se cargan los datos: FetchType.LAZY (carga de la entidad únicamente cuando se utiliza), FetchType.EAGER (carga de todas las entidades relacionadas con ella).
La propiedad mappedBy = “anuncio” indica el nombre de la entidad Anuncio en el objeto Comentario.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "anuncio") public List<Comentario> getComentarios() { return this.comentarios; }
En la entidad Comentario, para completar la relación tendremos que indicar, en la propiedad que hace referencia a la clase padre la relación existente haciendo uso de las siguientes anotaciones:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ANUNCIO", nullable = false) @NotNull public Anuncio getAnuncio() { return this.anuncio; }