diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 91522e67cf..0910361807 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -505,7 +505,7 @@ The method accepts a `persist` parameter indicating whether the revision entity [[envers-tracking-modified-entities-revchanges]] === Tracking entity names modified during revisions -By default entity types that have been changed in each revision are not being tracked. +By default, entity types that have been changed in each revision are not being tracked. This implies the necessity to query all tables storing audited data in order to retrieve changes made during specified revision. Envers provides a simple mechanism that creates `REVCHANGES` table which stores entity names of modified persistent objects. Single record encapsulates the revision identifier (foreign key to `REVINFO` table) and a string value. @@ -516,33 +516,18 @@ Tracking of modified entity names can be enabled in three different ways: In this case `org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity` will be implicitly used as the revision log entity. . Create a custom revision entity that extends `org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity` class. + -[source,java] +[source, JAVA, indent=0] ---- -@RevisionEntity -public class ExtendedRevisionEntity extends DefaultTrackingModifiedEntitiesRevisionEntity { - ... -} +include::{sourcedir}/EntityTypeChangeAuditDefaultTrackingTest.java[tags=envers-tracking-modified-entities-revchanges-example] ---- + . Mark an appropriate field of a custom revision entity with `@org.hibernate.envers.ModifiedEntityNames` annotation. The property is required to be of `Set` type. + -[source,java] +[source, JAVA, indent=0] ---- -@RevisionEntity -public class AnnotatedTrackingRevisionEntity { - ... - - @ElementCollection - @JoinTable( name = "REVCHANGES", joinColumns = @JoinColumn( name = "REV" ) ) - @Column( name = "ENTITYNAME" ) - @ModifiedEntityNames - private Set modifiedEntityNames; - - ... -} +include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-example] ---- -+ Users, that have chosen one of the approaches listed above, can retrieve all entities modified in a specified revision by utilizing API described in <>. diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java new file mode 100644 index 0000000000..12691b3133 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditDefaultTrackingTest.java @@ -0,0 +1,202 @@ +/* + * 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.util.Arrays; +import java.util.Date; +import java.util.Map; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +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.Audited; +import org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity; +import org.hibernate.envers.RevisionEntity; +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; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + AnnotatedTrackingRevisionEntity.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, + AnnotatedTrackingRevisionEntity.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 ); + } ); + } + 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-example[] + @Entity(name = "AnnotatedTrackingRevisionEntityListener") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity + public static class AnnotatedTrackingRevisionEntity + extends DefaultTrackingModifiedEntitiesRevisionEntity { + + } + //end::envers-tracking-modified-entities-revchanges-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java new file mode 100644 index 0000000000..1230e746e6 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/envers/EntityTypeChangeAuditTest.java @@ -0,0 +1,219 @@ +/* + * 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.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +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.Audited; +import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.ModifiedEntityNames; +import org.hibernate.envers.RevisionEntity; +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; + +/** + * @author Vlad Mihalcea + */ +public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Customer.class, + AnnotatedTrackingRevisionEntity.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, + AnnotatedTrackingRevisionEntity.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 ); + } ); + } + 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-example[] + @Entity(name = "AnnotatedTrackingRevisionEntityListener") + @Table(name = "TRACKING_REV_INFO") + @RevisionEntity + public static class AnnotatedTrackingRevisionEntity extends DefaultRevisionEntity { + + @ElementCollection + @JoinTable( + name = "REVCHANGES", + joinColumns = @JoinColumn( name = "REV" ) + ) + @Column( name = "ENTITYNAME" ) + @ModifiedEntityNames + private Set modifiedEntityNames = new HashSet<>(); + + public Set getModifiedEntityNames() { + return modifiedEntityNames; + } + } + //end::envers-tracking-modified-entities-revchanges-example[] +}