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