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]
----
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 <<envers-tracking-modified-entities-queries>>.
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<ModifiedEntityTypeEntity> modifiedEntityTypes = new HashSet<ModifiedEntityTypeEntity>();
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<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]]
=== 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() {
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 {
}

View File

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

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[]
}