segunda-feira, março 12, 2012

Auditoria: Hibernate Envers

Olá seguidores e entusiastas do SerJava!!

Faz certo tempo que não os alimento de conteúdo, mas tenham certeza que não abandonei o blog nem o interesse em disseminar p conhecimento e, acima de tudo, propagar a tecnologia Java.

Desde que comecei a trabalhar no ecossistema Java, confesso, sempre tive uma queda pelas tecnologias relativas a ORM (Mapeamento Objeto-Relacional); gostos a parte, optei como artigo de retomada, abordar sobre o HibernateEnvers.

Comumente, desde pequenas a grandes aplicações, tem-se o desejo (necessidade realmente) de monitorar as ações do usuário frente ao sistema, de modo a prover controle sobre o uso da aplicação, bem como, resguardar a integridade das informações administradas pela mesma. Por certo, a maioria das aplicações desenvolvidas sobre a plataforma Java, fazem uso de frameworks ORM. Aproveitando os preceitos desta tecnologia, diga-se de passagem, já abordados em tópicos anteriores (mas ainda há mais conteúdos, aguardem!), o Hibernate dispões do subprojeto Envers.

O Hibernate Envers "provê o histórico de versionamento dos dados manipulados pela aplicação", ou seja, é possível fazer uso de objetos persistentes (entidades mapeadas para a persistência JPA) para auditar as modificações havidas num dado registro. O Envers, utiliza de artifícios de revisões, conceito similar ao subservion. Sua configuração é bastante simples, bastando a adição do jar correspondente ao classpath e a configuração do arquivo persistence.xml.

No intuito de ser objetivo, neste tópico vou abordar apenas os primeiros passos, novos posts trarão mais detalhes sobre o Envers.

Implementação

Aproveitando o conteúdo já trabalhado, a respeito de persistência nos posts anteriores, farei uso do projeto criado até então, onde foi realizado o mapeamento para a persistência de objetos representativos de um Produto (http://www.serjava.blogspot.com/2011/12/persistencia-jpa-dao-generico.html).

Como o projeto foi feito há um tempinho (rsrs) resolvi modernizar inserindo a última versão do Hibernate, a 4.0, disponível em: Hibernate Download, feito o download, observe que o diretório lib contém: envers, jpa,  required e optional. Para atualizar seu hibernate, delete todos os artefatos do seu projeto, exceto o driver de conexão com o banco (caso esteja utilizando o projeto iniciado anteriormente) e adicione os jars dos diretórios: envers, jpa e required no classpath da sua aplicação (o primeiro post sobre JPA mostra como fazer). Seu classpath, neste momento, deverá conter:


O próximo passo é adicionarmos a configuração do Envers, faremos a auditória, até então, apenas para a criação e atualização de dados, assim, seu arquivo persistence.xml, deverá ficar similar a este:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
 <persistence-unit name="PersistenciaPU"
  transaction-type="RESOURCE_LOCAL">

  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <class>br.com.serjava.persistencia.entity.Produto</class>

  <properties>
   <property name="hibernate.connection.username" value="postgres" />
   <property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
   <property name="hibernate.connection.password" value="123456" />
   <property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/PERSISTENCIA" />
   <property name="hibernate.show_sql" value="true" />
   
   <!-- Permite operacoes ddls pelo jpa -->
   <property name="hibernate.hbm2ddl.auto" value="update" />

   <!-- configuracao do Envers -->
   <property name="post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener, org.hibernate.envers.event.EnversListener" />
   <property name="post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener, org.hibernate.envers.event.EnversListener" />
  </properties>

 </persistence-unit>
</persistence>



O próximo passo, é marcarmos a entidade que deverá ser auditada, no caso Produto, apenas anotando-a com @Audited:
package br.com.serjava.persistencia.entity;
//import omitidos
@Entity
@Audited
@AuditTable(value="PRODUTO_AUDIT")
@Table(name="produto")
@SequenceGenerator(name="produto_id_produto_seq", sequenceName="produto_id_produto_seq", allocationSize=1)
public class Produto implements Serializable {
 @Id
 @GeneratedValue(generator="produto_id_produto_seq", strategy=GenerationType.SEQUENCE)
 @Column(name="id_produto")
 private Integer idProduto;
 
 @Column(name="nm_produto")
 private String nomeProduto;
 
 @Column(name="quantidade")
 private Integer quantidade;
 
 @Column(name="valor")
 private Double valor;

 //getters and setters
}

A anotação @Audited diz ao Envers que esta entidade deverá ser auditada, e @AuditTable, discrimina, explicitamente, o nome da tabela a ser gerada que deverá conter as revisions dos dados.

Feito isto, execute a classe main, já implementado do projeto anterior, em seguida, acesso o banco postgres, e verifique a criação das tabelas: produto_audit e revinfo:



Pronto! Sua entidade produto já está sendo auditada, a pártir de agora, nas operações de inserção e atualização. Faça mais testes, com alterações. Nos próximos posts, demonstrarei como as revisions são organizadas e, como recuperar as revisões de um registro.

Espero que tenham gostado!

9 comentários:

  1. Olá,

    uma duvida quando digito diretamente no banco de dados via front-end (workbench) não cria registro na tabela produto_audit porque ??? estou fazendo no banco mysql...

    ResponderExcluir
  2. Olá Paulo,

    esta auditoria via Envers é de domínio da aplicação, ou seja, ela se faz presente por todas as ações realizadas dentro da aplicação.

    Diretamente pelo banco não é possível mapear pelo envers, ai vc teria que pensar em usar triggers. Não vejo muita ncessidade, pois o usuário não deverá manipular nada diretamente ao banco, exceto se você possuir mais de uma aplicação manipulando o mesmo banco.

    Agradeço o interessse ;)
    Qualquer dúvida, pode postar!

    ResponderExcluir
  3. Fabio faz um post de como adicionar campos na rev info, tipo usuario logado, eu fiz o que eles falaram no site da envers so que a entidade nova que criei nao é ne chamada... complicado isso.. e to precisando urgente disso.

    ResponderExcluir
    Respostas
    1. Olá Adalberto, desculpe a demora, não tinha reparado no comment.

      Vou tentar priorizá-lo!

      Obrigado pela visita, espero que os conteúdos tenham sido do seu interesse.

      []s

      Excluir
  4. Já tentei de tudo e não funciona.
    Coloquei as versões iguais a daqui, o persistence.xml ta igual, anotações iguais

    e quando rodo a aplicação, ele cria as tabelas assim como esta aqui
    mais na hora que eu salvo ou faço qualquer outra operação, ele não audita nada =//////.... já tentei de tudo.... Algum help?

    ResponderExcluir
    Respostas
    1. Rafael, dá uma re-conferida naqueles events, no caso deste post temos 2 eventos, configurados no persistence.xml, que dizem qdo, de fato, a auditoria deve ser realizada. Verifique se o value delas condizem com o que vc está usando.

      Qualquer coisa, poste ai, na medida do possível dou um help.

      []s

      Excluir
  5. Boa tarde.

    Gostaria de saber se tem como eu mudar o banco..
    não queria que as tabelas de auditoria ficassem no mesmo banco do sistema..
    exemplo

    DB_SISTEMA
    DB_SISTEMA_AUD

    sacou ?

    Vlw

    ResponderExcluir
    Respostas
    1. Oi, vi teu comment e achei interessante... Dá pra fazer isso sim, mas em relação ao esquema de banco de dados que está usando... Você pode criar um esquema apenas para guardar suas tabelas e informá-lo na propriedade "org.hibernate.envers.default_schema" do persistence.xml e lá serão criadas as tabelas de auditoria...

      Dá uma olhada: http://docs.jboss.org/hibernate/core/4.1/devguide/en-US/html/ch15.html

      P.S: Ótimo post! o/

      Excluir
  6. Meu projeto me retorna pagina 404 e esse erro
    GRAVE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
    Poderia me ajudar?

    ResponderExcluir