Sei sulla pagina 1di 7

_envers

Desmistificando o
Hibernate Envers
em 10 passos
Auditoria de dados? Registro de Log? Trilha de auditoria? Descubra
como fcil, atravs do Hibernate Envers, rastrear o histrico de
mudanas e ter informaes de quem, quando e o que foi modificado
por um usurio. Em dez passos tem-se uma aplicao capaz de gerenciar todas as modificaes realizadas na base de dados de forma
simples e no intrusiva.

uando desenvolvemos sistemas de criticidade


alta, que requerem regras de trilha de auditoria
ou log de aes realizadas pelo usurio (isso ocorre
principalmente em sistemas governamentais) nos
deparamos com incluses de auditoria na base de
dados, gerando um registro de histrico das aes
exercidas passo a passo pelos usurios. A motivao
para o cliente solicitar esse requisito poder auditar completamente os seus dados, ou seja, rastrear o
histrico de mudana e ter as informaes de quem,
quando e o que foi modificado.
Esse requisito tratado como ponto crtico para
o sistema e avaliado pelos gerentes e/ou lderes de

projeto na gesto de riscos do projeto. Cabe ao arquiteto decidir qual ser a soluo tcnica e o procedimento adotado para gerar uma trilha de auditoria
utilizando bibliotecas que auxiliam no registro histrico dos dados.
O framework Hibernate Envers nos ajuda a criar
trilhas de auditoria de forma simples, eficaz e principalmente no intrusiva, sendo compatvel com
projetos que utilizam o Hibernate para a camada de
persistncia. Esse framework ao facilitar o versionamento de classes persistentes (ORM) agrega valor ao
produto entregue ao cliente, visto possibilitar respostas sobre quem, quando e o que foi modificado.

5\

Bruno Moreira Rocha | bruno@petrim.com.br


brasiliense, bacharel em Cincias da Computao pelo UniCEUB e trabalha com Java h 6 anos. SCJP, OCWCD e Certified
ScrumMaster. http://www.petrim.com.br

Luis Gustavo Santos Fernandez | luisgustavo.fernandez@gmail.com


formado em Engenharia da Computao pelo Centro Universitrio de Braslia. Trabalha com desenvolvimento de software h 6
anos. Atualmente exercendo a funo de arquiteto de sistemas. http://www.futurextending.com.br

Este artigo est estruturado de forma que o leitor


consiga entender o Hibernate Envers e desmistific-lo em dez (10) passos, contendo itens como: conceito, configurao, modelagem de dados e consultas de
registros histricos.

Hibernate Envers

O Envers (Easy Entity Versioning) um framework que possibilita o versionamento das entidades persistentes de forma simples e fcil, sendo
necessria somente a utilizao de anotaes. Com
isso permitido manter um histrico das alteraes
dos dados das entidades, sem que o desenvolvedor
gere classes especficas para o mapeamento das entidades de histrico. Para auditar as alteraes realizadas na entidade por meio de conceitos de reviso
similares aos do Subversion.
O conceito de reviso se caracteriza pela persistncia de uma entidade, mapeada pela biblioteca, em
uma transao atmica. Ou seja, para toda persistncia de uma entidade auditvel criada uma reviso,
um clone do objeto contendo um atributo de reviso
como chave primria que persiste em uma tabela de
histrico na base de dados.
O Hibernate Envers, mdulo do Hibernate, requer o Hibernate Annotations e o EntityManager
para o seu funcionamento. Alm disso, as entidades devem ter identificadores nicos (Primary Key)
imutveis. O Envers trabalha de forma autnoma ou
dentro de um container como JBoss AS, em conjunto com os Frameworks JBoss SEAM ou SpringFramework.
Algumas caractersticas:
Mapeamento da auditoria definido pela especificao do JPA.
Mapeamentos de auditoria, em conjunto com
a JPA, extensveis para atributos de dados,
como colees e mapas.
Criao de um registro de reviso para cada
registro de dados de auditoria, ou seja, para
cada entidade auditada criada uma entidade
/6

de reviso.
Consulta de dados histricos.

Desmistificando o Hibernate Envers em


dez passos

Para facilitar o entendimento dos passos abaixo utilizaremos um modelo de dados para ilustrar o
nosso universo (figura 1).

Passo 1: Transaes de Auditorias

O Envers considera que cada transao como


uma nova reviso (similar ao conceito do subversion), desde que a entidade esteja mapeada com
a anotao @Audited. Quando houver transaes
concorrentes de uma mesma entidade, para que o
histrico seja real, a incluso do histrico ocorrer
em uma mesma transao.
Um erro comum que pode ocorrer quando se
utiliza relacionamentos um para muitos (OneToMany) e muitos para muitos (ManyToMany). Ao incluir a entidade pai, os relacionamentos representados por colees devem ser persistidos na mesma
transao. Ao persistir o relacionamento em uma
transao distinta, o Envers definir cdigos de reviso diferentes para a Entidade pai e para os relacionamentos deixando completamente inconsistente a
consulta do histrico.

Passo 2: Restries e Definies

O Envers possui algumas definies e restries


que devem ser conhecidas para um melhor entendimento dos passos seguintes.
As entidades no auditadas no podem ser
anotadas com @Audited. Por mais que a anotao
possua o atributo targetAuditMode definido como
RelationTargetAuditMode.NOT_AUDITED, o Envers
considerar que a classe ser auditada. isso diz respeito somente aos atributos e relacionamentos da
Entidade, sendo assim, caso na anlise da entidade
seja verificado que a mesma no precisa ser audita-

Figura 1. Modelo de dados.

da, no acrescente a anotao.


Quando utilizamos o JPA/Hibernate para o mapeamento de entidades e necessitamos mapear entidades que contenham chaves primrias compostas
necessria a criao de uma classe que representa
a chave composta. Como restrio do Envers, nesse
tipo de mapeamento, no podemos utilizar anotaes @ManyToOne, @OneToOne, @OneToMany e/
ou @ManyToMany.
O Envers no suporta colees porque esse tipo
de dado pode conter elementos no excludentes. O
motivo est contido no conceito de normalizao de
dados, mais especificamente na primeira forma normal, segundo a qual cada atributo de uma tabela deve
conter somente valores atmicos e no pode conter
grupos ou atributos multivalorados.
No caso de tabelas que requerem uma tabela associativa quando houver um elemento duplicado ser
lanada uma exceo devido violao de restrio
exclusiva.
Ao utilizar mapeamentos @OneToMany, para
representar um mapeamento bidirecional, pode-se
ocasionar excees de mapeamento no qual o Envers
nos informa que o atributo mapeado no pode ser
lido. Para solucionar o problema devemos utilizar a
anotao @NotAudited ao invs da anotao @Audited. Neste momento o Envers no ir auditar o relacionamento bidirecional.

Listagem 1. A Listagem 1 apresenta um exemplo de


mapeamento muitos para um utilizando a anotao @
ManyToOne.
@ManyToOne
@JoinColumn(name = property)
@Audited(targetAuditMode = RelationTargetAuditMode.
NOT_AUDITED)
private Object objeto;

Listagem 2. A Listagem 2 apresenta um exemplo de


mapeamento um para muitos utilizando a anotao @
OneToMany.
@OneToMany(mappedBy = property)
@NotAudited
private Set<Object> lista;

Passo 3: Acrescentando a dependncia


do Hibernate Envers, utilizando o
Maven.

Neste artigo, utilizaremos a ferramenta Apache


Maven para gerenciar o controle de dependncias de
bibliotecas do Hibernate Envers nos projetos.
Para acrescent-lo no projeto necessrio configurar o arquivo pom.xml de acordo com a verso
do hibernate-core e hibernate-entitymanager. Para
verses anteriores ao Hibernate 3.4 deve-se utilizar
o JBoss Envers.

Listagem 3. Adicionando a dependncia do JBoss


Envers para Hibernate 3.3.
<dependency>
<groupId>org.jboss.envers</groupId>
<artifactId>jboss-envers</artifactId>
<version>1.2.2.GA-hibernate-3.3</version>
</dependency>

Para a verso do Hibernate 3.6 e 4.1 deve-se utilizar o mdulo hibernate-envers.

Listagem 4. Adicionando a dependncia do Hibernate Envers para Hibernate 3.6.

7\

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>3.6.10-Final</version>
</dependency>

Passo 5: Como Auditar Entidades

Para auditar as entidades necessrio identific-las com a anotao @Audited da mesma maneira
como identificamos as entidades persistentes com @
Entity.
O Hibernate Core o Envers possibilita a customizao da nomenclatura da tabela tanto pela anotao
@AuditTable quanto pela @Table. Quando no se
Listagem 5. Adicionando a dependncia do Hiberna- utiliza essa anotao o Envers entende que a nomente Envers para Hibernate 4.1.
clatura da tabela de histrico ser o nome da classe
acrescentado pelo sufixo _AUD.
<dependency>
Para exemplificar a anotao @AuditTable temos
<groupId>org.hibernate</groupId>
uma entidade mapeada com a anotao @Table(name
<artifactId>hibernate-envers</artifactId>
= tabela, schema=dbschema) e o Envers entender
<version>4.1.0-Final</version>
que a nomenclatura da tabela de auditoria ser a ta</dependency>
bela_AUD para o esquema de banco de dados dbschema. No caso de se adicionar a entidade anotao
@AuditTable(name=hist_tabela, schema=dbhist)
Passo 4: Adicionando os listerners do
o Envers entender que a tabela de auditoria ser a
Envers
hist_tabela para o esquema dbhist. Assim podemos
O Hibernate Envers utiliza classes de listener que
customizar a nomenclatura das tabelas de registros
so responsveis pelas aes de auditoria, ou seja,
de dados histricos.
tm a funo de incluir registros nas tabelas de histrico de acordo com a ao realizada pelo usurio na Listagem 7. A Listagem 7 apresenta um exemplo de
aplicao.
mapeamento utilizando JPA e Hibernate Envers.
Essas classes de listener devem constar no arquivo persistence.xml, caso contrrio de nada adiantar
as annotations presentes nas Entidades responsveis @Entity
pela auditoria. Segue abaixo como deve ficar a confi- @Table(name = USUARIO, schema = DBSISTEMA)
@Audited
gurao no arquivo persistence.xml:

Listagem 6. Adicionando os listerners no persistence.

xml.

<properties>
<property name=hibernate.ejb.event.post-insert
value=org.hibernate.ejb.event.
EJB3PostInsertEventListener,
org.hibernate.envers.event.AuditEventListener />
<property name=hibernate.ejb.event.post-update
value= org.hibernate.ejb.event.
EJB3PostUpdateEventListener,
org.hibernate.envers.event.AuditEventListener />
<property name=hibernate.ejb.event.post-delete
value=org.hibernate.ejb.event.
EJB3PostDeleteEventListener,
org.hibernate.envers.event.AuditEventListener />
<property name=hibernate.ejb.event.precollection-update value= org.hibernate.envers.
event.AuditEventListener />
<property name=hibernate.ejb.event.precollection-remove value= org.hibernate.envers.
event.AuditEventListener />
<property name=hibernate.ejb.event.postcollection-recreate value= org.hibernate.
envers.event.AuditEventListener />
</properties>

/8

@AuditedTable(table = HIST_USUARIO, schema =


DBSISTEMA)
public class Usuario {
// Atributos
@Id
@Column(name = CODIGO)
private Integer codigo;
@Column(name = NOME)
private String nome;
// Relacionamentos
@ManyToOne
@JoinColumn(name = EMPRESA_CODIGO,
referencedColumnName = CODIGO)
private Empresa empresa;
@OneToMany
@JoinColumn(name = USUARIO_CODIGO,
referencedColumnName = CODIGO)
private List<Documento> documentos;
}

//gets e sets omitidos

A partir do momento que existe a anotao @Audited, automaticamente todos os atributos e relacionamentos sero auditados sem necessitar de alguma
configurao para que isso acontea.

Caso seja necessrio, possvel limitar o que Listagem 9. A Listagem 9 apresenta uma entidade de
deve ser auditado. Por exemplo, pode-se definir que reviso mapeada com JPA e Hibernate Envers.
apenas algumas informaes de uma determinada tabela sejam necessrias para a auditoria ou para evitar
@Entity
que alguma coluna contendo um valor extenso seja @RevisionEntity(RevisaoListener.class)
armazenado tambm na tabela de auditoria, como @Table(name = REVISAO, schema = DBSISTEMA)
um Blob.
@SequenceGenerator(name = SEQ_REVISAO,
Para no auditar atributos ou relaciona- sequenceName = DBSISTEMA.SEQ_REVISAO,
mentos utilize as anotaes @NotAudited ou @ allocationSize = 1, initialValue = 1)
Audited(targetAuditMode = RelationTargetAuditMo- public class Revisao {
de.NOT_AUDiTED). A primeira anotao ignora to@Id
talmente o atributo ou relacionamento, sendo ideal
@GeneratedValue(generator = SEQ_REVISAO,
para relacionamentos que so somente leitura (constrategy = GenerationType.SEQUENCE)
figurados como insertable = false e updatable = false).
@Column(name = CODIGO_REVISAO, length = 8,
A segunda servir para relacionamentos com tabelas
nullable = false)
de domnio (code tables) com relacionamentos @
@RevisionNumber
ManyToOne, por exemplo. Os valores dessas tabelas
private Long codigoRevisao;
no sofrero alteraes por isso no seria interessan@RevisionTimestamp
te manter uma ou mais tabelas de histrico para ela.
@Temporal(TemporalType.TIMESTAMP)
@Column(name = DT_REVISAO)
private Date timestamp;

Passo 6: Entidade de Reviso

O Hibernate Envers controla as verses por meio


de uma Entidade de Reviso que contm a anotao
@RevisionEntity. A tabela que essa entidade representa conter o cdigo de reviso, o tipo de reviso e
a data em que foi feita a ao.
possvel colocar quantos atributos forem necessrios nessa tabela, como, por exemplo, o cdigo
do usurio, o endereo iP do usurio, entre outras informaes. Essas informaes sero preenchidas pela
classe de Listener de Reviso definida na anotao @
RevisionEntity.
O prprio Envers responsvel por informar o
valor do tipo de reviso analisando o mtodo utilizado (insert, update e delete). Os valores possveis para
o tipo de reviso so: 1 (um) para incluses, 2 (dois)
para alteraes e 3 (trs) para delees.
Os atributos da Entidade de Reviso que representam o cdigo da reviso e a data da ao devem
conter as anotaes @RevisionNumber e @RevisionTimestamp.
O Envers considerar que foram criadas as colunas REV e REV_TYPE, que representam o cdigo de
reviso e o tipo de reviso, respectivamente. Caso
seja necessrio alterar o nome dessas colunas por
uma questo de padronizao de banco de dados, por
exemplo, basta acrescentar configuraes no persistence.xml, abaixo:

Listagem 8. Configuraes no persistence.xml..


<properties>
<property name=org.hibernate.envers.revision_
field_name value=CODIGO_REVISAO />
<property name=org.hibernate.envers.revision_
type_field_name value=CODIGO_TIPO_REVISAO/>
</properties>

@Column(name = CODIGO_USUARIO)
private Long codigoUsuario;
@Column(name = EMAIL_USUARIO)
private String emailUsuario;
@Column(name = NOME_USUARIO)
private String nomeUsuario;
//...
}

Passo 7: Listener de Reviso

As classes de listeners de reviso, que possuem


a responsabilidade de preenchimento da entidade de
reviso com os dados pertinentes auditoria, so invocadas aps as aes definidas nas propriedades do
arquivo persistence.xml (conforme Listagem 6).
O requisito para criao de classes de listerner de
reviso a implementao da interface org.hibernate.envers.RevisionListener desenvolvendo o mtodo
void newRevision(Object revisionEntity), responsvel pela criao de uma nova reviso para os dados
auditados.

Listagem 10. A Listagem 10 apresenta um exemplo


de listener para recuperar os dados de um usurio,
utiliza-se o SpringFramework.
public class RevisaoListener implements org.hibernate.
envers.RevisionListener {
@Override

9\

public void newRevision(Object objetoRevisao) {


Revisao revisao = (Revisao) objetoRevisao;
Authentication authentication =
SecurityContextHolder.getContext().
getAuthentication();
Usuario usuario = (Usuario) authentication.
getPrincipal();
revisao.setCodigoUsuario(usuario.getId());
revisao.setNomeUsuario(usuario.getNome());
revisao.setEmailUsuario(usuario.getEmail());
revisao.setTimestamp(new Date());
}

muitos para um (ManyToOne), este no ser carregado de forma automtica (mesmo utilizando o tipo
de inicializao EAGER). Este relacionamento precisa
ser inicializado manualmente utilizando-se o mtodo initialize da classe org.hibernate.Hibernate.
infelizmente, mesmo que seja feita uma inicializao explcita, o Envers no carregar os relacionamentos um para muitos (OneToMany) e muitos para
muitos (ManyToMany). Uma soluo seria carregar
separadamente a lista de Entidades que representam
um determinado relacionamento atravs de consultas com a APi do Envers utilizando o cdigo de reviso desejado e a classe especfica.

Listagem 11. Mtodo initialize.

Passo 8: Criando Tabelas de Auditoria e


Reviso

As tabelas de histrico devem ser consideradas


espelhos das tabelas auditadas, mantendo a mesma
nomenclatura da tabela de origem. Acrescentando
dois atributos: o cdigo da reviso (chave primria
gerada atravs da tabela de reviso) e o tipo de reviso que definir a ao executada como incluso,
alterao ou excluso de dados.

Passo 9: Relacionamentos

Quando for necessrio recuperar os dados de


auditoria e a entidade contiver o relacionamento de

Figura 2. Modelo de Dados, Tabelas de Histrico.

/ 10

public static void initialize(Object proxy) throws


HibernateException {
if ( proxy == null ) {
return;
} else if ( proxy instanceof HibernateProxy ) {
( ( HibernateProxy ) proxy ).
getHibernateLazyInitializer().initialize();
} else if ( proxy instanceof PersistentCollection ) {
( ( PersistentCollection ) proxy ).forceInitialization();
}
}
//Usando o mtodo
Hibernate.initialize(entidade.getRelacionamento());

Passo 10: Consultando Revises

Ao contrrio do que normalmente feito, no


possvel consultar dados histricos utilizando os
mecanismos de busca fornecidos pelo Hibernate, ou
seja, no ser possvel a utilizao de HQL e Criteria
para recuperar Entidades que representam as tabelas
de histrico. No existe Entidade mapeada para as tabelas de auditoria, apenas para as originais, somente
sendo possvel recuperar a verso desejada no formato da Entidade com as consultas disponibilizadas
pela API do Envers ou atravs de SQL Nativo.
Para realizar consultas sem a utilizao de SQL
nativo, o Envers fornece a interface AuditReader que
contm mtodos que realizam consultas utilizando
as entidades j mapeadas. O Envers cria a Entidade
em tempo de execuo que representa a tabela de
histrico contendo as mesmas propriedades e relacionamentos da Entidade de origem.
A classe AuditReaderFactory responsvel por
recuperar uma instncia que implementa AuditReader. O mtodo get se encarrega dessa funo exigindo
como parmetro o EntityManager:

public List<T> findByRevision(Class classe,


Number numeroRevisao) {
reader = getAuditReader();
AuditQueryCreator query = reader.createQuery();
PropertyNameGetter p =
new RevisionNumberPropertyName();
AuditCriterion c = new SimpleAuditExpression(p,
numeroRevisao, =);
List<T> revisoes = (List<T>) query.
forEntitiesAtRevision(classe, numeroRevisao).
add(c).getResultList();
return revisoes;
}

Consideraes Finais

Neste artigo, foram abordados conceitos e demonstraes da utilizao do Hibernate Envers. Um


poderosssimo framework para a criao de trilhas de
auditoria, sem que haja a necessidade de desenvolver
uma estrutura especfica para as auditorias no SGBD
(Stored Procedures ou triggers).
Listagem 12. AuditReader.
No desenvolvimento de novos projetos que requerem trilhas de auditoria j podemos incorporar
o Hibernate Envers para o desenvolvimento deste
private AuditReader getAuditReader() {
requisito, minimizando os impactos por ser um fraAuditReader reader = AuditReaderFactory.
get(entityManager);
mework transparente, baseado em revises e princireturn reader;
pal por no ser intrusivo.
}
Seja produtivo com o mnimo de impacto possvel no desenvolvimento de auditoria no seu projeto,
divirta-se e adapte seu projeto para realizar trilhas de
Listagem 13. Exemplo como recuperar uma Entidade auditorias de forma simples e prtica utilizando-se o
Hibernate Envers.
atravs de sua chave e a reviso especfica desejada.
public T findByIdAndRevision(Object id, Number revision)
{
reader = getAuditReader();
T obj = reader.find(getPersistentClass(), id, revision);
return obj;
}

Lembre-se de inicializar os relacionamentos


(muitos-para-um) para que seja possvel recuperar os
valores dos mesmos.
Alm de fornecer uma srie de consultas teis
atravs da interface AuditReader, o Envers fornece
uma srie de interfaces que viabilizam o uso de Criteria para consultas personalizadas em dados histricos.

/referncias
> http://www.jboss.org/envers
> http://docs.jboss.org/envers/docs/index.html
> http://www.hibernate.org/
> http://download.jboss.org/envers/envers-1.2.2.gahibernate-3.3.pdf

Listagem 14. Consulta personalizada que recupera

uma lista de uma determinada Entidade atravs de um


cdigo de reviso especfico.

11 \

Potrebbero piacerti anche