Durante o desenvolvimento de aplicações corporativas em geral, nos deparamos sempre com requisitos de registro de histórico ou auditoria em banco de dados. Para isso podemos utilizar a ferramenta Hibernate Envers que foi iniciado como um projeto do JBoss e futuramente será englobado pelo Hibernate (provavelmente na versão 3.5, conforme anunciado no site do projeto).

Realizamos uma prova de conceito desta ferramenta visando assegurar o seu uso em nossos projetos. Para a prova de conceito utilizamos a versão 1.2.0.GA, que funciona somente com o Hibernate 3.3. Por este motivo, não foi possível utilizá-lo no JBoss 4.2.3 (que utiliza o Hibernate 3.2) e não foi trivialmente possível substituir a versão do Hibernate no JBoss para fazer o Envers funcionar.

A prova de conceito foi desenvolvida utilizando o myContainer e construindo uma suite de testes unitários para verificar se o framework funciona para o que se propõe a fazer. Posteriormente o módulo EJB do projeto foi deployado no JBoss AS 5.0.1.GA, onde os testes foram executados utilizando um cliente remoto.

O framework foi avaliado em vários aspectos, que serão explicados a seguir.

Configuração

Adicionar auditoria em um projeto é muito simples. Basta anotar as entidades em que se deseja auditoria e fazer uma simples configuração no arquivo persistence.xml. Para cada entidade anotada com @Audited (org.hibernate.envers.Audited) o framework cria uma nova tabela do banco. Por exemplo, se anotarmos e entidade Cliente com @Audited, o framework irá criar uma tabela Cliente_AUD onde irá replicar as informações da entidade modificada e registrar alguns dados como número de revisão e operação (remoção/alteração/inclusão). É possível também utilizar um controle mais granular, indicando quais campos de cada entidade devem ser auditados e definir o nome das tabelas/colunas geradas pelo framework.

O framework utiliza o mesmo conceito do Subversion com relação às revisões, ou seja, cada revisão é um marco global possuindo um número que é unico para o banco de dados inteiro e não um número por tabela ou registro. As alterações que forem feitas em uma mesma transação irão possuir o mesmo número de revisão.

A configuração é bastante simples e para ativar o Envers basta adicionar as seguintes linhas no persistence.xml:

<property name="hibernate.ejb.event.post-insert" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-update" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-delete" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" />

Volume de Dados Gerado

Uma das grandes preocupações quando pensamos em histórico de registros é o volume de dados gerado e a performance da aplicação. Este problema foi levado em consideração pelo framework pois o mecanismo de replicação de registros e relacionamentos é bastante inteligente. Todos os relacionamentos de uma entidade auditada precisam, obrigatoriamente, ser entidades auditadas, porém o mecanismo cria registros de histórico somente quando necessário. Por exemplo: imagine que temos o relacionamento “Cliente 1—>* Contato” (um cliente possui vários contatos). Ao alterar um cliente, o framework cria um registro de histórico somente para o registro alterado. Será criado registro de histórico para os contatos somente se eles forem também alterados. Poderíamos até mesmo adicionar um novo contato na lista que o framework irá criar somente o histórico do cliente e do novo contato adicionado, não criando registros de histórico para os demais contatos associados ao cliente em questão. Desta forma, o volume de dados gerado é considerado aceitável.

Consulta de Histórico

O framework fornece duas APIs para consulta de histórico:

  • AuditReader -> Fornece métodos para a consulta de uma determinada entidade em uma determinada revisão.
  • AuditQuery -> Mecanismo de query muito semelhante à Criteria API do Hibernate. Pode ser utilizado para fazer consultas mais complexas.

Ponto fraco: Ao consultar o histórico de uma entidade, todos os seus relacionamentos comportam-se como se o FetchType fosse Lazy. Outro problema é que não é possível resolver estas dependências posteriormente (em outra sessão do Hibernate), sendo necessário carregar todos os relacionamentos que serão utilizados no momento da consulta.

BUG: Ao consultar um número de revisão inexistente com a API AuditReader é retornado o registro de histórico mais atual. Deveria retornar null segundo a documentação.

Ponto forte: Navegação entre os registros de histórico: Após consultar um registro de histórico em uma determinada revisão com o AuditReader é possível navegar por todo o grafo de relacionamentos deste registro no estado em que ele estava no número da revisão utilizado para pesquisa.

Customização dos Registros de Histórico

O registro padrão de histórico armazena somente o número da revisão e a data da alteração do registro (em uma tabela separada chamada REVINFO).

Em muitos casos é necessário customizar o registro de histórico para armazenar mais informações como, por exemplo, o usuário que efetuou a alteração. O framework possui uma maneira simples de fazer essa customização. Basta criar uma nova entidade e utilizar a anotação @RevisionEntity. Essa anotação define que a entidade é uma “Entidade de Revisão”, ou seja, ela é uma entidade separada que mantém informações relacionadas a um número de revisão. Todas as tabelas de histórico possuem uma FK que aponta para essa tabela e ela substituirá a tabela REVINFO.

Para preencher essa entidade com as informações necessárias (por exemplo: o login do usuário), é necessário criar uma classe que implemente a interface RevisionListener e passar essa classe como parâmetro da anotação @RevisionEntity. O método newRevision dessa classe será chamado sempre que um novo registro de histórico estiver sendo criado.

Consulta de histórico diretamente no Banco de dados

O framework gera o banco de dados de uma maneira que as tabelas de histórico possuem a mesma estrutura das tabelas reais, inclusive as tabelas de relacionamentos. Desta maneira é possível fazer consultas no banco de histórico como se fosse o banco real, em um determinado marco no tempo, definido por uma data ou número de revisão, apenas fazendo join com a tabela REVINFO (ou outra tabela caso o registro de histórico seja customizado).

Resumo

Em resumo podemos citar como pontos positivos:

  • Configuração – É simples configurar e adicionar auditoria aos projetos.
  • Volume de dados – Criação do registro de histórico somente quando o registro foi modificado.
  • Customização – Facilidade de customizar o histórico.
  • Conceito de revisão – É simples consultar o histórico do banco em um determinado marco no tempo através do número de revisão.

Pontos negativos:

  • A API de consulta possui alguns problemas como por exemplo: Ignorar o fetch dos relacionamentos e tratar tudo como lazy.
  • Seria bom se fosse possível consultar o histórico através de queries JPA.
  • Não funciona, através de configuração trivial, no JBoss 4.2.X, apenas no JBoss 5 em diante.

Prova de Conceito

Ao executar, os dois testes deverão acusar falhas. Estas falhas foram mantidas nos casos de teste porque, segundo a documentação do Hibernate Envers, o comportamento esperado é diferente do comportamento obtido ou desejado.

Clique aqui para fazer o download dos casos de teste implementados na prova de conceito.