Máscaras com Hibernate
1 - Introdução
Em um projeto de migração, deparei-me com um problema comum, mas de difícil resolução (utilizando muita ‘gambiarra’). Recebi um damp de um banco de dados com várias tabelas, uma delas, TB_ENDERECO, possuía uma coluna de CEP, porém esta era do tipo numérica no banco, mas deveria ser sempre visualizada com a máscara 99999-999.
Criei então um UserType do hibernate, responsável pela formatação da máscara ao recuperar e remoção da máscara ao gravar o valor.
2 - Construção do utilitário
O primeiro passo é construir o utilitário que irá adicionar e remover a máscara.
package br.com.guj.hibernate;
/**
* Classe utilitária com métodos utilizados para formatar e remover formato
* @author fabio.viana
*/
public class MaskUtils{
/**
* Método que remove a mascara da string
*/
public static String removeMask(String numero) {
if (StringUtils.isNotEmpty(numero)) {
numero = numero.replaceAll("/", "");
numero = numero.replaceAll(".", "");
numero = numero.replaceAll("-", "");
numero = numero.replaceAll("\\", "");
numero = numero.replaceAll("_", "");
numero = numero.replaceAll("(", "");
numero = numero.replaceAll(")", "");
numero = numero.replaceAll(" ", "");
}
return numero;
}
/**
* Método que adiciona a mascara na string
*/
public static String addMask(String pMask, String pValue){
if (pValue == null || pValue.trim().equals(""))
return null;
for(int i = 0; i < pValue.length(); i++){
pMask = pMask.replaceFirst("#", pValue.substring(i, i + 1));
}
return pMask.replaceAll("#", "");
}
}
3 - O Tipo Formatador
Para criar um tipo de dados para o hibernate basta criar uma classe implementando a interface org.hibernate.usertype.UserType. Nela há métodos que lê e grava o valor diretamente do banco de dados, além de outros métodos auxiliares.
Para nosso formatador ficar bem reutilizável teremos que implementar outra interface, a org.hibernate.usertype.ParameterizedType, que indica pro Factory do hibernate que o type possue parâmetros que devem ser enviados para ele.
package br.com.guj.hibernate;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
/**
* @author fabio.viana
*/
public class MaskFormatType implements UserType, ParameterizedType {
/**
* Máscara utilizada
*/
private String mask = null;
/**
* Tipo que será setado no PreparedStatement
*/
private Class classType = Long.class;
/**
* Tipo de dado da coluna
*/
private int type = Types.NUMERIC;
public MaskFormatType(){
}
/**
* Tipo de dado da coluna
*/
public int[] sqlTypes() {
return new int[] { type };
}
/**
* Tipo que o UserType retorna
*/
public Class returnedClass() {
return String.class;
}
/**
* Compara dois valores formatados
*/
public boolean equals(Object obj0, Object obj1) throws HibernateException {
String x = (String) obj0;
String y = (String) obj1;
if (x == null){
x = "";
}
if (y == null){
y = "";
}
return x.equals(y);
}
/**
* Obtem o valor do banco, adiciona a máscara e retorna o valor formatado
*/
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
try {
Object value = rs.getObject(names[0]);
if (value == null)
return null;
String valueStr = BeanUtilsBean.getInstance().getConvertUtils().convert(value);
return MaskUtils.addMask(mask, valueStr);
} catch (Exception e) {
return null;
}
}
/**
* Pega o valor com a máscara, remove a máscara e seta no banco
*/
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
Object valSet = BeanUtilsBean.getInstance().getConvertUtils().convert(MaskUtils.removeMask((String)value), classType);
if (valSet == null)
st.setNull(index, type);
else
st.setObject(index, valSet);
}
/**
* Copia uma nova instancia do valor
*/
public Object deepCopy(Object value) throws HibernateException {
if (value == null)
return null;
else
return new String(value.toString());
}
/**
* Indica que o valor é mutável
*/
public boolean isMutable() {
return true;
}
/**
* Serializa o valor original
*/
public Serializable disassemble(Object value) throws HibernateException {
return (String)value;
}
/**
* Deserializa o valor em cache
*/
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
/**
* Retorna um possível subistituto
*/
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
/**
* Retorna o hashCode do valor formatado
*/
public int hashCode(Object obj) throws HibernateException {
return obj.hashCode();
}
/**
* Seta os parametros de configuração da máscara
*/
public void setParameterValues(Properties props) {
this.mask = (String) props.get("mask");
String classType = (String) props.get("classType");
if (StringUtils.isNotEmpty(classType)){
try {
this.classType = (Class) Class.forName(classType);
} catch (Exception e) {
e.printStackTrace();
}
}
String type = (String) props.get("type");
if (StringUtils.isNotEmpty(type)){
try {
this.type = Integer.parseInt(type);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Neste formatador existe 3 parametros, a máscara, o classType (tipo a ser setado para gravação) e o type (tipo da coluna no banco).
Obs.: Observe que estou utilizando a api commons-beans para a conversão de tipos, que pode ser baixado no site da Apache.
4 - Utilização
/**
* @author fabio.viana
*/
@Entity
public class Endereco implements Serializable{
@Id
@OneToOne
private Pessoa pessoa;
@Type (type="br.com.guj.hibernate.MaskFormatType", parameters={@Parameter(name="mask", value="#####-###") , @Parameter(name="classType", value="java.lang.Long"), @Parameter(name="type", value="2") })
private String cep;
...
}
Repare que o cep é do tipo string, mas no banco o tipo é numérico (2 representa o tipo NUMERIC - ver java.sql.Types), sendo registrado pelo parâmetro ‘type’, e no hibernate (na verdade no PreparedStatement) o tipo a ser instanciado e gravado é java.lang.Long, sendo registrado pelo parâmetro classType.
Sendo assim, ao recuperar um Endereço, a propriedade cep virá com a máscara, e ao salvar um Endereço, a coluna cep receberá um número.
5 - Conclusão
Este é um mero exemplo de utilização de UserType do hibernate. Em vários casos podemos utilizar este recurso para facilitar o desenvolvimento. Nesta mesma migração, criamos também um UserType para criar uma lista a partir de um campo varchar2 que armazena telefones no mesmo campo utilizando; como separador.
Então, é só…