• Nenhum resultado encontrado

Desmistificando o Hibernate Envers em 10 passos

N/A
N/A
Protected

Academic year: 2021

Share "Desmistificando o Hibernate Envers em 10 passos"

Copied!
7
0
0

Texto

(1)

_envers

Auditoria de dados? Registro de Log? Trilha de auditoria? Descubra

como é fácil, através do Hibernate Envers, rastrear o histórico de

mudanças e ter informações de quem, quando e o que foi modifi cado

por um usuário. Em dez passos tem-se uma aplicação capaz de

ge-renciar todas as modifi cações realizadas na base de dados de forma

simples e não intrusiva.

Q

uando desenvolvemos sistemas de criticidade alta, que requerem regras de trilha de auditoria ou log de ações realizadas pelo usuário (isso ocorre principalmente em sistemas governamentais) nos deparamos com inclusões de auditoria na base de dados, gerando um registro de histórico das ações exercidas passo a passo pelos usuários. A motivação para o cliente solicitar esse requisito é poder audi-tar completamente os seus dados, ou seja, rastrear o histórico de mudança e ter as informações de quem, quando e o que foi modifi cado.

Esse requisito é tratado como ponto crítico para o sistema e avaliado pelos gerentes e/ou líderes de

Desmistifi cando o

Hibernate Envers

projeto na gestão de riscos do projeto. Cabe ao ar-quiteto decidir qual será a solução técnica e o proce-dimento adotado para gerar uma trilha de auditoria utilizando bibliotecas que auxiliam no registro his-tórico dos dados.

O framework Hibernate Envers nos ajuda a criar trilhas de auditoria de forma simples, efi caz e prin-cipalmente não intrusiva, sendo compatível com projetos que utilizam o Hibernate para a camada de persistência. Esse framework ao facilitar o versiona-mento de classes persistentes (ORM) agrega valor ao produto entregue ao cliente, visto possibilitar res-postas sobre quem, quando e o que foi modifi cado.

Desmistifi cando o

Hibernate Envers

em 10 passos

(2)

Bruno Moreira Rocha | bruno@petrim.com.br

é brasiliense, bacharel em Ciências da Computação 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 Computação pelo Centro Universitário de Brasília. Trabalha com desenvolvimento de software há 6 anos. Atualmente exercendo a função 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: concei-to, configuração, modelagem de dados e consultas de registros históricos.

Hibernate Envers

O Envers (Easy Entity Versioning) é um fra-mework que possibilita o versionamento das enti-dades persistentes de forma simples e fácil, sendo necessária somente a utilização de anotações. Com isso é permitido manter um histórico das alterações dos dados das entidades, sem que o desenvolvedor gere classes específicas para o mapeamento das en-tidades de histórico. Para auditar as alterações reali-zadas na entidade por meio de conceitos de revisão similares aos do Subversion.

O conceito de revisão se caracteriza pela persis-tência de uma entidade, mapeada pela biblioteca, em uma transação atômica. Ou seja, para toda persistên-cia de uma entidade auditável é criada uma revisão, um clone do objeto contendo um atributo de revisão como chave primária que persiste em uma tabela de histórico na base de dados.

O Hibernate Envers, módulo do Hibernate, re-quer o Hibernate Annotations e o EntityManager para o seu funcionamento. Além disso, as entida-des devem ter identificadores únicos (Primary Key) imutáveis. O Envers trabalha de forma autônoma ou dentro de um container como JBoss AS, em conjun-to com os Frameworks JBoss SEAM ou SpringFra-mework.

Algumas características:

» Mapeamento da auditoria definido pela espe-cificação do JPA.

» Mapeamentos de auditoria, em conjunto com a JPA, extensíveis para atributos de dados, como coleções e mapas.

» Criação de um registro de revisão para cada registro de dados de auditoria, ou seja, para cada entidade auditada é criada uma entidade

de revisão.

» Consulta de dados históricos.

Desmistificando o Hibernate Envers em

dez passos

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

Passo 1: Transações de Auditorias

O Envers considera que cada transação como uma nova revisão (similar ao conceito do subver-sion), desde que a entidade esteja mapeada com a anotação @Audited. Quando houver transações concorrentes de uma mesma entidade, para que o histórico seja real, a inclusão do histórico ocorrerá em uma mesma transação.

Um erro comum que pode ocorrer é quando se utiliza relacionamentos um para muitos (OneTo-Many) e muitos para muitos (ManyTo(OneTo-Many). Ao in-cluir a entidade pai, os relacionamentos represen-tados por coleções devem ser persistidos na mesma transação. Ao persistir o relacionamento em uma transação distinta, o Envers definirá códigos de revi-são diferentes para a Entidade pai e para os relacio-namentos deixando completamente inconsistente a consulta do histórico.

Passo 2: Restrições e Definições

O Envers possui algumas definições e restrições que devem ser conhecidas para um melhor entendi-mento dos passos seguintes.

As entidades não auditadas não podem ser anotadas com @Audited. Por mais que a anotação possua o atributo targetAuditMode definido como

RelationTargetAuditMode.NOT_AUDITED, o Envers

considerará que a classe será auditada. issodiz res-peito somente aos atributos e relacionamentos da Entidade, sendo assim, caso na análise da entidade seja verificado que a mesma não precisa ser

(3)

audita-da, não acrescente a anotação.

Quando utilizamos o JPA/Hibernate para o ma-peamento de entidades e necessitamos mapear en-tidades que contenham chaves primárias compostas é necessária a criação de uma classe que representa a chave composta. Como restrição do Envers, nesse tipo de mapeamento, não podemos utilizar anota-ções @ManyToOne, @OneToOne, @OneToMany e/ ou @ManyToMany.

O Envers não suporta coleções porque esse tipo de dado pode conter elementos não excludentes. O motivo está contido no conceito de normalização de dados, mais especificamente na primeira forma nor-mal, segundo a qual cada atributo de uma tabela deve conter somente valores atômicos e não pode conter grupos ou atributos multivalorados.

No caso de tabelas que requerem uma tabela as-sociativa quando houver um elemento duplicado será lançada uma exceção devido à violação de restrição exclusiva.

Ao utilizar mapeamentos @OneToMany, para representar um mapeamento bidirecional, pode-se ocasionar exceções de mapeamento no qual o Envers nos informa que o atributo mapeado não pode ser lido. Para solucionar o problema devemos utilizar a anotação @NotAudited ao invés da anotação

@Audi-ted. Neste momento o Envers não irá auditar o

rela-cionamento bidirecional.

Listagem 1.

A Listagem 1 apresenta um exemplo de mapeamento muitos para um utilizando a anotação @ 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 anotação @ OneToMany.

@OneToMany(mappedBy = “property”) @NotAudited

private Set<Object> lista;

Passo 3: Acrescentando a dependência

do Hibernate Envers, utilizando o

Maven.

Neste artigo, utilizaremos a ferramenta Apache Maven para gerenciar o controle de dependências de bibliotecas do Hibernate Envers nos projetos.

Para acrescentá-lo no projeto é necessário con-figurar o arquivo pom.xml de acordo com a versão do hibernate-core e hibernate-entitymanager. Para versões anteriores ao Hibernate 3.4 deve-se utilizar o JBoss Envers.

Listagem 3.

Adicionando a dependência 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 versão do Hibernate 3.6 e 4.1 deve-se uti-lizar o módulo hibernate-envers.

Listagem 4.

Adicionando a dependência do Hiberna-te Envers para HibernaHiberna-te 3.6.

(4)

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-envers</artifactId>

<version>3.6.10-Final</version> </dependency>

Listagem 5.

Adicionando a dependência do Hiberna-te Envers para HibernaHiberna-te 4.1.

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-envers</artifactId>

<version>4.1.0-Final</version> </dependency>

Passo 4: Adicionando os listerners do

Envers

O Hibernate Envers utiliza classes de listener que são responsáveis pelas ações de auditoria, ou seja, têm a função de incluir registros nas tabelas de his-tórico de acordo com a ação realizada pelo usuário na aplicação.

Essas classes de listener devem constar no arqui-vo persistence.xml, caso contrário de nada adiantará as annotations presentes nas Entidades responsáveis pela auditoria. Segue abaixo como deve ficar a confi-guração no arquivo persistence.xml:

Listagem 6.

Adicionando os listerners no persistence. xml. <properties> <propertyname=”hibernate.ejb.event.post-insert” value=”org.hibernate.ejb.event. EJB3PostInsertEventListener, org.hibernate.envers.event.AuditEventListener”/> <propertyname=”hibernate.ejb.event.post-update” value=” org.hibernate.ejb.event. EJB3PostUpdateEventListener, org.hibernate.envers.event.AuditEventListener”/> <propertyname=”hibernate.ejb.event.post-delete” value=”org.hibernate.ejb.event. EJB3PostDeleteEventListener, org.hibernate.envers.event.AuditEventListener”/> <property

collection-update”value=” org.hibernate.envers. event.AuditEventListener”/>

<property

collection-remove”value=” org.hibernate.envers. event.AuditEventListener”/>

<property collection-recreate” value=” org.hibernate. envers.event.AuditEventListener”/> </properties>

Passo 5: Como Auditar Entidades

Para auditar as entidades é necessário identificá--las com a anotação @Audited da mesma maneira como identificamos as entidades persistentes com @

Entity.

O Hibernate Core o Envers possibilita a customi-zação da nomenclatura da tabela tanto pela anotação

@AuditTable quanto pela @Table. Quando não se

utiliza essa anotação o Envers entende que a nomen-clatura da tabela de histórico será o nome da classe acrescentado pelo sufixo _AUD.

Para exemplificar a anotação @AuditTable temos uma entidade mapeada com a anotação @Table(name

= “tabela”, schema=”dbschema”) e o Envers entenderá

que a nomenclatura da tabela de auditoria será a ta-bela_AUD para o esquema de banco de dados dbsche-ma. No caso de se adicionar a entidade à anotação

@AuditTable(name=”hist_tabela”, schema=”dbhist”)

o Envers entenderá que a tabela de auditoria será a hist_tabela para o esquema dbhist. Assim podemos customizar a nomenclatura das tabelas de registros de dados históricos.

Listagem 7.

A Listagem 7 apresenta um exemplo de mapeamento utilizando JPA e Hibernate Envers.

@Entity

@Table(name = “USUARIO”, schema = “DBSISTEMA”) @Audited

@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;

//get’s e set’s omitidos

}

A partir do momento que existe a anotação @Audi-ted, automaticamente todos os atributos e relacio-namentos serão auditados sem necessitar de alguma configuração para que isso aconteça.

(5)

Caso seja necessário, é possível limitar o que deve ser auditado. Por exemplo, pode-se definir que apenas algumas informações de uma determinada ta-bela sejam necessárias para a auditoria ou para evitar que alguma coluna contendo um valor extenso seja armazenado também na tabela de auditoria, como um Blob.

Para não auditar atributos ou relaciona-mentos utilize as anotações @NotAudited ou @ Audited(targetAuditMode = RelationTargetAuditMo-de.NOT_AUDiTED). A primeira anotação ignora to-talmente o atributo ou relacionamento, sendo ideal para relacionamentos que são somente leitura (con-figurados como insertable = false e updatable = false). A segunda servirá para relacionamentos com tabelas de domínio (code tables) com relacionamentos @ ManyToOne, por exemplo. Os valores dessas tabelas não sofrerão alterações por isso não seria interessan-te maninteressan-ter uma ou mais tabelas de histórico para ela.

Passo 6: Entidade de Revisão

O Hibernate Envers controla as versões por meio de uma Entidade de Revisão que contém a anotação @RevisionEntity. A tabela que essa entidade repre-senta conterá o código de revisão, o tipo de revisão e a data em que foi feita a ação.

É possível colocar quantos atributos forem ne-cessários nessa tabela, como, por exemplo, o código do usuário, o endereço iP do usuário, entre outras in-formações. Essas informações serão preenchidas pela classe de Listener de Revisão definida na anotação @ RevisionEntity.

O próprio Envers é responsável por informar o valor do tipo de revisão analisando o método utiliza-do (insert, update e delete). Os valores possíveis para o tipo de revisão são: 1 (um) para inclusões, 2 (dois) para alterações e 3 (três) para deleções.

Os atributos da Entidade de Revisão que repre-sentam o código da revisão e a data da ação devem conter as anotações @RevisionNumber e @Revision-Timestamp.

O Envers considerará que foram criadas as colu-nas REV e REV_TYPE, que representam o código de revisão e o tipo de revisão, respectivamente. Caso seja necessário alterar o nome dessas colunas por uma questão de padronização de banco de dados, por exemplo, basta acrescentar configurações no persis-tence.xml, abaixo:

Listagem 8

. Configurações no persistence.xml..

<properties> <propertyname=”org.hibernate.envers.revision_ field_name”value=”CODIGO_REVISAO”/> <propertyname=”org.hibernate.envers.revision_ type_field_name”value=”CODIGO_TIPO_REVISAO”/> </properties>

Listagem 9.

A Listagem 9 apresenta uma entidade de revisão mapeada com JPA e Hibernate Envers.

@Entity

@RevisionEntity(RevisaoListener.class)

@Table(name = “REVISAO”, schema = “DBSISTEMA”) @SequenceGenerator(name = “SEQ_REVISAO”, sequenceName = “DBSISTEMA.SEQ_REVISAO”, allocationSize = 1, initialValue = 1)

public class Revisao {

@Id

@GeneratedValue(generator = “SEQ_REVISAO”, strategy = GenerationType.SEQUENCE)

@Column(name = “CODIGO_REVISAO”, length = 8, nullable = false)

@RevisionNumber private Long codigoRevisao;

@RevisionTimestamp

@Temporal(TemporalType.TIMESTAMP) @Column(name = “DT_REVISAO”) private Date timestamp;

@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 Revisão

As classes de listeners de revisão, que possuem a responsabilidade de preenchimento da entidade de revisão com os dados pertinentes à auditoria, são in-vocadas após as ações definidas nas propriedades do arquivo persistence.xml (conforme Listagem 6).

O requisito para criação de classes de listerner de revisão é a implementação da interface org.hiberna-te.envers.RevisionListener desenvolvendo o método void newRevision(Object revisionEntity), responsá-vel pela criação de uma nova revisão para os dados auditados.

Listagem 10.

A Listagem 10 apresenta um exemplo de listener para recuperar os dados de um usuário, utiliza-se o SpringFramework.

public class RevisaoListener implements org.hibernate. envers.RevisionListener {

(6)

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()); }

}

Passo 8: Criando Tabelas de Auditoria e

Revisão

As tabelas de histórico devem ser consideradas espelhos das tabelas auditadas, mantendo a mesma nomenclatura da tabela de origem. Acrescentando dois atributos: o código da revisão (chave primária gerada através da tabela de revisão) e o tipo de re-visão que definirá a ação executada como inclusão, alteração ou exclusão de dados.

Passo 9: Relacionamentos

Quando for necessário recuperar os dados de auditoria e a entidade contiver o relacionamento de

muitos para um (ManyToOne), este não será carre-gado de forma automática (mesmo utilizando o tipo de inicialização EAGER). Este relacionamento precisa ser inicializado manualmente utilizando-se o méto-do initialize da classe org.hibernate.Hibernate.

infelizmente, mesmo que seja feita uma iniciali-zação explícita, o Envers não carregará os relaciona-mentos um para muitos (OneToMany) e muitos para muitos (ManyToMany). Uma solução seria carregar separadamente a lista de Entidades que representam um determinado relacionamento através de consul-tas com a APi do Envers utilizando o código de revi-são desejado e a classe específica.

Listagem 11.

Método initialize.

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 método

Hibernate.initialize(entidade.getRelacionamento());

(7)

Passo 10: Consultando Revisões

Ao contrário do que normalmente é feito, não é possível consultar dados históricos utilizando os mecanismos de busca fornecidos pelo Hibernate, ou seja, não será possível a utilização de HQL e Criteria para recuperar Entidades que representam as tabelas de histórico. Não existe Entidade mapeada para as ta-belas de auditoria, apenas para as originais, somente sendo possível recuperar a versão desejada no for-mato da Entidade com as consultas disponibilizadas pela API do Envers ou através de SQL Nativo.

Para realizar consultas sem a utilização de SQL nativo, o Envers fornece a interface AuditReader que contém métodos que realizam consultas utilizando as entidades já mapeadas. O Envers cria a Entidade em tempo de execução que representa a tabela de histórico contendo as mesmas propriedades e rela-cionamentos da Entidade de origem.

A classe AuditReaderFactory é responsável por recuperar uma instância que implementa AuditRea-der. O método get se encarrega dessa função exigindo como parâmetro o EntityManager:

Listagem 12

. AuditReader.

private AuditReader getAuditReader() { AuditReader reader = AuditReaderFactory. get(entityManager);

return reader; }

Listagem 13.

Exemplo como recuperar uma Entidade através de sua chave e a revisão específica 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 possível recuperar os valores dos mesmos.

Além de fornecer uma série de consultas úteis através da interface AuditReader, o Envers fornece uma série de interfaces que viabilizam o uso de Cri-teria para consultas personalizadas em dados histó-ricos.

Listagem 14.

Consulta personalizada que recupera uma lista de uma determinada Entidade através de um código de revisão específico.

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; }

Considerações Finais

Neste artigo, foram abordados conceitos e de-monstrações da utilização do Hibernate Envers. Um poderosíssimo framework para a criação de trilhas de auditoria, sem que haja a necessidade de desenvolver uma estrutura específica para as auditorias no SGBD (Stored Procedures ou triggers).

No desenvolvimento de novos projetos que re-querem trilhas de auditoria já podemos incorporar o Hibernate Envers para o desenvolvimento deste requisito, minimizando os impactos por ser um fra-mework transparente, baseado em revisões e princi-pal por não ser intrusivo.

Seja produtivo com o mínimo de impacto possí-vel no desenvolvimento de auditoria no seu projeto, divirta-se e adapte seu projeto para realizar trilhas de auditorias de forma simples e prática utilizando-se o Hibernate Envers. > 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.ga-hibernate-3.3.pdf

/referências

Referências

Documentos relacionados

Deste modo, o Estado socialista real deixou de ser, como no capitalismo clássico, um complemento da superestrutura destinado a garantir a ordem jurídica, que expressa as relações

De fato, a aplicação das propriedades da regra variável aos estudos lingüísticos além da fonologia não constitui assunto tranqüilo, seja porque a variável passa a ser

 Recupera a entidade City com o id especificado na revisão informada, se a entidade não existir na revisão informada retorna null, se apenas algumas propriedades foram

Se nesse período crítico, ela encontra alguém que, ignorante e inescrupulosamente, lhe fornece exercícios respiratórios, e se ela segue as instruções fidedignamente na esperança

Processo de se examinar, em conjunto, os recursos disponíveis para verificar quais são as forças e as fraquezas da organização.

In-situ (sem escavação) SOLO TRATADO Ex-situ (com escavação) - Biológico - Físico / Químico - Térmico Resíduos não biodegradáveis Controle de Qualidade Biofi ltro Material

Remete para o conceito de que, caso o carbono seja inexistente na altura da produção do combustível, nomeadamente na fase da combustão, este não será envolvido na reação

o programa impõe 11 horas de meditação diária, com intervalos para as refeições, descanso e instruções a respeito da técnica, que são dadas em hindi ou inglês.. as