From 28758f7d53f4a254a624ea12813b964f73161049 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 31 Jul 2017 12:29:20 +0300 Subject: [PATCH] HHH-11886 - Elaborate Envers documentation and switch to actual source code examples Extend the Revision log example with actual test cases and SQL statement snippets --- .../userguide/chapters/envers/Envers.adoc | 119 +++---- ...tities-revchanges-after-rename-example.sql | 9 + ...ityTypeChangeAuditDefaultTrackingTest.java | 8 +- .../envers/EntityTypeChangeAuditTest.java | 20 +- ...angeAuditTrackingRevisionListenerTest.java | 337 ++++++++++++++++++ 5 files changed, 423 insertions(+), 70 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 0910361807..66f5f796eb 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -529,90 +529,85 @@ include::{sourcedir}/EntityTypeChangeAuditDefaultTrackingTest.java[tags=envers-t include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-example] ---- +Considering we have a `Customer` entity illustrated by the following example: + +[[envers-tracking-modified-entities-revchanges-before-rename-example]] +.`Customer` entity before renaming +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-before-rename-example] +---- +==== + +If the `Customer` entity class name is changed to `ApplicationCustomer`, +Envers is going to insert a new record in the `REVCHANGES` table with the previous entity class name: + +[[envers-tracking-modified-entities-revchanges-after-rename-example]] +.`Customer` entity after renaming +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-after-rename-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/envers-tracking-modified-entities-revchanges-after-rename-example.sql[] +---- +==== + Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a specified revision by utilizing API described in <>. Users are also allowed to implement custom mechanism of tracking modified entity types. -In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. -`EntityTrackingRevisionListener` interface exposes one method that notifies whenever audited entity instance has been added, modified or removed within current revision boundaries. +In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener` +interface as the value of `@org.hibernate.envers.RevisionEntity` annotation. -.CustomEntityTrackingRevisionListener.java +`EntityTrackingRevisionListener` interface exposes one method that notifies whenever audited entity instance has been +added, modified or removed within current revision boundaries. + +[[envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example]] +.The `EntityTrackingRevisionListener` implementation ==== -[source,java] +[source, JAVA, indent=0] ---- -public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener { - - @Override - public void entityChanged( Class entityClass, String entityName, - Serializable entityId, RevisionType revisionType, - Object revisionEntity ) { - String type = entityClass.getName(); - ( ( CustomTrackingRevisionEntity ) revisionEntity ).addModifiedEntityType( type ); - } - - @Override - public void newRevision( Object revisionEntity ) { - } -} +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example] ---- ==== -.CustomTrackingRevisionEntity.java +The `CustomTrackingRevisionListener` adds the fully-qualified class name to the `modifiedEntityTypes` attribute of the `CustomTrackingRevisionEntity`. + +[[envers-tracking-modified-entities-revchanges-RevisionEntity-example]] +.The `RevisionEntity` using the custom `EntityTrackingRevisionListener` ==== -[source,java] +[source, JAVA, indent=0] ---- -@Entity -@RevisionEntity( CustomEntityTrackingRevisionListener.class ) -public class CustomTrackingRevisionEntity { - - @Id - @GeneratedValue - @RevisionNumber - private int customId; - - @RevisionTimestamp - private long customTimestamp; - - @OneToMany( mappedBy="revision", cascade={ CascadeType.PERSIST, CascadeType.REMOVE } ) - private Set modifiedEntityTypes = new HashSet(); - - public void addModifiedEntityType( String entityClassName ) { - modifiedEntityTypes.add( new ModifiedEntityTypeEntity( this, entityClassName ) ); - } - - ... -} +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-RevisionEntity-example] ---- ==== -.ModifiedEntityTypeEntity.java +The `CustomTrackingRevisionEntity` contains a `@OneToMany` list of `ModifiedTypeRevisionEntity` + +[[envers-tracking-modified-entities-revchanges-EntityType-example]] +.The `EntityType` encapsulatets the entity type name before a class name modification ==== -[source,java] +[source, JAVA, indent=0] ---- -@Entity -public class ModifiedEntityTypeEntity { - - @Id - @GeneratedValue - private Integer id; - - @ManyToOne - private CustomTrackingRevisionEntity revision; - - private String entityClassName; - - ... -} +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityType-example] ---- ==== -[source,java] ----- -CustomTrackingRevisionEntity revEntity = - getAuditReader().findRevision( CustomTrackingRevisionEntity.class, revisionNumber ); +Now, when fetching the `CustomTrackingRevisionEntity`, you cna get access to the previous entity class name. -Set modifiedEntityTypes = revEntity.getModifiedEntityTypes(); +[[envers-tracking-modified-entities-revchanges-query-example]] +.Getting the `EntityType` through the `CustomTrackingRevisionEntity` +==== +[source, JAVA, indent=0] ---- +include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-query-example] +---- +==== [[envers-tracking-properties-changes]] === Tracking entity changes at property level diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql new file mode 100644 index 0000000000..b66b68764d --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/extras/envers-tracking-modified-entities-revchanges-after-rename-example.sql @@ -0,0 +1,9 @@ +insert +into + REVCHANGES + (REV, ENTITYNAME) +values + (?, ?) + +-- binding parameter [1] as [INTEGER] - [1] +-- binding parameter [2] as [VARCHAR] - [org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer] diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java index 12691b3133..44a6643bf1 100644 --- a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java @@ -39,7 +39,7 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF protected Class[] getAnnotatedClasses() { return new Class[] { Customer.class, - AnnotatedTrackingRevisionEntity.class + CustomTrackingRevisionEntity.class }; } @@ -62,7 +62,7 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, Arrays.asList( ApplicationCustomer.class, - AnnotatedTrackingRevisionEntity.class + CustomTrackingRevisionEntity.class ) ); settings.put( @@ -191,10 +191,10 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF } //tag::envers-tracking-modified-entities-revchanges-example[] - @Entity(name = "AnnotatedTrackingRevisionEntityListener") + @Entity(name = "CustomTrackingRevisionEntity") @Table(name = "TRACKING_REV_INFO") @RevisionEntity - public static class AnnotatedTrackingRevisionEntity + public static class CustomTrackingRevisionEntity extends DefaultTrackingModifiedEntitiesRevisionEntity { } diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java index 1230e746e6..922e81b5ac 100644 --- a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java @@ -45,7 +45,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa protected Class[] getAnnotatedClasses() { return new Class[] { Customer.class, - AnnotatedTrackingRevisionEntity.class + CustomTrackingRevisionEntity.class }; } @@ -68,7 +68,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, Arrays.asList( ApplicationCustomer.class, - AnnotatedTrackingRevisionEntity.class + CustomTrackingRevisionEntity.class ) ); settings.put( @@ -98,6 +98,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa } } + //tag::envers-tracking-modified-entities-revchanges-before-rename-example[] @Audited @Entity(name = "Customer") public static class Customer { @@ -114,6 +115,9 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa @CreationTimestamp private Date createdOn; + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-before-rename-example[] + public Long getId() { return id; } @@ -145,8 +149,11 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa public void setCreatedOn(Date createdOn) { this.createdOn = createdOn; } + //tag::envers-tracking-modified-entities-revchanges-before-rename-example[] } + //end::envers-tracking-modified-entities-revchanges-before-rename-example[] + //tag::envers-tracking-modified-entities-revchanges-after-rename-example[] @Audited @Entity(name = "Customer") public static class ApplicationCustomer { @@ -163,6 +170,9 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa @CreationTimestamp private Date createdOn; + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-after-rename-example[] + public Long getId() { return id; } @@ -194,13 +204,15 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa public void setCreatedOn(Date createdOn) { this.createdOn = createdOn; } + //tag::envers-tracking-modified-entities-revchanges-after-rename-example[] } + //end::envers-tracking-modified-entities-revchanges-after-rename-example[] //tag::envers-tracking-modified-entities-revchanges-example[] - @Entity(name = "AnnotatedTrackingRevisionEntityListener") + @Entity(name = "CustomTrackingRevisionEntity") @Table(name = "TRACKING_REV_INFO") @RevisionEntity - public static class AnnotatedTrackingRevisionEntity extends DefaultRevisionEntity { + public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity { @ElementCollection @JoinTable( diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java new file mode 100644 index 0000000000..2ed026030d --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTrackingRevisionListenerTest.java @@ -0,0 +1,337 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.userguide.envers; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.Audited; +import org.hibernate.envers.EntityTrackingRevisionListener; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; +import org.hibernate.envers.RevisionType; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditTrackingRevisionListenerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + CustomTrackingRevisionEntity.class, + EntityType.class + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + Customer customer = new Customer(); + customer.setId( 1L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe" ); + + entityManager.persist( customer ); + } ); + + EntityManagerFactory entityManagerFactory = null; + try { + Map settings = buildSettings(); + settings.put( + org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, + Arrays.asList( + ApplicationCustomer.class, + CustomTrackingRevisionEntity.class, + EntityType.class + ) + ); + settings.put( + AvailableSettings.HBM2DDL_AUTO, + "update" + ); + entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( + new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ), + settings + ).build().unwrap( SessionFactoryImplementor.class ); + + final EntityManagerFactory emf = entityManagerFactory; + + doInJPA( () -> emf, entityManager -> { + ApplicationCustomer customer = new ApplicationCustomer(); + customer.setId( 2L ); + customer.setFirstName( "John" ); + customer.setLastName( "Doe Jr." ); + + entityManager.persist( customer ); + } ); + + doInJPA( () -> emf, entityManager -> { + //tag::envers-tracking-modified-entities-revchanges-query-example[] + AuditReader auditReader = AuditReaderFactory.get( entityManager ); + + List revisions = auditReader.getRevisions( + ApplicationCustomer.class, + 1L + ); + + CustomTrackingRevisionEntity revEntity = auditReader.findRevision( + CustomTrackingRevisionEntity.class, + revisions.get( 0 ) + ); + + Set modifiedEntityTypes = revEntity.getModifiedEntityTypes(); + assertEquals( 1, modifiedEntityTypes.size() ); + + EntityType entityType = modifiedEntityTypes.iterator().next(); + assertEquals( + Customer.class.getName(), + entityType.getEntityClassName() + ); + //end::envers-tracking-modified-entities-revchanges-query-example[] + } ); + } + finally { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + } + + @Audited + @Entity(name = "Customer") + public static class Customer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + @Audited + @Entity(name = "Customer") + public static class ApplicationCustomer { + + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Temporal( TemporalType.TIMESTAMP ) + @Column(name = "created_on") + @CreationTimestamp + private Date createdOn; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + } + + //tag::envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example[] + public static class CustomTrackingRevisionListener implements EntityTrackingRevisionListener { + + @Override + public void entityChanged(Class entityClass, + String entityName, + Serializable entityId, + RevisionType revisionType, + Object revisionEntity ) { + String type = entityClass.getName(); + ( (CustomTrackingRevisionEntity) revisionEntity ).addModifiedEntityType( type ); + } + + @Override + public void newRevision( Object revisionEntity ) { + } + } + //end::envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example[] + + //tag::envers-tracking-modified-entities-revchanges-RevisionEntity-example[] + @Entity(name = "CustomTrackingRevisionEntity") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity( CustomTrackingRevisionListener.class ) + public static class CustomTrackingRevisionEntity { + + @Id + @GeneratedValue + @RevisionNumber + private int customId; + + @RevisionTimestamp + private long customTimestamp; + + @OneToMany( + mappedBy="revision", + cascade={ + CascadeType.PERSIST, + CascadeType.REMOVE + } + ) + private Set modifiedEntityTypes = new HashSet<>(); + + public Set getModifiedEntityTypes() { + return modifiedEntityTypes; + } + + public void addModifiedEntityType(String entityClassName ) { + modifiedEntityTypes.add( new EntityType( this, entityClassName ) ); + } + } + //end::envers-tracking-modified-entities-revchanges-RevisionEntity-example[] + + //tag::envers-tracking-modified-entities-revchanges-EntityType-example[] + @Entity(name = "EntityType") + public static class EntityType { + + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private CustomTrackingRevisionEntity revision; + + private String entityClassName; + + private EntityType() { + } + + public EntityType(CustomTrackingRevisionEntity revision, String entityClassName) { + this.revision = revision; + this.entityClassName = entityClassName; + } + + //Getters and setters are omitted for brevity + //end::envers-tracking-modified-entities-revchanges-EntityType-example[] + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public CustomTrackingRevisionEntity getRevision() { + return revision; + } + + public void setRevision(CustomTrackingRevisionEntity revision) { + this.revision = revision; + } + + public String getEntityClassName() { + return entityClassName; + } + + public void setEntityClassName(String entityClassName) { + this.entityClassName = entityClassName; + } + //tag::envers-tracking-modified-entities-revchanges-EntityType-example[] + } + //end::envers-tracking-modified-entities-revchanges-EntityType-example[] +}