Spring3 min de leitura

Auditoria de entidades JPA com Embeddables e Entity Listeners

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.

&lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Embeddable</span>&lt;/span&gt;
&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;class&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;Audit&lt;/span&gt; {
    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Column(name = &amp;quot;created_on&amp;quot;)</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; LocalDateTime createdOn;

    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Column(name = &amp;quot;created_by&amp;quot;)</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; String createdBy;

    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Column(name = &amp;quot;updated_on&amp;quot;)</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; LocalDateTime updatedOn;

    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Column(name = &amp;quot;updated_by&amp;quot;)</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; String updatedBy;

    &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// Getters e setters omitidos para brevidade&lt;/span&gt;</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.

&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;interface&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;Auditable&lt;/span&gt; {
    Audit &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;getAudit&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;()&lt;/span&gt;;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">void</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;setAudit&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;(Audit audit)&lt;/span&gt;;
}

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).

&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;class&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;AuditListener&lt;/span&gt; {
    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@PrePersist</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">void</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;setCreatedOn&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;(Auditable auditable)&lt;/span&gt; {
        &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;Audit&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;audit&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; auditable.getAudit();
        &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; (audit == &lt;span class=<span class="hljs-string">&quot;hljs-literal&quot;</span>&gt;<span class="hljs-literal">null</span>&lt;/span&gt;) {
            audit = &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">new</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;Audit&lt;/span&gt;();
            auditable.setAudit(audit);
        }
        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get()); &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// Utilitário para obter usuário logado&lt;/span&gt;</span>
    }

    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@PreUpdate</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">void</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;setUpdatedOn&lt;/span&gt;&lt;span class=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;(Auditable auditable)&lt;/span&gt; {
        &lt;span class=<span class="hljs-string">&quot;hljs-type&quot;</span>&gt;Audit&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-variable&quot;</span>&gt;audit&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-operator&quot;</span>&gt;=&lt;/span&gt; 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:

&lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Entity(name = &amp;quot;Post&amp;quot;)</span>&lt;/span&gt;
&lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Table(name = &amp;quot;post&amp;quot;)</span>&lt;/span&gt;
&lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@EntityListeners(AuditListener.class)</span>&lt;/span&gt;
&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">public</span>&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;class&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;Post&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;implements&lt;/span&gt; &lt;span class=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;Auditable&lt;/span&gt; {
    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Id</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; Long id;

    &lt;span class=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;<span class="hljs-meta">@Embedded</span>&lt;/span&gt;
    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; Audit audit;

    &lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">private</span>&lt;/span&gt; String title;
    
    &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// Getters e setters&lt;/span&gt;</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">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">INSERT INTO</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> post (created_by, created_on, updated_by, updated_on, title, id)
<span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">VALUES</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> (<span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span>#x27;Joao<span class="hljs-operator">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</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">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">NULL</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">NULL</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span>#x27;Título do Post<span class="hljs-operator">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-number&quot;<span class="hljs-operator">&gt;</span><span class="hljs-number">1</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>)

Exemplo de UPDATE gerado automaticamente:

<span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">UPDATE</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> post 
<span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">SET</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> created_by <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span>#x27;Joao<span class="hljs-operator">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, created_on <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</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">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, 
    updated_by <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span>#x27;Maria<span class="hljs-operator">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, updated_on <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</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">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span>, 
    title <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-string&quot;<span class="hljs-operator">&gt;</span><span class="hljs-operator">&amp;</span>#x27;Título Atualizado<span class="hljs-operator">&amp;</span>#x27;<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> 
<span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-keyword&quot;<span class="hljs-operator">&gt;</span><span class="hljs-keyword">WHERE</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> id <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-operator&quot;<span class="hljs-operator">&gt;=</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span>span class<span class="hljs-operator">=</span>&quot;hljs-number&quot;<span class="hljs-operator">&gt;</span><span class="hljs-number">1</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">&gt;</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

← Voltar aos Artigos