Auditoria Automática no JPA: Guia Prático com @Embeddable e @EntityListeners
Muitas vezes, precisamos rastrear quem criou ou modificou um registro e quando isso ocorreu. Embora o uso de @MappedSuperclass seja comum, a utilização de tipos embutidos (@Embeddable) oferece uma alternativa elegante para reutilizar propriedades entre múltiplas entidades.
1. O Modelo de Dados de Auditoria (@Embeddable)
O primeiro passo é encapsular os campos de auditoria em uma classe anotada com @Embeddable. Isso permite que esses quatro campos (created_by, created_on, updated_by, updated_on) sejam tratados como uma única unidade reutilizável.
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Embeddable</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Audit</span> {
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Column(name = &quot;created_on&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> LocalDateTime createdOn;
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Column(name = &quot;created_by&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> String createdBy;
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Column(name = &quot;updated_on&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> LocalDateTime updatedOn;
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Column(name = &quot;updated_by&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> String updatedBy;
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Getters e setters omitidos para brevidade</span></span>
}
2. Definindo a Interface Auditable
Para que nosso "ouvinte" de eventos (Listener) consiga manipular os dados de auditoria de forma genérica, definimos uma interface que as entidades deverão implementar.
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>interface</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Auditable</span> {
Audit <span class=<span class="hljs-string">"hljs-title function_"</span>>getAudit</span><span class=<span class="hljs-string">"hljs-params"</span>>()</span>;
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>setAudit</span><span class=<span class="hljs-string">"hljs-params"</span>>(Audit audit)</span>;
}
3. O Ouvinte de Eventos (@EntityListeners)
O AuditListener é o coração da automação. Ele utiliza anotações de ciclo de vida do JPA para interceptar operações de banco de dados:
@PrePersist: Executado antes da inserção (INSERT).
@PreUpdate: Executado antes da atualização (UPDATE).
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>AuditListener</span> {
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@PrePersist</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>setCreatedOn</span><span class=<span class="hljs-string">"hljs-params"</span>>(Auditable auditable)</span> {
<span class=<span class="hljs-string">"hljs-type"</span>>Audit</span> <span class=<span class="hljs-string">"hljs-variable"</span>>audit</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> auditable.getAudit();
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">if</span></span> (audit == <span class=<span class="hljs-string">"hljs-literal"</span>><span class="hljs-literal">null</span></span>) {
audit = <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">new</span></span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Audit</span>();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
audit.setCreatedBy(LoggedUser.get()); <span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Utilitário para obter usuário logado</span></span>
}
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@PreUpdate</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">void</span></span> <span class=<span class="hljs-string">"hljs-title function_"</span>>setUpdatedOn</span><span class=<span class="hljs-string">"hljs-params"</span>>(Auditable auditable)</span> {
<span class=<span class="hljs-string">"hljs-type"</span>>Audit</span> <span class=<span class="hljs-string">"hljs-variable"</span>>audit</span> <span class=<span class="hljs-string">"hljs-operator"</span>>=</span> auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
audit.setUpdatedBy(LoggedUser.get());
}
}
4. Aplicando às Entidades
Agora, basta aplicar a anotação @EntityListeners e implementar a interface Auditable em suas entidades, como no exemplo da classe Post:
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Entity(name = &quot;Post&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Table(name = &quot;post&quot;)</span></span>
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@EntityListeners(AuditListener.class)</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">public</span></span> <span class=<span class="hljs-string">"hljs-keyword"</span>>class</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Post</span> <span class=<span class="hljs-string">"hljs-keyword"</span>>implements</span> <span class=<span class="hljs-string">"hljs-title class_"</span>>Auditable</span> {
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Id</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> Long id;
<span class=<span class="hljs-string">"hljs-meta"</span>><span class="hljs-meta">@Embedded</span></span>
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> Audit audit;
<span class=<span class="hljs-string">"hljs-keyword"</span>><span class="hljs-keyword">private</span></span> String title;
<span class=<span class="hljs-string">"hljs-comment"</span>><span class="hljs-comment">// Getters e setters</span></span>
}
5. Resultados na Prática
Ao persistir ou atualizar uma entidade, o Hibernate cuida automaticamente das colunas de auditoria.
Exemplo de INSERT gerado automaticamente:
<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">INSERT INTO</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> post (created_by, created_on, updated_by, updated_on, title, id)
<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">VALUES</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> (<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;Joao<span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;<span class="hljs-number">2026</span><span class="hljs-number">-04</span><span class="hljs-number">-20</span> <span class="hljs-number">11</span>:<span class="hljs-number">17</span>:<span class="hljs-number">40.552</span><span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">NULL</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">NULL</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;Título do Post<span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-number"<span class="hljs-operator">></span><span class="hljs-number">1</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>)
Exemplo de UPDATE gerado automaticamente:
<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">UPDATE</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> post
<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">SET</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> created_by <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;Joao<span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, created_on <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;<span class="hljs-number">2026</span><span class="hljs-number">-04</span><span class="hljs-number">-20</span> <span class="hljs-number">09</span>:<span class="hljs-number">17</span>:<span class="hljs-number">40.552</span><span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>,
updated_by <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;Maria<span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>, updated_on <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;<span class="hljs-number">2026</span><span class="hljs-number">-04</span><span class="hljs-number">-20</span> <span class="hljs-number">09</span>:<span class="hljs-number">17</span>:<span class="hljs-number">40.605</span><span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>,
title <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-string"<span class="hljs-operator">></span><span class="hljs-operator">&</span>#x27;Título Atualizado<span class="hljs-operator">&</span>#x27;<span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>
<span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-keyword"<span class="hljs-operator">></span><span class="hljs-keyword">WHERE</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> id <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-operator"<span class="hljs-operator">>=</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span> <span class="hljs-operator"><</span>span class<span class="hljs-operator">=</span>"hljs-number"<span class="hljs-operator">></span><span class="hljs-number">1</span><span class="hljs-operator"><</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>
Conclusão
Esta abordagem permite reutilizar tanto a estrutura de dados (via @Embeddable) quanto o comportamento (via @EntityListeners). É uma solução limpa que mantém suas entidades de negócio focadas, enquanto a infraestrutura de auditoria funciona silenciosamente nos bastidores.
Publicado por: Guilherme Gomes - 28/04/2026 20:44
Caramelo.dev