Exceção De Hibernate Genérica - Exceção Genérica em Hibernate | PDF | Classe (programação de ...
Exceção Genérica em Hibernate | PDF | Classe (programação de ...

Como lidar com exceções genéricas do Hibernate na prática

Quem programa com Hibernate já se deparou com aquela stack trace gigante que termina em algo como org.hibernate.HibernateException sem nenhuma dica clara do que realmente deu errado. O problema não é o Hibernate em si, mas o fato de ele funcionar como uma exceção genérica que encapsula outros erros por baixo. A exceção de hibernate genérica é, essencialmente, um wrapper. Ela existe porque o Hibernate precisa capturar falhas em camadas diferentes — persistência nativa, JDBC, acesso a dados — e devolver tudo sob um mesmo guarda-chuva. O resultado é que, na superfície, parece que o framework lançou uma exceção qualquer, mas a verdadeira causa está enterrada no stack trace.

O que é a exceção de hibernate genérica

A classe raiz dessa cadeia é org.hibernate.HibernateException, uma subclasse de RuntimeException. Ela serve como exceção raiz para o ecossistema Hibernate ORM. Quando você vê essa exceção no log, o primeiro passo técnico é chamar getCause() para empurrar a pilha para baixo. Na maioria das vezes, você vai encontrar algo como org.hibernate.exception.SQLStateConverter.convert ou uma mensagem que aponta diretamente para SQLState, constraint violation, ou deadlock. Outras subclasses aparecem com frequência: ConstraintViolationException, DataException, TransactionException, LockAcquisitionException. Cada uma mapeia para uma categoria de falha. O Hibernate também converte exceções JDBC em suas próprias classes, e esse processo de conversão é comandado por um SQLExceptionConverter definido no seu persistence.xml ou nas propriedades de configuração. Se a conversão falhar ou não estiver configurada para o seu dialeto de banco, a exceção genérica aparece sem contexto útil.

Como desvendar a stack trace

A técnica mais direta é inspecionar a causa raiz. No código, um bloco de tratamento padrão ficaria assim:

try {
    session.persist(entity);
    transaction.commit();
} catch (HibernateException he) {
    Throwable cause = he.getCause();
    logger.error("Erro de persistência", he);
    if (cause != null) {
        logger.error("Causa raiz:", cause);
    }
    transaction.rollback();
    throw he;
}

O detalhe importante é que o getCause() pode retornar outra exceção do Hibernate, que por sua vez tem outra causa. Você precisa seguir a cadeia até o final, não parar na primeira camadade wrapper. Em casos mais complicados, usar StringUtils.rootCauseMessage(he) do Apache Commons ou simplesmente iterar sobre cause.getCause() resolve rapidamente. Se o seu logger for um SLF4J comum, passe a exceção inteira como último argumento do método de log. Isso faz o framework imprimir o stack trace completo automaticamente, em vez de você precisar chamar printStackTrace() manualmente.

Um caso real que encontrei

Num projeto interno, tivemos uma aplicação que parou de funcionar em produção sem erro aparente. O log mostrava apenas uma exceção de hibernate genérica durante uma operação simples de saveOrUpdate. Nenhuma mensagem de constraint, nenhum SQLState visível. Passei cerca de três horas revisando mapeamentos, configurações de pool e índices antes de perceber que o problema era um ClassNotFoundException para uma classe de UserType personalizada. O Hibernate capturava a falha de carregamento da classe e lançava uma HibernateException genérica sem expor o ClassNotFoundException corretamente no getCause(). A solução foi adicionar um ExceptionHandler customizado implementando a interface org.hibernate.engine.jdbc.spi.SqlExceptionHelper, mas o caminho mais prático foi ajustar a configuração do classloader para garantir que a JAR com a classe customizada estivesse em todos os nós do cluster. Depois disso, o stack trace voltou a mostrar a causa raiz corretamente. Aprendi que, em ambientes com múltiplos classloaders, a conversão de exceção pode perder informação se a classe de causa não for Carregada no mesmo loader que dispara a exceção original.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Pegadinhas comuns que ninguém mentiona

A primeira pegadinha é a configuração do dialeto. Se o hibernate.dialect não corresponde exatamente ao banco ou à versão dele, o SQLExceptionConverter padrão não consegue mapear códigos de erro específicos. O resultado é que exceções que seriam LockAcquisitionException ou ConstraintViolationException viram HibernateException pura. Sempre verifique se o valor do dialeto está na lista de disponíveis na documentação oficial do Hibernate correspondente à versão do produto. A segunda pegadinha envolve transações gerenciadas pelo contêiner. Em containers como WildFly ou TomEE, o Hibernate pode misturar LocalTransaction com JTA. Se você chamar transaction.rollback() manualmente quando a transação já está sendo controlada pelo JTA, oHibernate lança uma exceção genérica informando que a operação não é permitida. O correto é deixar o container gerenciar o rollback ou usar @Transactional com propagação adequada.

Uma terceira questão que vejo todo dia é a confusão entre NoUniqueBeanDefinitionException e exceções do Hibernate. Quando há mais de um EntityManagerFactory ou SessionFactory no contexto, o Hibernate tenta injetar o bean e falha antes mesmo de chegar na camada de persistência. A exceção resultante parece genérica, mas o problema é de dependência, não de SQL.

Falhas conhecidas e limitações do wrapper

O design do Hibernate como caixa preta para exceções tem uma desvantagem clara: erros de infraestrutura — tempo limite de conexão, esgotamento de pool, problemas de rede — muitas vezes chegam como HibernateException sem distinguir a natureza do problema. Isso dificulta monitoramento automatizado. Uma solução parcial é configurar um org.hibernate.engine.jdbc.spi.SqlExceptionHelper customizado que exponha o SQLState original e o tipo de erro JDBC antes de envolver tudo na exceção genérica. Outro ponto fraco é que o SQLStateConverter não cobre todos os bancos de forma uniforme. Para PostgreSQL e Oracle o mapeamento é razoavelmente completo. Para bancos mais incomuns ou versões mais antigas, exceções de integrity constraint e deadlock podem simplesmente desaparecer e retornar como exceções genéricas. Nesse cenário, a alternativa mais viável é ler diretamente o SQLState pela propriedade getSQLState() da exceção encapsulada, em vez de confiar na classificação automática do Hibernate.

O que fazer quando a exceção simplesmente não revela nada

Se após seguir a cadeia de causas você ainda não identificou o problema, ative o log SQL do Hibernate com nível DEBUG. O comando hibernate.show_sql combinado com hibernate.format_sql gera o SQL exato que foi enviado ao banco no momento do erro. Às vezes, a consulta mostra um tipo incompatível, um campo nulo inesperado, ou uma coluna que foi removida do schema mas ainda consta no mapeamento. Se o erro ocorrer em produção, considere adicionar um GlobalExceptionMapper ou um Filter que capture exceções do tipo HibernateException e registre o SQL executado, os parâmetros enviados e o SQLState retornado pelo driver. Esse log adicional reduz o tempo de investigação de horas para minutos na grande maioria dos casos.

Por fim, mantenha o Hibernate atualizado. Versões mais recentes do ORM melhoraram significativamente a conversão de exceções JDBC e a exposição de causas raiz. Mudanças na API de SQLExceptionConverter entre a versão 5 e a 6 também afetam como as exceções são propagadas, então upgrade sem revisão do código de tratamento pode introduzir comportamentos diferentes.