Hibernate – Como definir la forma de persistir nuestros objetos mediante la interfaz CompositeUserType.

0
8063

Hibernate – Como definir la forma de persistir nuestros objetos mediante la interfaz CompositeUserType.

0. Índice de contenidos.

1. Introducción

Trabajando con Hibernate podemos vernos en la necesidad de persistir un objeto de una forma específica,
para ello el framework nos ofrece una serie de interfaces que nos facilitan la tarea.

Tenemos dos opciones:

  • La interfaz «org.hibernate.usertype.UserType»
    La clase que implementamos extendiendo dicha interfaz es la clase que sabe como serializar instancias de otra clase desde/hacia JDBC.
    Solamente es capaz de definir tipos simples.
  • La interfaz «org.hibernate.usertype.CompositeUserType»
    Esta interfaz es una versión ampliada de la anterior, permitiendo definir «propiedades».
    El uso de CompositeUserType esta asociado a la necesidad de tener más de una columna de base de datos para representar un objeto.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

3. CompositeUserType.

Vamos a utilizar «org.hibernate.usertype.CompositeUserType».
Las implementaciones deben ser inmutables y declarar un constructor público predeterminado.

Esta interfaz, debe aplicarse a campos anotados con @Type donde definimos el nombre de la clase que implementa la interfaz.
Ej. @Type(type = «com.autentia.xxx.yyy.StatusType»)

Imaginemos que tenemos una entidad cuenta que posee una máquina de estados con un estado actual y un estado anterior.

Los posibles estados son ACTIVE, UNCOVER, SUSPEND, CANCELED.

Estos estados son servicios de Spring que poseen la funcionalidad que implementa las transiciones desde este al resto de estados ante determinados eventos.

Para aislar la funcionalidad de la máquina de estados la encapsulamos en la clase StatusHolder,como no es el objetivo del tutorial no
profundizaremos en su implementación.

La entidad Account debe tener el siguiente atributo que nos permite persistir en BBDD los nombres de las clases que se encuentran asociadas al valor actual y anterior.

@Type(type = "com.autentia.xxx.yyy.StatusType")
@Columns(columns = { @Column(name = "actual"), @Column(name = "previous") })
private StatusHolder statusHolder = new StatusHolder();

Pongo la clase completa como ejemplo de implementación.

package com.autentia.xxx.yyy.status;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.springframework.util.ClassUtils;

import com.autentia.wuija.util.spring.SpringUtils;

public class StatusType implements CompositeUserType {

	// nombre de las propiedades
	private String[] propertyNames = { "actual", "previous" };

	// tipologia de las propiedades
	private Type[] propertyTypes = { Hibernate.STRING, Hibernate.STRING };
	
	//  Metodo para reconstruir un objeto desde la representación cacheable.
	@Override
	public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
		return deepCopy(cached);
	}
	
	//  Metodo para transformar el objeto en su representación cacheable.
	@Override
	public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
		return (Serializable)deepCopy(value);
	}

	// Devuelve una copia del objeto 
	@Override
	public Object deepCopy(Object value) throws HibernateException {
		final StatusHolder originalStatusHolder = (StatusHolder)value;
		StatusHolder statusHolder = new StatusHolder();
		statusHolder.setActual(originalStatusHolder.status());
		statusHolder.setPrevious(originalStatusHolder.previousStatus());
		return statusHolder;
	}

	@Override
	public boolean equals(Object x, Object y) throws HibernateException {
		return x.equals(y);
	}

	@Override
	public String[] getPropertyNames() {
		return propertyNames;
	}

	@Override
	public Type[] getPropertyTypes() {
		return propertyTypes;
	}

	@Override
	public Object getPropertyValue(Object component, int property) throws HibernateException {
		StatusHolder holder = (StatusHolder)component;
		switch (property) {
		case 0:
			return holder.status();
		case 1:
			return holder.previousStatus();
		}
		throw new IllegalArgumentException("property " + property + " recibido es un índice inválido para la clase "
				+ ClassUtils.getQualifiedName(component.getClass()));
	}

	@Override
	public int hashCode(Object x) throws HibernateException {
		return x.hashCode();
	}

	@Override
	public boolean isMutable() {
		return true;
	}


	// Metodo que construye una instancia de la clase asignada en base al resultado JDBC.
	@Override
	public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
			throws HibernateException, SQLException {
		if (rs.wasNull()) {
			return null;
		}
		String actualName = rs.getString(names[0]);
		String previousName = rs.getString(names[1]);
		StatusHolder holder = null;
		holder = new StatusHolder();
		try {
			Status actualState = getStatusByClassName(actualName);
			holder.setActual(actualState);
			Status previousState = getStatusByClassName(previousName);
			holder.setPrevious(previousState);
		} catch (Exception e) {
			throw new HibernateException(e);
		}
		return holder;
	}

	// Metodo que construye un PreparedStatement en base a la instancia pasada como parámetro.
	@Override
	public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
			throws HibernateException, SQLException {
		if (value == null) {
			st.setNull(index, Hibernate.STRING.sqlType());
			st.setNull(index+1, Hibernate.STRING.sqlType());
		} else {
			StatusHolder holder = (StatusHolder)value;
			st.setString(index, ClassUtils.getQualifiedName(holder.status().getClass()));
			if ( holder.previousStatus() != null ){
				st.setString(index+1, ClassUtils.getQualifiedName(holder.previousStatus().getClass()));
			} else {
				st.setNull(index+1, Hibernate.STRING.sqlType());	
			}
		}
	}

	@Override
	public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException {
		return deepCopy(original);
	}

	@Override
	public Class returnedClass() {
		return StatusHolder.class;
	}

	@Override
	public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
		StatusHolder holder = (StatusHolder)component;
		String propertyValue = (String)value;
		Status status;
		try {
			status = getStatusByClassName(propertyValue);
		} catch (ClassNotFoundException e) {
			throw new IllegalArgumentException("Clase no encontrada", e);
		}
		switch (property) {
		case 0:
			holder.setActual(status);
			return;
		case 1:
			holder.setPrevious(status);
			return;
		}
		throw new IllegalArgumentException("property " + property + " recibido es un índice inválido para la clase "
				+ ClassUtils.getQualifiedName(component.getClass()));
	}

	private Status getStatusByClassName(String previousName) throws ClassNotFoundException {
		return (Status) SpringUtils.getBean(Class.forName(previousName));
	}
}

4. Conclusión.

Como vemos en este ejemplo, simplemente implementando una interfaz («org.hibernate.usertype.CompositeUserType» en este caso) somos capaces de controlar el modo en el que
hibernate persiste nuestras clases.
Esto nos proporciona un mayor control y conocimiento sobre nuestro ORM.

Espero que os haya sido de utilidad.
Cualquier aclaración, duda o sugerencia podéis incluirla en la zona de comentarios.

Un saludo.
Alvaro Cuesta.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad