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
This commit is contained in:
Vlad Mihalcea 2017-07-31 12:29:20 +03:00
parent e56ecc24ff
commit 28758f7d53
5 changed files with 423 additions and 70 deletions

View File

@ -529,90 +529,85 @@ include::{sourcedir}/EntityTypeChangeAuditDefaultTrackingTest.java[tags=envers-t
include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-revchanges-example] 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, Users, that have chosen one of the approaches listed above,
can retrieve all entities modified in a specified revision by utilizing API described in <<envers-tracking-modified-entities-queries>>. can retrieve all entities modified in a specified revision by utilizing API described in <<envers-tracking-modified-entities-queries>>.
Users are also allowed to implement custom mechanism of tracking modified entity types. 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. In this case, they shall pass their own implementation of `org.hibernate.envers.EntityTrackingRevisionListener`
`EntityTrackingRevisionListener` interface exposes one method that notifies whenever audited entity instance has been added, modified or removed within current revision boundaries. 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 { include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityTrackingRevisionListener-example]
@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 ) {
}
}
---- ----
==== ====
.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 include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-RevisionEntity-example]
@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<ModifiedEntityTypeEntity> modifiedEntityTypes = new HashSet<ModifiedEntityTypeEntity>();
public void addModifiedEntityType( String entityClassName ) {
modifiedEntityTypes.add( new ModifiedEntityTypeEntity( this, entityClassName ) );
}
...
}
---- ----
==== ====
.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 include::{sourcedir}/EntityTypeChangeAuditTrackingRevisionListenerTest.java[tags=envers-tracking-modified-entities-revchanges-EntityType-example]
public class ModifiedEntityTypeEntity {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityClassName;
...
}
---- ----
==== ====
[source,java] Now, when fetching the `CustomTrackingRevisionEntity`, you cna get access to the previous entity class name.
----
CustomTrackingRevisionEntity revEntity =
getAuditReader().findRevision( CustomTrackingRevisionEntity.class, revisionNumber );
Set<ModifiedEntityTypeEntity> 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]] [[envers-tracking-properties-changes]]
=== Tracking entity changes at property level === Tracking entity changes at property level

View File

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

View File

@ -39,7 +39,7 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { return new Class<?>[] {
Customer.class, Customer.class,
AnnotatedTrackingRevisionEntity.class CustomTrackingRevisionEntity.class
}; };
} }
@ -62,7 +62,7 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF
org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, org.hibernate.jpa.AvailableSettings.LOADED_CLASSES,
Arrays.asList( Arrays.asList(
ApplicationCustomer.class, ApplicationCustomer.class,
AnnotatedTrackingRevisionEntity.class CustomTrackingRevisionEntity.class
) )
); );
settings.put( settings.put(
@ -191,10 +191,10 @@ public class EntityTypeChangeAuditDefaultTrackingTest extends BaseEntityManagerF
} }
//tag::envers-tracking-modified-entities-revchanges-example[] //tag::envers-tracking-modified-entities-revchanges-example[]
@Entity(name = "AnnotatedTrackingRevisionEntityListener") @Entity(name = "CustomTrackingRevisionEntity")
@Table(name = "TRACKING_REV_INFO") @Table(name = "TRACKING_REV_INFO")
@RevisionEntity @RevisionEntity
public static class AnnotatedTrackingRevisionEntity public static class CustomTrackingRevisionEntity
extends DefaultTrackingModifiedEntitiesRevisionEntity { extends DefaultTrackingModifiedEntitiesRevisionEntity {
} }

View File

@ -45,7 +45,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { return new Class<?>[] {
Customer.class, Customer.class,
AnnotatedTrackingRevisionEntity.class CustomTrackingRevisionEntity.class
}; };
} }
@ -68,7 +68,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, org.hibernate.jpa.AvailableSettings.LOADED_CLASSES,
Arrays.asList( Arrays.asList(
ApplicationCustomer.class, ApplicationCustomer.class,
AnnotatedTrackingRevisionEntity.class CustomTrackingRevisionEntity.class
) )
); );
settings.put( settings.put(
@ -98,6 +98,7 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
} }
} }
//tag::envers-tracking-modified-entities-revchanges-before-rename-example[]
@Audited @Audited
@Entity(name = "Customer") @Entity(name = "Customer")
public static class Customer { public static class Customer {
@ -114,6 +115,9 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
@CreationTimestamp @CreationTimestamp
private Date createdOn; private Date createdOn;
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-before-rename-example[]
public Long getId() { public Long getId() {
return id; return id;
} }
@ -145,8 +149,11 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
public void setCreatedOn(Date createdOn) { public void setCreatedOn(Date createdOn) {
this.createdOn = 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 @Audited
@Entity(name = "Customer") @Entity(name = "Customer")
public static class ApplicationCustomer { public static class ApplicationCustomer {
@ -163,6 +170,9 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
@CreationTimestamp @CreationTimestamp
private Date createdOn; private Date createdOn;
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-after-rename-example[]
public Long getId() { public Long getId() {
return id; return id;
} }
@ -194,13 +204,15 @@ public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCa
public void setCreatedOn(Date createdOn) { public void setCreatedOn(Date createdOn) {
this.createdOn = 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[] //tag::envers-tracking-modified-entities-revchanges-example[]
@Entity(name = "AnnotatedTrackingRevisionEntityListener") @Entity(name = "CustomTrackingRevisionEntity")
@Table(name = "TRACKING_REV_INFO") @Table(name = "TRACKING_REV_INFO")
@RevisionEntity @RevisionEntity
public static class AnnotatedTrackingRevisionEntity extends DefaultRevisionEntity { public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity {
@ElementCollection @ElementCollection
@JoinTable( @JoinTable(

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Number> revisions = auditReader.getRevisions(
ApplicationCustomer.class,
1L
);
CustomTrackingRevisionEntity revEntity = auditReader.findRevision(
CustomTrackingRevisionEntity.class,
revisions.get( 0 )
);
Set<EntityType> 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<EntityType> modifiedEntityTypes = new HashSet<>();
public Set<EntityType> 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[]
}