Utilizando o Hibernate Envers com Spring MVC

Muitas pessoas utilizam em suas entidades a anotação @Audited mas nunca precisaram de fato utilizar o Hibernate ORM Envers e recuperar os dados das tabelas de auditoria. Na maioria dos casos, costumam utilizar o Envers só para deixar o registro no banco para futuras necessidades.

Eu também nunca havia precisado usar mas recentemente precisei e foi difícil encontrar um site que continha exemplos de como recuperar esses dados de auditoria/versionamento por meio de código. Neste tutorial vou mostrar a implementação que usei, de uma maneira fácil e rápida para recuperar e listar esses dados que o Envers gera.

Tenho aqui uma entidade chamada Cliente para utilizar no exemplo:

[markdown]

“`
@Entity
@Audited
public class Cliente extends AbstractEntity {

/**
*
*/
private static final long serialVersionUID = 2054949375369790564L;

@Column(name = “NOME”)
private String nome;

@Column(name = “ABREVIATURA”)
private String abreviatura;

@Column(name = “SITE”)
private String site;

@ManyToOne
@JoinColumn(name=”COMPANY_ID”, nullable = false)
private Company company;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = false, fetch = FetchType.LAZY, mappedBy = “cliente”)
private Set projetos;

}
“`

[/markdown]

Criei outra entidade chamada CustomRevision para mapear todos os dados que eu quero armazenar quando o Envers criar um revision. Como pode perceber, coloquei a anotação: @RevisionEntity(CustomRevisionListener.class) que vai definir esta minha entidade como a entidade que o Envers usará para armazenar o histórico das revisions. Criei uma classe chamada CustomRevisionListener que vai ser um “interceptor” da classe RevisionListener e vai implementar o comportamento customizado que preciso na hora da criação da revision. Percebe-se que estendo a classe DefaultRevisionEntity, cuja possuí dois atributos: Id e Timestamp.

[markdown]

“`
@Entity
@RevisionEntity(CustomRevisionListener.class)
public class CustomRevision extends DefaultRevisionEntity {

/**
*
*/
private static final long serialVersionUID = 1L;

@Column(name = “USUARIO_NOME”)
private String userName;

@Column(name = “USUARIO_ID”)
private Long userId;

@Column(name = “DATA_ALTERACAO”)
private LocalDateTime dataAlteracao;

}
“`

[/markdown]

Na classe CustomRevisionListener fiz override no método newRevision que recebe um Object, esse parametro é a entidade que está sendo auditada/versionada, que no nosso caso é a entidade Cliente. Esse método é chamado toda vez que o Envers vai criar uma nova revision, assim, podemos instanciar nossa classe CustomRevision e setar os atributos que queremos nela. No exemplo adicionei o Id e o Nome do usuário que está realizando a alteração, você pode adicionar o atributo que quiser. Os atributos que vem da DefaultRevisionEntity (Id e Timestamp), é preenchido automaticamente pelo Envers através das anotações que eles possuem: @RevisionNumber e @RevisionTimestamp.

[markdown]
“`
@Component
public class CustomRevisionListener implements RevisionListener {

@Override
public void newRevision(Object arg0) {

DefaultUser usuarioLogado = UserUtils.getUserLogged();
usuarioLogado = usuarioLogado != null ? usuarioLogado : new DefaultUser();

CustomRevision cr = new CustomRevision();
cr.setUserId(usuarioLogado.getId());
cr.setUserName(usuarioLogado.getName());
cr.setDataAlteracao(LocalDateTime.now());
}
}
“`
[/markdown]

Agora que fizemos a parte de customização do armazenamento das revisions, vamos ao método para buscar esses registros. Criei um controller chamado AuditoriaController onde vou implementar o método que vai recuperar todas as revisions da entidade Cliente e passar para uma DTO para retornar os dados que me importam.
Para recuperar utilizaremos a classe AuditReader criada a partir da classe AuditReaderFactory. Com a instancia dela, chamo o método createQuery() e falo que quero as revisions da entidade Cliente com o método forRevisionsOfEntity, passando a entidade, o parametro para retornar somente a entidade e o parametro para retornar os registros excluídos dessa minha entidade. Esse método vai me retornar uma instancia da classe AuditQuery.

[markdown]
“`
/**
*
* @author GSuaki
*
*/
@Transactional
@RestController
@RequestMapping(value = “/auditoria”)
public class AuditoriaController {

private static final String CLIENTE = “Cliente”;

@Autowired
private SessionFactory sessionFactory;

/**
* @return currentSession
*/
private Session getSession() {
return sessionFactory.getCurrentSession();
}

@SuppressWarnings({ “unchecked” })
public Set findAllRevisionsOfCliente() {

Set dtos = new HashSet();

AuditReader reader = AuditReaderFactory.get(getSession());

AuditQuery query = reader.createQuery().forRevisionsOfEntity(Cliente.class, false, true);

List result = query.getResultList();
}

}
“`
[/markdown]

A lista que a query retorna, é uma lista de Object[] e por isso faremos um foreach recuperando os Object[]. Dentro dessas instancias, o primeiro índice do vetor, é a entidade que você recuperou as revisios (Cliente), o hibernate não deixa ver os valores do vetor pois ele utiliza proxys. Faremos então um cast no primeiro índice e terá a entidade, o segundo índice, retorna CustomRevision que é a nossa entidade customizada do Envers e no terceiro índice, retorna o tipo da revision (RevisionType), que podem ser três: ADD, DEL e MOD. Com estes dados instanciamos a classe AuditoriaDTO e adiciono na lista que iremos retornar para o front-end.

[markdown]
“`
/**
*
* @author GSuaki
*
*/
@Transactional
@RestController
@RequestMapping(value = “/auditoria”)
public class AuditoriaController {

private static final String CLIENTE = “Cliente”;

@Autowired
private SessionFactory sessionFactory;

/**
* @return currentSession
*/
private Session getSession() {
return sessionFactory.getCurrentSession();
}

@SuppressWarnings({ “unchecked” })
public Set findAllRevisionsOfCliente() {

Set dtos = new HashSet();

AuditReader reader = AuditReaderFactory.get(getSession());

AuditQuery query = reader.createQuery().forRevisionsOfEntity(Cliente.class, false, true);

List result = query.getResultList();

for (Object[] o : result) {
Cliente cliente = (Cliente) o[0];
CustomRevision revision = (CustomRevision) o[1];
RevisionType revisionType = (RevisionType) o[2];

AuditoriaDTO dto = new AuditoriaDTO(cliente.getId(), revisionType, revision.getDataAlteracao(), CLIENTE, revision.getUserName(), revision.getUserId());

dtos.add(dto);
}

return dtos;
}

}
“`

[/markdown]

Espero que tenham gostado, disponibilizarei os códigos aqui git:blogspot-java. Quaisquer dúvidas, sugestões e/ou reclamações, comentem!

[markdown]

[/markdown]

Gabriel Suaki – Desenvolvedor de software na redspark.
GitHub: GSuaki
Twitter: @gsuaki

One Comment

  1. Eduardo Ximendes

    Bacana teu tutorial Gabriel, porém não encontrei o projeto no link do GitHub ;/

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>