Auditing met JPA, Hibernate en Spring Data JPA

1. Overzicht

In de context van ORM betekent database-auditing het bijhouden en loggen van gebeurtenissen met betrekking tot persistente entiteiten, of eenvoudigweg entiteitsversiebeheer. Geïnspireerd door SQL-triggers, zijn de gebeurtenissen invoegen, bijwerken en verwijderen van entiteiten. De voordelen van databasecontrole zijn analoog aan die van bronversiebeheer.

We zullen drie benaderingen demonstreren om auditing in een applicatie te introduceren. Ten eerste zullen we het implementeren met behulp van standaard JPA. Vervolgens zullen we kijken naar twee JPA-extensies die hun eigen auditfunctionaliteit bieden: een geleverd door Hibernate, een andere door Spring Data.

Hier zijn de voorbeeldgerelateerde entiteiten, Bar en Foo, die in dit voorbeeld zal worden gebruikt:

2. Auditing met JPA

JPA bevat niet expliciet een audit-API, maar de functionaliteit kan worden bereikt met behulp van levenscyclusgebeurtenissen van de entiteit.

2.1. @PrePersist,@PreUpdate en @PreRemove

In JPA Entiteit class, kan een methode worden gespecificeerd als een callback die wordt aangeroepen tijdens een bepaalde levenscyclusgebeurtenis van een entiteit. Omdat we geïnteresseerd zijn in callbacks die worden uitgevoerd vóór de bijbehorende DML-bewerkingen, zijn er @PrePersist, @PreUpdate en @PreRemove terugbelaankondigingen beschikbaar voor onze doeleinden:

@Entity public class Bar {@PrePersist public void onPrePersist () {...} @PreUpdate public void onPreUpdate () {...} @PreRemove public void onPreRemove () {...}}

Interne callback-methoden moeten altijd void retourneren en geen argumenten accepteren. Ze kunnen elke naam en elk toegangsniveau hebben, maar mogen niet statisch zijn.

Houd er rekening mee dat de @Versie annotatie in JPA is niet strikt gerelateerd aan ons onderwerp - het heeft meer te maken met optimistische vergrendeling dan met controlegegevens.

2.2. Implementatie van de callback-methoden

Er is echter een aanzienlijke beperking bij deze benadering. Zoals vermeld in JPA 2-specificatie (JSR 317):

Over het algemeen mag de levenscyclusmethode van een draagbare applicatie niet worden aangeroepen EntityManager of Vraag bewerkingen, toegang krijgen tot andere entiteitsinstances of relaties wijzigen binnen dezelfde persistentiecontext. Een callback-methode voor de levenscyclus kan de niet-relatiestatus wijzigen van de entiteit waarop deze wordt aangeroepen.

Als er geen controleframework is, moeten we het databaseschema en het domeinmodel handmatig onderhouden. Laten we voor ons eenvoudige gebruik twee nieuwe eigenschappen aan de entiteit toevoegen, aangezien we alleen de "niet-relatiestatus van de entiteit" kunnen beheren. Een operatie eigenschap slaat de naam op van een uitgevoerde bewerking en een tijdstempel eigenschap is voor het tijdstempel van de bewerking:

@ Entity public class Bar {// ... @Column (name = "operation") privé String-bewerking; @Column (name = "timestamp") privé lang tijdstempel; // ... // standaard setters en getters voor de nieuwe eigenschappen // ... @PrePersist public void onPrePersist () {audit ("INSERT"); } @PreUpdate public void onPreUpdate () {audit ("UPDATE"); } @PreRemove public void onPreRemove () {audit ("DELETE"); } private ongeldige audit (String operatie) {setOperation (operatie); setTimestamp ((nieuwe datum ()). getTime ()); }}

Als u dergelijke auditing aan meerdere klassen wilt toevoegen, kunt u @EntityListeners om de code te centraliseren. Bijvoorbeeld:

@EntityListeners (AuditListener.class) @Entity openbare klasse Bar {...}
openbare klasse AuditListener {@PrePersist @PreUpdate @PreRemove private void beforeAnyOperation (Object-object) {...}}

3. Slaapstand Envers

Met Hibernate konden we gebruik maken van Onderscheppers en EventListeners evenals database-triggers om auditing uit te voeren. Maar het ORM-framework biedt Envers, een module die auditing en versiebeheer van persistente klassen implementeert.

3.1. Ga aan de slag met Envers

Om Envers in te stellen, moet u het overwinteren JAR in je klassenpad:

 org.hibernate hibernate-envers $ {hibernate.version} 

Voeg dan gewoon het @Audited annotatie ofwel op een @Entiteit (om de hele entiteit te controleren) of op specifiek @Koloms (als u alleen specifieke eigenschappen moet controleren):

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

Let daar op Bar heeft een een-op-veel-relatie met Foo. In dit geval moeten we ofwel auditeren Foo ook door toe te voegen @Audited Aan Foo of set @NotAudited op het terrein van de relatie in Bar:

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

3.2. Controlelogboektabellen maken

Er zijn verschillende manieren om controletabellen te maken:

  • set slaapstand.hbm2ddl.auto naar creëren, create-drop of bijwerken, zodat Envers ze automatisch kan aanmaken
  • gebruik org.hibernate.tool.EnversSchemaGenerator om het volledige databaseschema programmatisch te exporteren
  • gebruik een Ant-taak om de juiste DDL-instructies te genereren
  • gebruik een Maven-plug-in voor het genereren van een databaseschema op basis van uw toewijzingen (zoals Juplo) om Envers-schema te exporteren (werkt met Hibernate 4 en hoger)

We gaan de eerste route, omdat deze de meest eenvoudige is, maar houd er rekening mee dat het gebruik van slaapstand.hbm2ddl.auto is niet veilig in productie.

In ons geval, bar_AUD en foo_AUD (als je hebt ingesteld Foo net zo @Audited ook) tabellen moeten automatisch worden gegenereerd. De audittabellen kopiëren alle gecontroleerde velden uit de tabel van de entiteit met twee velden, REVTYPE (waarden zijn: "0" voor toevoegen, "1" voor bijwerken, "2" voor het verwijderen van een entiteit) en REV.

Naast deze wordt een extra tafel genoemd REVINFO wordt standaard gegenereerd, het bevat twee belangrijke velden, REV en REVTSTMP en registreert het tijdstempel van elke revisie. En zoals je kunt raden, bar_AUD.REV en foo_AUD.REV zijn eigenlijk externe sleutels naar REVINFO.REV.

3.3. Envers configureren

U kunt Envers-eigenschappen configureren zoals elke andere Hibernate-eigenschap.

Laten we bijvoorbeeld het achtervoegsel van de controletabel wijzigen (dat standaard is ingesteld op '_AUD") naar "_AUDIT_LOG“. Hier ziet u hoe u de waarde van de bijbehorende eigenschap instelt org.hibernate.envers.audit_table_suffix:

Eigenschappen hibernateProperties = nieuwe eigenschappen (); hibernateProperties.setProperty ("org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG"); sessionFactory.setHibernateProperties (hibernateProperties);

Een volledige lijst met beschikbare eigendommen is te vinden in de Envers-documentatie.

3.4. Entiteitsgeschiedenis openen

U kunt zoeken naar historische gegevens op een manier die vergelijkbaar is met het opvragen van gegevens via de API voor slaapstandcriteria. De auditgeschiedenis van een entiteit is toegankelijk met de AuditReader interface, die kan worden verkregen met een open EntityManager of Sessie via de AuditReaderFactory:

AuditReader reader = AuditReaderFactory.get (sessie);

Envers biedt AuditQueryCreator (geretourneerd door AuditReader.createQuery ()) om auditspecifieke query's te maken. De volgende regel geeft alles terug Bar instanties gewijzigd bij revisie # 2 (waar bar_AUDIT_LOG.REV = 2):

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

Hier leest u hoe u kunt zoeken naar Bar'S revisies, d.w.z. het zal resulteren in een lijst van alle Bar gevallen in al hun staten die werden gecontroleerd:

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

Als de tweede parameter false is, wordt het resultaat samengevoegd met de REVINFO table, anders worden alleen entiteitsinstanties geretourneerd. De laatste parameter specificeert of verwijderd moet worden geretourneerd Bar gevallen.

Vervolgens kunt u beperkingen specificeren met behulp van de AuditEntity fabrieksklasse:

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

4. Spring Data JPA

Spring Data JPA is een raamwerk dat JPA uitbreidt door een extra abstractielaag toe te voegen bovenop de JPA-provider. Deze laag biedt ondersteuning voor het maken van JPA-repositories door Spring JPA-repository-interfaces uit te breiden.

Voor onze doeleinden kunt u verlengen CrudRepository, de interface voor generieke CRUD-bewerkingen. Zodra u uw repository heeft gemaakt en in een ander onderdeel heeft geïnjecteerd, zorgt Spring Data automatisch voor de implementatie en bent u klaar om controlefunctionaliteit toe te voegen.

4.1. JPA-controle inschakelen

Om te beginnen willen we auditing inschakelen via annotatieconfiguratie. Om dat te doen, voegt u gewoon toe @EnableJpaAuditing op je @Configuratie klasse:

@Configuration @EnableTransactionManagement @EnableJpaRepositories @EnableJpaAuditing openbare klasse PersistenceConfig {...}

4.2. De Entity Callback Listener van Spring toevoegen

Zoals we al weten, biedt JPA de @EntityListeners annotatie om callback listener-klassen op te geven. Spring Data biedt zijn eigen JPA-entiteit-listener-klasse: AuditingEntityListener. Dus laten we de luisteraar specificeren voor de Bar entiteit:

@Entity @EntityListeners (AuditingEntityListener.class) openbare klasse Bar {...}

Nu wordt controle-informatie vastgelegd door de luisteraar bij het behouden en bijwerken van het Bar entiteit.

4.3. Bijhouden van gemaakte en laatst gewijzigde datums

Vervolgens zullen we twee nieuwe eigenschappen toevoegen voor het opslaan van de gemaakte en laatst gewijzigde datums aan onze Bar entiteit. De eigenschappen zijn geannoteerd door de @CreatedDate en @LastModifiedDate annotaties dienovereenkomstig, en hun waarden worden automatisch ingesteld:

@Entity @EntityListeners (AuditingEntityListener.class) openbare klasse Bar {// ... @Column (name = "created_date", nullable = false, updatable = false) @CreatedDate privé lange createdDate; @Column (name = "modified_date") @LastModifiedDate privé lange modifiedDate; // ...}

Over het algemeen zou u de eigenschappen naar een basisklasse verplaatsen (geannoteerd door @BuienRadarNL) die door al uw gecontroleerde entiteiten zou worden uitgebreid. In ons voorbeeld voegen we ze rechtstreeks toe aan Bar voor de eenvoud.

4.4. Auditing van de auteur van wijzigingen met Spring Security

Als uw app Spring Security gebruikt, kunt u niet alleen bijhouden wanneer er wijzigingen zijn aangebracht, maar ook wie ze heeft aangebracht:

@Entity @EntityListeners (AuditingEntityListener.class) openbare klasse Bar {// ... @Column (name = "created_by") @CreatedBy private String createdBy; @Column (name = "modified_by") @LastModifiedBy private String modifiedBy; // ...}

De kolommen die zijn geannoteerd met @Gemaakt door en @BuienRadarNL worden gevuld met de naam van de opdrachtgever die de entiteit heeft gemaakt of het laatst heeft gewijzigd. De informatie wordt opgehaald uit SecurityContext‘S Authenticatie voorbeeld. Als u waarden wilt aanpassen die zijn ingesteld op de geannoteerde velden, kunt u AuditorAware koppel:

public class AuditorAwareImpl implementeert AuditorAware {@Override public String getCurrentAuditor () {// uw aangepaste logica}}

Om de app te configureren om te gebruiken AuditorAwareImpl om de huidige hoofdsom op te zoeken, geeft u een boon aan van AuditorAware type geïnitialiseerd met een exemplaar van AuditorAwareImpl en specificeer de naam van de boon als de auditorAwareRef parameter waarde in @EnableJpaAuditing:

@EnableJpaAuditing (auditorAwareRef = "auditorProvider") openbare klasse PersistenceConfig {// ... @Bean AuditorAware auditorProvider () {retourneer nieuwe AuditorAwareImpl (); } // ...}

5. Conclusie

We hebben drie benaderingen overwogen om controlefuncties te implementeren:

  • De pure JPA-benadering is de meest elementaire en bestaat uit het gebruik van callbacks voor de levenscyclus. U mag echter alleen de niet-relatiestatus van een entiteit wijzigen. Dit maakt het @PreRemove callback is nutteloos voor onze doeleinden, aangezien alle instellingen die u in de methode heeft gemaakt, samen met de entiteit zullen worden verwijderd.
  • Envers is een volwassen auditmodule die wordt aangeboden door Hibernate. Het is zeer configureerbaar en mist de gebreken van de pure JPA-implementatie. Het stelt ons dus in staat om de verwijderbewerking te controleren, aangezien het inlogt op andere tabellen dan de tabel van de entiteit.
  • De Spring Data JPA-benadering abstraheert het werken met JPA-callbacks en biedt handige annotaties voor het controleren van eigenschappen. Het is ook klaar voor integratie met Spring Security. Het nadeel is dat het dezelfde tekortkomingen van de PPV-aanpak erft, zodat de verwijderingsoperatie niet kan worden gecontroleerd.

De voorbeelden voor dit artikel zijn beschikbaar in een GitHub-opslagplaats.