Audit avec JPA, Hibernate et Spring Data JPA

Les données]

  • lien:/tag/hibernate/[hibernate]

  • lien:/tag/jpa/[JPA]

1. Vue d’ensemble

Dans le contexte de l’ORM, l’audit de base de données signifie le suivi et la journalisation des événements liés aux entités persistantes, ou simplement la gestion des versions des entités.

Inspirés par les déclencheurs SQL, les événements sont des opérations d’insertion, de mise à jour et de suppression d’entités. Les avantages de l’audit de base de données sont analogues à ceux fournis par le contrôle de version source.

Nous allons démontrer trois approches pour introduire l’audit dans une application. Tout d’abord, nous allons l’implémenter en utilisant la norme JPA. Nous examinerons ensuite deux extensions JPA offrant leurs propres fonctionnalités d’audit: l’une fournie par Hibernate, l’autre par Spring Data.

Voici les exemples d’entités associées, Bar et Foo, qui seront utilisés dans cet exemple:

2. Audit avec JPA

JPA ne contient pas explicitement d’API d’audit, mais la fonctionnalité peut être obtenue à l’aide d’événements de cycle de vie d’entité.

2.1. @ PrePersist, @ PreUpdate et __ @ PreRemove

__

Dans JPA Entity class, une méthode peut être spécifiée comme un rappel qui sera appelé lors d’un événement de cycle de vie d’entité particulier. Comme les rappels exécutés avant les opérations DML correspondantes nous intéressent, il existe des annotations @ PrePersist , @ PreUpdate et @ PreRemove disponibles pour nos besoins:

@Entity
public class Bar {

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

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

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

}

Les méthodes de rappel internes doivent toujours renvoyer et ne prendre aucun argument.

Ils peuvent avoir n’importe quel nom et n’importe quel niveau d’accès, mais ne devraient pas l’être.

Sachez que l’annotation @ Version dans JPA n’est pas strictement liée à notre sujet. Elle concerne davantage le verrouillage optimiste que les données d’audit.

2.2. Implémentation des méthodes de rappel

Il existe cependant une restriction importante avec cette approche. Comme indiqué dans l’APP

En général, la méthode du cycle de vie d’une application portable ne doit pas appeler EntityManager ou Query__ opérations, accéder à d’autres instances d’entité ou modifier des relations dans le même contexte de persistance.

Une méthode de rappel de cycle de vie peut modifier l’état de non-relation de l’entité sur laquelle elle est invoquée.

En l’absence d’une structure d’audit, nous devons gérer manuellement le schéma de base de données et le modèle de domaine. Pour notre cas d’utilisation simple, ajoutons deux nouvelles propriétés à l’entité, car nous ne pouvons gérer que «l’état de non-relation de l’entité». Une propriété operation stockera le nom d’une opération effectuée et une propriété timestamp correspond à l’horodatage de l’opération:

@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());
    }

}

Si vous devez ajouter cet audit à plusieurs classes, vous pouvez utiliser @ EntityListeners pour centraliser le code. Par exemple:

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

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

}

=== 3. Hibernate Envers

Avec Hibernate, nous pourrions utiliser Interceptors et EventListeners ainsi que des déclencheurs de base de données pour effectuer des audits. Mais le framework ORM offre Envers, un module implémentant l’audit et le contrôle de version des classes persistantes.

==== 3.1. Commencez avec Envers

Pour configurer Envers, vous devez ajouter le fichier JAR hibernate-envers dans votre chemin de classe:

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

Ajoutez simplement l’annotation @ Audited soit sur une @ Entity (pour auditer l’entité entière), soit sur un __ @ Colonne __s spécifique (si vous devez auditer des propriétés spécifiques uniquement):

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

Notez que Bar a une relation un-à-plusieurs avec Foo . Dans ce cas, nous devons également auditer Foo en ajoutant @ Audited sur Foo ou définir @ NotAudited sur la propriété de la relation dans Bar :

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

==== 3.2. Création de tables de journal d’audit

Il existe plusieurs façons de créer des tables d’audit:

  • définir hibernate.hbm2ddl.auto sur create , create-drop ou update ,

alors Envers peut les créer automatiquement ** utilisez o _rg.hibernate.tool.EnversSchemaGenerator _ pour exporter le

compléter le schéma de base de données par programme ** utiliser une tâche Ant pour générer les instructions DDL appropriées

  • utiliser un plugin Maven pour générer un schéma de base de données à partir de vos mappages

(comme Juplo) pour exporter le schéma Envers (fonctionne avec Hibernate 4 et versions ultérieures)

Nous allons passer par la première voie, car c’est la plus simple, mais sachez que l’utilisation de hibernate.hbm2ddl.auto n’est pas sans danger en production.

Dans notre cas, les tables bar AUD et foo AUD (si vous définissez Foo comme @ Audited également) doivent être générées automatiquement. Les tables d’audit copient tous les champs audités de la table de l’entité avec deux champs, REVTYPE (les valeurs sont: “0” pour l’ajout, “1” pour la mise à jour, “2” pour la suppression d’une entité) et REV .

En plus de cela, une table supplémentaire nommée REVINFO sera générée par défaut, elle comprend deux champs importants, REV et REVTSTMP , et enregistre l’horodatage de chaque révision. Et comme vous pouvez le deviner, bar AUD.REV et foo AUD.REV sont en réalité des clés étrangères de REVINFO.REV.

==== 3.3. Configuration de Envers

Vous pouvez configurer les propriétés Envers comme n’importe quelle autre propriété Hibernate.

Par exemple, modifions le suffixe de la table d’audit (par défaut « _AUD ») en « __AUDIT LOG ». Voici comment définir la valeur de la propriété correspondante org.hibernate.envers.audit table suffix_ :

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

Vous trouverez une liste complète des propriétés disponibles à l’adresse de la documentation Envers .

==== 3.4. Accéder à l’historique de l’entité

Vous pouvez interroger des données historiques de la même manière que vous interrogez des données via l’API de critères d’Hibernate. Vous pouvez accéder à l’historique d’audit d’une entité à l’aide de l’interface AuditReader , qui peut être obtenue avec un EntityManager ou Session ouvert à l’aide de AuditReaderFactory :

AuditReader reader = AuditReaderFactory.get(session);

Envers fournit AuditQueryCreator (renvoyé par AuditReader.createQuery () ) afin de créer des requêtes spécifiques à l’audit.

La ligne suivante retournera toutes les instances de Bar modifiées à la révision n ° 2 (où bar AUDIT LOG.REV = 2 ):

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

Voici comment interroger les révisions de Bar , c’est-à-dire que vous obtiendrez une liste de toutes les instances de Bar dans tous leurs états audités:

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

Si le deuxième paramètre est false, le résultat est joint à la table REVINFO . Sinon, seules les instances d’entité sont renvoyées. Le dernier paramètre spécifie s’il faut renvoyer les instances supprimées Bar .

Ensuite, vous pouvez spécifier des contraintes à l’aide de la classe de fabrique AuditEntity :

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

=== 4. Spring Data JPA

Spring Data JPA est un framework qui étend JPA en ajoutant une couche supplémentaire d’abstraction sur le dessus du fournisseur JPA. Cette couche permet de prendre en charge la création de référentiels JPA en étendant les interfaces de référentiel Spring JPA.

Pour nos besoins, vous pouvez étendre __CrudRepository <T, ID à Serializable>, l’interface pour les opérations CRUD génériques. Dès que vous avez créé et injecté votre référentiel dans un autre composant, Spring Data fournira automatiquement l’implémentation et vous êtes prêt à ajouter une fonctionnalité d’audit.

==== 4.1. Activation de l’audit JPA

Pour commencer, nous voulons activer l’audit via la configuration des annotations. Pour ce faire, ajoutez simplement @ EnableJpaAuditing à votre classe @ Configuration :

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

==== 4.2. Ajouter un écouteur de rappel d’entité de printemps

Comme nous le savons déjà, JPA fournit l’annotation @ EntityListeners pour spécifier les classes d’écouteur de rappel. Spring Data fournit sa propre classe d’écoute d’entités JPA: AuditingEntityListener . Spécifions donc l’auditeur pour l’entité Bar :

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

À présent, les informations d’audit seront capturées par l’auditeur lorsqu’il persiste et met à jour l’entité Bar .

==== 4.3. Suivi des dates de création et de dernière modification

Ensuite, nous allons ajouter deux nouvelles propriétés pour stocker les dates de création et de dernière modification dans notre entité Bar . Les propriétés sont annotées en conséquence par les annotations @ CreatedDate et @ LastModifiedDate , et leurs valeurs sont définies automatiquement:

@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;

   //...

}

En règle générale, vous déplaceriez les propriétés dans une classe de base (annotée par @ MappedSuperClass ) qui serait étendue à toutes vos entités auditées. Dans notre exemple, nous les ajoutons directement à Bar pour des raisons de simplicité.

==== 4.4. Audit de l’auteur des modifications avec Spring Security

Si votre application utilise Spring Security, vous pouvez non seulement savoir quand les modifications ont été apportées, mais aussi qui les a apportées:

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

   //...

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

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

   //...

}

Les colonnes annotées avec @ CreatedBy et @ LastModifiedBy sont renseignées avec le nom du principal qui a créé ou modifié en dernier lieu l’entité. Les informations sont extraites de l’instance SecurityContext Authentication . Si vous souhaitez personnaliser les valeurs définies pour les champs annotés, vous pouvez implémenter l’interface AuditorAware <T> :

public class AuditorAwareImpl implements AuditorAware<String> {

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

}

Afin de configurer l’application afin qu’elle utilise AuditorAwareImpl pour rechercher le principal actuel, déclarez un bean de type AuditorAware initialisé avec une instance de AuditorAwareImpl et indiquez le nom du bean comme

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

   //...

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

   //...

}

=== 5. Conclusion

Nous avons envisagé trois approches pour implémenter la fonctionnalité d’audit:

  • L’approche JPA pure est la plus fondamentale et consiste à utiliser

callbacks de cycle de vie. Cependant, vous êtes uniquement autorisé à modifier l’état de non-relation d’une entité. Cela rend le rappel @ PreRemove inutile pour nos besoins, car tous les paramètres que vous avez définis dans la méthode seront supprimés avec l’entité.

  • Envers est un module d’audit mature fourni par Hibernate. C’est hautement

configurable et manque des défauts de l’implémentation JPA pure. Ainsi, cela nous permet d’auditer l’opération de suppression car elle se connecte à des tables autres que la table de l’entité.

  • Les résumés d’approche Spring Data JPA utilisant les rappels JPA et

fournit des annotations pratiques pour l’audit des propriétés. Il est également prêt pour l’intégration à Spring Security. L’inconvénient est qu’elle hérite des mêmes défauts de l’approche JPA, de sorte que l’opération de suppression ne peut pas être auditée.

Les exemples de cet article sont disponibles dans a du référentiel GitHub .