Auditoria com JPA, Hibernate e Spring Data JPA

Auditoria com JPA, Hibernate e Spring Data JPA

*1. Visão geral *

No contexto do ORM, auditoria de banco de dados significa rastrear e registrar eventos relacionados a entidades persistentes ou simplesmente versionamento de entidade. Inspirados nos gatilhos SQL, os eventos são operações de inserção, atualização e exclusão de entidades. Os benefícios da auditoria do banco de dados são análogos aos fornecidos pelo controle de versão de origem.

Demonstraremos três abordagens para introduzir a auditoria em um aplicativo. Primeiro, vamos implementá-lo usando o JPA padrão. A seguir, veremos duas extensões JPA que fornecem sua própria funcionalidade de auditoria: uma fornecida pelo Hibernate e outra pela Spring Data.

Aqui estão as entidades relacionadas à amostra, Bar e _Foo, _ que serão usadas neste exemplo:

link:/wp-content/uploads/2016/07/Screenshot_4.png [imagem:/wp-content/uploads/2016/07/Screenshot_4.png [imagem, largura = 419, altura = 131]]

===* 2. Auditoria com JPA *

A JPA não contém explicitamente uma API de auditoria, mas a funcionalidade pode ser alcançada usando eventos do ciclo de vida da entidade.

====* 2.1 _ @ PrePersist, _ _ @ PreUpdate_ e * _ @ PreRemove_

Na classe JPA Entity, um método pode ser especificado como um retorno de chamada que será chamado durante um evento específico do ciclo de vida da entidade. Como estamos interessados ​​em retornos de chamada executados antes das operações DML correspondentes, há anotações de retorno de chamada _ @ PrePersist_, _ @ PreUpdate_ e _ @ PreRemove_ disponíveis para nossos propósitos:

@Entity
public class Bar {

    @PrePersist
    public void onPrePersist() { ... }

    @PreUpdate
    public void onPreUpdate() { ... }

    @PreRemove
    public void onPreRemove() { ... }

}

Os métodos internos de retorno de chamada sempre devem retornar [.code] # void # e não aceitar argumentos. Eles podem ter qualquer nome e qualquer nível de acesso, mas não devem ser [.code] # static #.

Esteja ciente de que a anotação _ @ Version_ no JPA não está estritamente relacionada ao nosso tópico - tem a ver com bloqueio otimista mais do que com dados de auditoria.

2.2 Implementando os métodos de retorno de chamada

Há uma restrição significativa com essa abordagem. Conforme declarado na especificação JPA [.st] # 2 (JSR 317): #

_ Em geral, o método do ciclo de vida de um aplicativo portátil não deve invocar operações _EntityManager ou Query, acessar outras instâncias de entidade ou modificar relacionamentos no mesmo contexto de persistência. Um método de retorno de chamada do ciclo de vida pode modificar o estado de não-relacionamento da entidade na qual é chamado. __

Na ausência de uma estrutura de auditoria, devemos manter o esquema do banco de dados e o modelo de domínio manualmente. Para o nosso caso de uso simples, vamos adicionar duas novas propriedades à entidade, pois podemos gerenciar apenas o "estado de não relacionamento da entidade". Uma propriedade operation armazenará o nome de uma operação executada e uma propriedade timestamp é o carimbo de data e hora da operação:

@Entity
public class Bar {

   //...

    @Column(name = "operation")
    private String operation;

    @Column(name = "timestamp")
    private long timestamp;

   //...

   //standard setters and getters for the new properties

   //...

    @PrePersist
    public void onPrePersist() {
        audit("INSERT");
    }

    @PreUpdate
    public void onPreUpdate() {
        audit("UPDATE");
    }

    @PreRemove
    public void onPreRemove() {
        audit("DELETE");
    }

    private void audit(String operation) {
        setOperation(operation);
        setTimestamp((new Date()).getTime());
    }

}

Se você precisar adicionar essa auditoria a várias classes, poderá usar _ @ EntityListeners_ para centralizar o código. Por exemplo:

@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }
public class AuditListener {

    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyOperation(Object object) { ... }

}

*3. Envers de hibernação *

Com o Hibernate, poderíamos usar Interceptors e EventListeners, bem como gatilhos de banco de dados para realizar auditoria. Mas a estrutura ORM oferece o Envers, um módulo que implementa auditoria e controle de versão de classes persistentes.

====* 3.1 Introdução aos Envers *

Para configurar o Envers, você precisa adicionar o hibernate-envers JAR em seu caminho de classe:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>${hibernate.version}</version>
</dependency>

Em seguida, basta adicionar a anotação _ @ Audited_ em uma _ @ Entity_ (para auditar toda a entidade) ou em @ Columns específicas (se você precisar auditar apenas propriedades específicas):

@Entity
@Audited
public class Bar { ... }

Observe que Bar tem um relacionamento um-para-muitos com Foo. Nesse caso, também precisamos auditar o Foo adicionando _ @ Audited_ no Foo ou definir _ @ NotAudited_ na propriedade do relacionamento em Bar:

@OneToMany(mappedBy = "bar")
@NotAudited
private Set<Foo> fooSet;

====* 3.2 Criando tabelas de log de auditoria *

Existem várias maneiras de criar tabelas de auditoria:

  • defina hibernate.hbm2ddl.auto como create, create-drop ou update, para que os Envers possam criá-los automaticamente

  • use org.hibernate.tool.EnversSchemaGenerator para exportar programaticamente o esquema completo do banco de dados

  • use uma tarefa Ant para gerar instruções DDL apropriadas *use um plug-in Maven para gerar um esquema de banco de dados a partir de seus mapeamentos (como o Juplo) para exportar o esquema Envers (funciona com o Hibernate 4 e superior)

Seguiremos a primeira rota, pois é a mais direta, mas saiba que o uso de hibernate.hbm2ddl.auto não é seguro na produção.

No nosso caso, as tabelas bar_AUD e foo_AUD (se você definiu Foo como _ @ Audited_ também) devem ser geradas automaticamente. As tabelas de auditoria copiam todos os campos auditados da tabela da entidade com dois campos, REVTYPE (os valores são: "0" para adicionar, "1" para atualizar, "2" para remover uma entidade) e REV.

Além disso, uma tabela extra denominada REVINFO será gerada por padrão, inclui dois campos importantes, REV e REVTSTMP e registra o registro de data e hora de cada revisão. E como você pode imaginar, bar_AUD.REV e foo_AUD.REV são realmente chaves estrangeiras para REVINFO.REV.

====* 3.3 Configurando Envers *

Você pode configurar as propriedades do Envers como qualquer outra propriedade do Hibernate.

Por exemplo, vamos mudar o sufixo da tabela de auditoria (o padrão é "AUD_") para "AUDIT_LOG_". Aqui está como definir o valor da propriedade correspondente org.hibernate.envers.audit_table_suffix:

Properties hibernateProperties = new Properties();
hibernateProperties.setProperty(
  "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG");
sessionFactory.setHibernateProperties(hibernateProperties);

Uma lista completa de propriedades disponíveis pode ser encontrada na documentação da Envers.

====* 3.4 Acessando o histórico da entidade *

Você pode consultar dados históricos de maneira semelhante à consulta de dados por meio da API de critérios do Hibernate. O histórico de auditoria de uma entidade pode ser acessado usando a interface AuditReader, que pode ser obtida com um EntityManager ou Session aberto através do AuditReaderFactory:

AuditReader reader = AuditReaderFactory.get(session);

O Envers fornece AuditQueryCreator (retornado por AuditReader.createQuery () _) para criar consultas específicas da auditoria. A linha a seguir retornará todas as instâncias _Bar modificadas na revisão 2 (onde bar_AUDIT_LOG.REV = 2):

AuditQuery query = reader.createQuery()
  .forEntitiesAtRevision(Bar.class, 2)

Aqui está como consultar as revisões de Bar_s, ou seja, resultará na obtenção de uma lista de todas as instâncias _Bar em todos os seus estados que foram auditadas:

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

Se o segundo parâmetro for falso, o resultado será associado à tabela REVINFO, caso contrário, somente as instâncias da entidade serão retornadas. O último parâmetro especifica se as instâncias Bar excluídas serão retornadas.

Em seguida, você pode especificar restrições usando a classe de fábrica AuditEntity:

query.addOrder(AuditEntity.revisionNumber().desc());

===* 4. Dados da Primavera JPA *

O Spring Data JPA é uma estrutura que estende o JPA adicionando uma camada extra de abstração na parte superior do provedor JPA. Essa camada permite suporte à criação de repositórios JPA, estendendo as interfaces do repositório Spring JPA.

Para nossos propósitos, você pode estender _CrudRepository <T, ID estende Serializable> _, a interface para operações CRUD genéricas. Assim que você criar e injetar seu repositório em outro componente, o Spring Data fornecerá a implementação automaticamente e você estará pronto para adicionar a funcionalidade de auditoria.

====* 4.1 Ativando a auditoria JPA *

Para começar, queremos habilitar a auditoria via configuração de anotação. Para fazer isso, basta adicionar _ @ EnableJpaAuditing_ na sua classe _ @ Configuration_:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }

====* 4.2 Adicionando o ouvinte de retorno de chamada da entidade do Spring *

Como já sabemos, o JPA fornece a anotação _ @ EntityListeners_ para especificar as classes do ouvinte de retorno de chamada. O Spring Data fornece sua própria classe de ouvinte de entidade JPA: AuditingEntityListener. Então, vamos especificar o ouvinte para a entidade Bar:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }

Agora, as informações de auditoria serão capturadas pelo ouvinte ao persistir e atualizar a entidade Bar.

====* 4.3 Acompanhamento de datas criadas e da última modificação *

Em seguida, adicionaremos duas novas propriedades para armazenar as datas criadas e modificadas pela última vez em nossa entidade Bar. As propriedades são anotadas pelas anotações _ @ CreatedDate_ e _ @ LastModifiedDate_ de acordo e seus valores são definidos automaticamente:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {

   //...

    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    private long createdDate;

    @Column(name = "modified_date")
    @LastModifiedDate
    private long modifiedDate;

   //...

}

Geralmente, você moveria as propriedades para uma classe base (anotada por _ @ MappedSuperClass_) que seria estendida por todas as suas entidades auditadas. Em nosso exemplo, nós os adicionamos diretamente a Bar por uma questão de simplicidade.

====* 4.4 Auditando o autor das alterações com o Spring Security *

Se seu aplicativo usa o Spring Security, você não pode apenas rastrear quando as alterações foram feitas, mas também quem as fez:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {

   //...

    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;

   //...

}

As colunas anotadas com _ @ CreatedBy_ e _ @ LastModifiedBy_ são preenchidas com o nome do principal que criou ou modificou a entidade pela última vez. As informações são extraídas da instância SecurityContext‘s Authentication. Se você deseja personalizar os valores definidos para os campos anotados, é possível implementar a interface _AuditorAware <T> _:

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
       //your custom logic
    }

}

Para configurar o aplicativo para usar AuditorAwareImpl para procurar o principal atual, declare um bean do tipo AuditorAware inicializado com uma instância de AuditorAwareImpl e especifique o nome do bean como o valor do parâmetro [.st] #_ auditorAwareRef_ em _ @ EnableJpaAuditing _: #

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {

   //...

    @Bean
    AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }

   //...

}

===* 5. Conclusão*

Consideramos três abordagens para implementar a funcionalidade de auditoria:

  • A abordagem JPA pura é a mais básica e consiste em usar retornos de chamada do ciclo de vida. No entanto, você só pode modificar o estado de não relacionamento de uma entidade. Isso torna o retorno de chamada _ @ PreRemove_ inútil para nossos propósitos, pois todas as configurações que você fez no método serão excluídas e junto com a entidade.

  • O Envers é um módulo de auditoria maduro fornecido pelo Hibernate. É altamente configurável e carece das falhas da implementação pura da JPA. Assim, ele nos permite auditar a operação de exclusão, pois ela se registra em tabelas diferentes da tabela da entidade.

  • A abordagem Spring Data JPA abstrai o trabalho com retornos de chamada JPA e fornece anotações úteis para propriedades de auditoria. Também está pronto para integração com o Spring Security. A desvantagem é que ele herda as mesmas falhas da abordagem JPA, portanto, a operação de exclusão não pode ser auditada.

Os exemplos deste artigo estão disponíveis em um repositório do GitHub.