Anotaciones en los bean de entidad

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:

  • Implementar la interfaz java.io.Serializable.
  • Tener una serie de constructores:
    • Constructor vacío.
    • Constructor donde se indican los atributos no nulos.
    • Constructor donde se indican todos los atributos.
  • Todas las propiedades han de tener su método get y set.

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: Identifica la clase como una entidad que mapea una tabla. Dicha clase debe tener al menos un constructor público y un id.
  • @Table: Nos permite especificar la tabla que mapea esta clase. En caso de no existir esta anotación el motor de persistencia intentará mapear la clase con una tabla que coincida, en nombre, con el de la clase. Las propiedades más reseñables de esta anotación son las siguiente:
    • name: Nos permite especificar el nombre de la tabla de base de datos que se pretende mapear.
    • schema: Nos eprmite especificar el nombre del esquema que contiene la tabla que pretendemos mapear.
    @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.

Anotaciones básicas

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: Nos indica que esta variable representa la clave primaria.
        @Id
	@Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)	
	public BigDecimal getId() {
		return this.id;
	}
  • @Column: Nos indica que esta variable representa una columna. Si no lleva parámetros el motor de persistencia deduce que la columna se llama como la variable. Las propiedades destacadas de esta anotación son:
    • name: Indica el nombre de la columna que se mapea.
    • unique: Indica si el valor de los datos que representa esta variable ha de ser único en la columna.
    • nullable: Indica si la columna puede ser nula.
    • lenght: Indica el tamaño máximo del contenido de la variable. Obligatorio en variables de tipo String.
        @Column(name = "NOMBRE", unique = true, nullable = false, length = 50)	
	public String getNombre() {
		return this.nombre;
	}
  • @Temporal: Imprescindible para propiedades de tipo fecha, esta anotación nos permite indicar si es una fecha o un Timestamp.
        @Temporal(TemporalType.DATE)
	@Column(name = "FECHA_PUBLICACION", nullable = false, length = 7)	
	public Date getFechaPublicacion() {
		return this.fechaPublicacion;
	}
  • @Transient: Esta anotación nos es útil si queremos introducir en nuestra clase una variable que no queremos que se mapee a base de datos. Bien porque es un valor calculado, bien porque la usamos para alguna condición. Esta anotación, por tanto, hace que se ignore esta variable en los procesos de persitencia o carga desde base de datos.

Anotaciones para LOB

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;
	}

Mapear booleano

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:

  • @OneToMany
  • @ManyToOne
  • @ManyToMany

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:

  • CascadeType.PERSIST: Cuando persistamos la entidad todas las entidades que contenga esta variable serán persistidas también.
  • CascadeType.REMOVE: Cuando borremos la entidad todas las entidades que contenga esta variable se borrarán del mismo modo.
  • CascadeType.REFRESH: Cuando actualicemos la entidad todas las entidades que contenga esta variable se actualizarán. (Con esta opción hay que tener cuidado, ya que si se utiliza la paginación fragmentada de la guia tecnica, puede provocar que se ejecuten muchas consultas innecesarias)
  • CascadeType.MERGE: Cuando hagamos un “merge” de la entidad todas las entidades que contenga esta variable realizarán la misma operación.
  • CascadeType.ALL: Todas las operaciones citadas anteriormente.

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: Indica que los objetos de la clase Comentario pertenecen a la clase Anuncio.
  • @JoinColumn: Indica la columna de base de datos que mapea dicha relación
        @ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "ANUNCIO", nullable = false)
	@NotNull
	public Anuncio getAnuncio() {
		return this.anuncio;
	}
  • fdw2.0/fundeweb2.0/gt/anotaciones_entidades.txt
  • Última modificación: 19/02/2019 12:30
  • por JUAN FELIPE MOLINA HERNANDEZ