Usando ResultTransformer personalizado na sua consulta com Hibernate
No post “Como retornar e preencher apenas alguns atributos da entidade na consulta com Hibernate” mostrei como usar a classe AliasToBeanResultTransformer junto com HQL. A classe fornecida pelo próprio Hibernate é realmente muito útil, acontece que se você tem alguma consulta SQL através do método createSQLQuery (da classe org.hibernate.Session), pode ter passado pelo problema que tive essa semana. Infelizmente a classe AliasToBeanResultTransformer não funciona com SQLQuery quando, entre os atributos retornados, há Enumerations. Outro problema é quando temos atributos de entidades relacionadas. A reflexão empregada por essa classe auxiliar não consegue instanciar e preencher as dependências entre a classe principal e seus relacionamentos.
Vamos ao exemplo abaixo:
public Bem pesquisaDadosDoBemParaPesquisa(Integer codigo) {
Bem bem = new Bem();
StringBuffer consulta = new StringBuffer("select b.cd_bem as codigo, b.ds_bem as descricao, b.tombamento, b.tipo_bem as \"tipoBem\", l.ds_local as \"local.descricao\" from bem b inner join local l on l.cd_local = b.cd_local where b.cd_bem = ?");
try {
getHibernate().beginTransaction();
Query query = getHibernate().getSession().createSQLQuery(consulta.toString());
query.setParameter(0, codigo);
query.setResultTransformer(new AliasToBeanResultTransformer());
bem = (Bem) query.uniqueResult();
getHibernate().commit();
} catch (HibernateException e) {
e.printStackTrace();
getHibernate().rollback();
throw e;
} finally {
getHibernate().close();
}
return bem;
}
Como dito anteriormente, essa query não vai funcionar. Vamos aos motivos: na consulta acima queremos trazer o tipo do Bem – atributo tipoBem, porém ele é um Enumeration e está armazenado no banco como String. A classe AliasToBeanResultTransformer não trabalha bem com Enumerations. Outro problema está no fato de se tentar recuperar a descrição do local do Bem (alias local.descricao). A entidade Local tem relacionamento ManyToOne com a entidade Bem. Acontece que o ResultTransformer utilizado não realizará a instanciação de um Local, não setará sua descrição e, muito menos, irá associá-lo ao Bem corrente.
Solução
Uma saída é criar seu próprio ResultTransformer. No meu caso, criei uma classe chamada BemTransformer. Essa classe deve implementar a interface ResultTransformer que obriga a declaração de dois métodos: transformList(lista) e transformTuple(valores, alias). Vamos ao código abaixo (observe principamente os comentários):
public class BemTransformer implements ResultTransformer {
private static final long serialVersionUID = 8767683063836706565L;
@SuppressWarnings("rawtypes")
@Override
public List transformList(List list) {
return list;
}
@Override
public Object transformTuple(Object[] valores, String[] alias) {
Bem bem = new Bem();
bem.setCodigo((Integer) valores[0]); // alias codigo
bem.setDescricao((String) valores[1]); // alias descricao
bem.setTombamento((Integer) valores[2]); // coluna tombamento
bem.setTipoBem(TipoBem.valueOf(valores[3].toString())); // alias tipoBem
bem.setLocal(new Local()); // instancio um novo local e o associo ao bem
bem.getLocal().setDescricao((String) valores[4]); // seto a descrição do local do bem
return bem; // retorna o bem
}
}
O método transformList é chamado somente uma vez, caso você queira fazer alguma operação sobre a listagem retornada pelo Hibernate. A operação mais importante, obviamente, está no método transformTuple. Ele é invocado para cada linha retornada na Query, devolve um Object e possui dois parâmetros: o primeiro é um Array de objetos com todos os valores retornados para cada linha da query; o outro são os alias de cada coluna. É nesse método que vai acontecer todo o procedimento. No meu caso instancio um Bem, associo a um local e seto a descrição dele, retornando depois o bem recém alocado.
Aqui cabe uma observação: não é necessário usar os alias correspondendo aos atributos do objeto final quando se usa SQLQuery e o ResultTransform que você implementou. Não modifiquei a consulta apenas para não complicar.