diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index ca4eedcae71e..a758c20db67b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -270,15 +270,16 @@ Only used if the `ValidityAuditStrategy` is used, and `org.hibernate.envers.audi When set to `true`, the legacy mapping behavior is used such that the revision end timestamp is only maintained in the root entity audit table. When set to `false`, the revision end timestamp is maintained in both the root entity and joined subclass audit tables; allowing the potential to apply database partitioning to the joined subclass tables just like the root entity audit tables. +[[envers-config-native-id]] `*org.hibernate.envers.use_revision_entity_with_native_id*` (default: `true` ):: Boolean flag that determines the strategy of revision number generation. Default implementation of revision entity uses native identifier generator. + If the current database engine does not support identity columns, users are advised to set this property to false. + -In this case revision numbers are created by preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. -See: `org.hibernate.envers.DefaultRevisionEntity` and `org.hibernate.envers.enhanced.SequenceIdRevisionEntity`. +In this case revision numbers are created by a preconfigured `org.hibernate.id.enhanced.SequenceStyleGenerator`. +[[envers-config-track-entities]] `*org.hibernate.envers.track_entities_changed_in_revision*` (default: `false` ):: Should entity types, that have been modified during each revision, be tracked. The default implementation creates `REVCHANGES` table that stores entity names of modified persistent objects. @@ -484,18 +485,49 @@ Either a `long/Long` or `java.util.Date` value representing the instant at which When using a `java.util.Date`, instead of a `long/Long` for the revision timestamp, take care not to store it to a column data type which will lose precision. Envers handles this information as an entity. -By default it uses its own internal class to act as the entity, mapped to the `REVINFO` table. -You can, however, supply your own approach to collecting this information which might be useful to capture additional details such as who made a change + +[[envers-default-revision-entity]] +==== Default Revision Entity + +By default, Envers uses its own internal class to act as the entity, mapped to the `REVINFO` table. +The entity type that's used depends on a couple configuration properties: <> and <>. Here is a table showing the entity type used based on the configuration values: +[cols="1,1,1"] +|=== +| +| native-id `false` +| native-id `true` + +| track-entities `false` +| `org.hibernate.envers.DefaultRevisionEntity` +| `org.hibernate.envers.enhanced.SequenceIdRevisionEntity` + +| track-entities `true` +| `org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity` +| `org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity` +|=== + +[[envers-custom-revision-entity]] +==== Custom Revision Entity + +You can also supply your own approach to collecting this information which might be useful to capture additional details such as who made a change or the IP address from which the request came. There are two things you need to make this work: . First, you will need to tell Envers about the entity you wish to use. Your entity must use the `@org.hibernate.envers.RevisionEntity` annotation. It must define the two attributes described above annotated with `@org.hibernate.envers.RevisionNumber` and `@org.hibernate.envers.RevisionTimestamp`, respectively. -You can extend from `org.hibernate.envers.DefaultRevisionEntity`, if you wish, to inherit all these required behaviors. +You can extend from any of the revision mapped superclass types, if you wish, to inherit all these required behaviors: + + org.hibernate.envers.RevisionMapping + org.hibernate.envers.TrackingModifiedEntitiesRevisionMapping + org.hibernate.envers.enhanced.SequenceIdRevisionMapping + org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionMapping + + Simply add the custom revision entity as you do your normal entities and Envers will *find it*. + +To understand which mapping you should extend based on configuration see the <> paragraph. ++ NOTE: It is an error for there to be multiple entities marked as `@org.hibernate.envers.RevisionEntity`. . Second, you need to tell Envers how to create instances of your revision entity which is handled by the @@ -1003,7 +1035,7 @@ If true, the result of the query will be a list of entities (which changed at re If false, the result will be a list of three element arrays: * the first element will be the changed entity instance. -* the second will be an entity containing revision data (if no custom entity is used, this will be an instance of `DefaultRevisionEntity`). +* the second will be an entity containing revision data (if no custom entity is used, this will be an instance of the <>). * the third will be the type of the revision (one of the values of the `RevisionType` enumeration: `ADD`, `MOD`, `DEL`). `selectDeletedEntities`:: The second parameter specifies if revisions, @@ -1330,17 +1362,31 @@ Here is a simple example: [source,java] ---- AuditQuery query = getAuditReader().createQuery() - .forRevisionsOfEntity( DefaultRevisionEntity.class, true ) + .forRevisionsOfEntity( Customer.class, true ) .add( AuditEntity.revisionNumber().between( 1, 25 ) ); ---- -This query will return all revision information entities for revisions between 1 and 25 including those which are +This query will return all information for revisions between 1 and 25 including those which are related to deletions. If deletions are not of interest, you would pass `false` as the second argument. -Note that this query uses the `DefaultRevisionEntity` class type. The class provided will vary depending on the -configuration properties used to configure Envers or if you supply your own revision entity. Typically users who -will use this API will likely be providing a custom revision entity implementation to obtain custom information -being maintained per revision. +Note that this query produces `@RevisionEntity` instances. The obtained instance type will vary depending on the +configuration properties used to configure Envers, like showed in <>, +or if you supply your own revision entity. + +[[envers-querying-revision-info]] +=== Directly querying revision information + +You can also directly query all revision information available on the database by writing HQL or Criteria queries +which select from the revision entity used by your application. For example: + +[source,java] +---- +List resultList = session.createQuery( "from DefaultRevisionEntity where id = 1", DefaultRevisionEntity.class ).getResultList(); +---- + +This query will return all revision entity information for revision numbers equal to 1 (the first revision of each entity). +Often, users who will take advantage of this functionality will be providing a custom revision entity implementation to +obtain additional information being maintained per revision. [[envers-conditional-auditing]] === Conditional auditing diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index bf4dd8567ef1..6a90fe729356 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -364,6 +364,10 @@ private void bindBasicEntityValues( entityDescriptor.setEntityName( entitySource.getEntityNamingSource().getEntityName() ); entityDescriptor.setJpaEntityName( entitySource.getEntityNamingSource().getJpaEntityName() ); entityDescriptor.setClassName( entitySource.getEntityNamingSource().getClassName() ); + if ( entityDescriptor.getJpaEntityName() != null && entityDescriptor.getClassName() != null ) { + metadataBuildingContext.getMetadataCollector() + .addImport( entityDescriptor.getJpaEntityName(), entityDescriptor.getClassName() ); + } entityDescriptor.setDiscriminatorValue( entitySource.getDiscriminatorMatchValue() != null diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/DefaultRevisionEntity.java b/hibernate-envers/src/main/java/org/hibernate/envers/DefaultRevisionEntity.java index 8d5991e32446..2aefe1337c6a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/DefaultRevisionEntity.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/DefaultRevisionEntity.java @@ -4,76 +4,12 @@ */ package org.hibernate.envers; -import java.io.Serializable; -import java.util.Date; - -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.Transient; +import jakarta.persistence.Entity; /** * @author Adam Warski (adam at warski dot org) * @author Chris Cranford */ -@MappedSuperclass -public class DefaultRevisionEntity implements Serializable { - private static final long serialVersionUID = 8530213963961662300L; - - @Id - @GeneratedValue - @RevisionNumber - private int id; - - @RevisionTimestamp - private long timestamp; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Transient - public Date getRevisionDate() { - return new Date( timestamp ); - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !(o instanceof DefaultRevisionEntity) ) { - return false; - } - - final DefaultRevisionEntity that = (DefaultRevisionEntity) o; - return id == that.id - && timestamp == that.timestamp; - } - - @Override - public int hashCode() { - int result; - result = id; - result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); - return result; - } - - @Override - public String toString() { - return "DefaultRevisionEntity(id = " + id - + ", revisionDate = " + DateTimeFormatter.INSTANCE.format(getRevisionDate() ) + ")"; - } +@Entity +public final class DefaultRevisionEntity extends RevisionMapping { } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/DefaultTrackingModifiedEntitiesRevisionEntity.java b/hibernate-envers/src/main/java/org/hibernate/envers/DefaultTrackingModifiedEntitiesRevisionEntity.java index e5cd2a80d2be..0c4e6cd1ae49 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/DefaultTrackingModifiedEntitiesRevisionEntity.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/DefaultTrackingModifiedEntitiesRevisionEntity.java @@ -4,17 +4,7 @@ */ package org.hibernate.envers; -import java.util.HashSet; -import java.util.Set; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.MappedSuperclass; - -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchMode; +import jakarta.persistence.Entity; /** * Extension of standard {@link DefaultRevisionEntity} that allows tracking entity names changed in each revision. @@ -23,54 +13,6 @@ * * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ -@MappedSuperclass -public class DefaultTrackingModifiedEntitiesRevisionEntity extends DefaultRevisionEntity { - @ElementCollection(fetch = FetchType.EAGER) - @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV")) - @Column(name = "ENTITYNAME") - @Fetch(FetchMode.JOIN) - @ModifiedEntityNames - private Set modifiedEntityNames = new HashSet<>(); - - public Set getModifiedEntityNames() { - return modifiedEntityNames; - } - - public void setModifiedEntityNames(Set modifiedEntityNames) { - this.modifiedEntityNames = modifiedEntityNames; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !(o instanceof DefaultTrackingModifiedEntitiesRevisionEntity) ) { - return false; - } - if ( !super.equals( o ) ) { - return false; - } - - final DefaultTrackingModifiedEntitiesRevisionEntity that = (DefaultTrackingModifiedEntitiesRevisionEntity) o; - - if ( modifiedEntityNames != null ? !modifiedEntityNames.equals( that.modifiedEntityNames ) - : that.modifiedEntityNames != null ) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "DefaultTrackingModifiedEntitiesRevisionEntity(" + super.toString() + ", modifiedEntityNames = " + modifiedEntityNames + ")"; - } +@Entity +public final class DefaultTrackingModifiedEntitiesRevisionEntity extends TrackingModifiedEntitiesRevisionMapping { } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/RevisionMapping.java b/hibernate-envers/src/main/java/org/hibernate/envers/RevisionMapping.java new file mode 100644 index 000000000000..da1351d026e2 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/RevisionMapping.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.envers; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Transient; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author Adam Warski (adam at warski dot org) + * @author Chris Cranford + */ +@MappedSuperclass +public class RevisionMapping implements Serializable { + private static final long serialVersionUID = 8530213963961662300L; + + @Id + @GeneratedValue + @RevisionNumber + private int id; + + @RevisionTimestamp + private long timestamp; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Transient + public Date getRevisionDate() { + return new Date( timestamp ); + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !(o instanceof RevisionMapping) ) { + return false; + } + + final RevisionMapping that = (RevisionMapping) o; + return id == that.id + && timestamp == that.timestamp; + } + + @Override + public int hashCode() { + int result; + result = id; + result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); + return result; + } + + @Override + public String toString() { + return "DefaultRevisionEntity(id = " + id + + ", revisionDate = " + DateTimeFormatter.INSTANCE.format(getRevisionDate() ) + ")"; + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/TrackingModifiedEntitiesRevisionMapping.java b/hibernate-envers/src/main/java/org/hibernate/envers/TrackingModifiedEntitiesRevisionMapping.java new file mode 100644 index 000000000000..37c815974274 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/TrackingModifiedEntitiesRevisionMapping.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.envers; + +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; + +import java.util.HashSet; +import java.util.Set; + +/** + * Extension of standard {@link RevisionMapping} that allows tracking entity names changed in each revision. + * This revision entity is implicitly used when {@code org.hibernate.envers.track_entities_changed_in_revision} + * parameter is set to {@code true}. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +public class TrackingModifiedEntitiesRevisionMapping extends RevisionMapping { + @ElementCollection(fetch = FetchType.EAGER) + @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV")) + @Column(name = "ENTITYNAME") + @Fetch(FetchMode.JOIN) + @ModifiedEntityNames + private Set modifiedEntityNames = new HashSet<>(); + + public Set getModifiedEntityNames() { + return modifiedEntityNames; + } + + public void setModifiedEntityNames(Set modifiedEntityNames) { + this.modifiedEntityNames = modifiedEntityNames; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !(o instanceof TrackingModifiedEntitiesRevisionMapping) ) { + return false; + } + if ( !super.equals( o ) ) { + return false; + } + + final TrackingModifiedEntitiesRevisionMapping that = (TrackingModifiedEntitiesRevisionMapping) o; + + if ( modifiedEntityNames != null ? !modifiedEntityNames.equals( that.modifiedEntityNames ) + : that.modifiedEntityNames != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "DefaultTrackingModifiedEntitiesRevisionEntity(" + super.toString() + ", modifiedEntityNames = " + modifiedEntityNames + ")"; + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java index 5ab6c98102c4..7dfedb225046 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java @@ -166,7 +166,7 @@ public Attribute getRevisionInfoRelationMapping() { true, false, true, - revisionInfoEntityName + revisionInfoClass.getName() ); attribute.setOnDelete( configuration.isCascadeDeleteRevision() ? "cascade" : null ); @@ -190,7 +190,7 @@ private RootPersistentEntity generateDefaultRevisionInfoMapping(String revisionI RootPersistentEntity mapping = new RootPersistentEntity( new AuditTableData( null, null, configuration.getDefaultSchemaName(), configuration.getDefaultCatalogName() ), revisionInfoClass, - revisionInfoEntityName, + useDefaultRevisionInfoMapping ? null : revisionInfoEntityName, DEFAULT_REVISION_ENTITY_TABLE_NAME ); @@ -283,7 +283,6 @@ private class RevisionEntityResolver { public RevisionEntityResolver(InFlightMetadataCollector metadata) { this.metadata = metadata; - this.revisionInfoEntityName = getDefaultEntityName(); this.revisionInfoIdData = createPropertyData( "id", "field" ); this.revisionInfoTimestampData = createPropertyData( "timestamp", "field" ); this.modifiedEntityNamesData = createPropertyData( "modifiedEntityNames", "field" ); @@ -294,15 +293,6 @@ public RevisionEntityResolver(InFlightMetadataCollector metadata) { locateRevisionEntityMapping(); } - private String getDefaultEntityName() { - if ( configuration.isNativeIdEnabled() ) { - return DefaultRevisionEntity.class.getName(); - } - else { - return SequenceIdRevisionEntity.class.getName(); - } - } - private void locateRevisionEntityMapping() { for ( PersistentClass persistentClass : metadata.getEntityBindings() ) { // Only process POJO models, not dynamic models @@ -390,7 +380,6 @@ private void locateRevisionEntityMapping() { revisionInfoClass = configuration.isNativeIdEnabled() ? DefaultTrackingModifiedEntitiesRevisionEntity.class : SequenceIdTrackingModifiedEntitiesRevisionEntity.class; - revisionInfoEntityName = revisionInfoClass.getName(); } else { revisionInfoClass = configuration.isNativeIdEnabled() @@ -398,6 +387,9 @@ private void locateRevisionEntityMapping() { : SequenceIdRevisionEntity.class; } + // Use the simple name of default revision entities as entity name + revisionInfoEntityName = revisionInfoClass.getSimpleName(); + timestampValueResolver = createRevisionTimestampResolver( revisionInfoClass, revisionInfoTimestampData, diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AbstractMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AbstractMetadataGenerator.java index 2409a256b40b..3131dd2afb5d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AbstractMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AbstractMetadataGenerator.java @@ -109,7 +109,7 @@ protected void addAuditStrategyAdditionalColumnsToEntity(PersistentEntity entity entity, metadataBuildingContext.getConfiguration(), metadataBuildingContext.getConfiguration().getRevisionTypePropertyType(), - metadataBuildingContext.getConfiguration().getRevisionInfo().getRevisionInfoEntityName(), + metadataBuildingContext.getConfiguration().getRevisionInfo().getRevisionInfoClass().getName(), false ) ); @@ -123,7 +123,7 @@ protected void addAuditStrategyRevisionEndTimestampOnly(PersistentEntity entity) entity, metadataBuildingContext.getConfiguration(), metadataBuildingContext.getConfiguration().getRevisionTypePropertyType(), - metadataBuildingContext.getConfiguration().getRevisionInfo().getRevisionInfoEntityName(), + metadataBuildingContext.getConfiguration().getRevisionInfo().getRevisionInfoClass().getName(), true ) ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionEntity.java b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionEntity.java index a31dfdf76df2..f281a16dcfba 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionEntity.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionEntity.java @@ -4,92 +4,13 @@ */ package org.hibernate.envers.enhanced; -import java.io.Serializable; -import java.util.Date; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.Transient; - -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Parameter; -import org.hibernate.envers.DateTimeFormatter; -import org.hibernate.envers.RevisionNumber; -import org.hibernate.envers.RevisionTimestamp; +import jakarta.persistence.Entity; /** * @author Adam Warski (adam at warski dot org) * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) * @author Chris Cranford */ -@MappedSuperclass -public class SequenceIdRevisionEntity implements Serializable { - private static final long serialVersionUID = 4159156677698841902L; - - @Id - @GeneratedValue(generator = "RevisionNumberSequenceGenerator") - @GenericGenerator( - name = "RevisionNumberSequenceGenerator", - strategy = "org.hibernate.envers.enhanced.OrderedSequenceGenerator", - parameters = { - @Parameter(name = "table_name", value = "REVISION_GENERATOR"), - @Parameter(name = "sequence_name", value = "REVISION_GENERATOR"), - @Parameter(name = "initial_value", value = "1"), - @Parameter(name = "increment_size", value = "1") - } - ) - @RevisionNumber - private int id; - - @RevisionTimestamp - private long timestamp; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - @Transient - public Date getRevisionDate() { - return new Date( timestamp ); - } - - public long getTimestamp() { - return timestamp; - } - - @SuppressWarnings("unused") - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !(o instanceof SequenceIdRevisionEntity) ) { - return false; - } - - final SequenceIdRevisionEntity that = (SequenceIdRevisionEntity) o; - return id == that.id && timestamp == that.timestamp; - } - - @Override - public int hashCode() { - int result = id; - result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); - return result; - } - - @Override - public String toString() { - return "SequenceIdRevisionEntity(id = " + id - + ", revisionDate = " + DateTimeFormatter.INSTANCE.format(getRevisionDate() ) - + ")"; - } +@Entity +public final class SequenceIdRevisionEntity extends SequenceIdRevisionMapping { } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionMapping.java b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionMapping.java new file mode 100644 index 000000000000..ddbae7f57b25 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdRevisionMapping.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.envers.enhanced; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Transient; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.envers.DateTimeFormatter; +import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + * @author Chris Cranford + */ +@MappedSuperclass +public class SequenceIdRevisionMapping implements Serializable { + private static final long serialVersionUID = 4159156677698841902L; + + @Id + @GeneratedValue(generator = "RevisionNumberSequenceGenerator") + @GenericGenerator( + name = "RevisionNumberSequenceGenerator", + strategy = "org.hibernate.envers.enhanced.OrderedSequenceGenerator", + parameters = { + @Parameter(name = "table_name", value = "REVISION_GENERATOR"), + @Parameter(name = "sequence_name", value = "REVISION_GENERATOR"), + @Parameter(name = "initial_value", value = "1"), + @Parameter(name = "increment_size", value = "1") + } + ) + @RevisionNumber + private int id; + + @RevisionTimestamp + private long timestamp; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Transient + public Date getRevisionDate() { + return new Date( timestamp ); + } + + public long getTimestamp() { + return timestamp; + } + + @SuppressWarnings("unused") + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !(o instanceof SequenceIdRevisionMapping) ) { + return false; + } + + final SequenceIdRevisionMapping that = (SequenceIdRevisionMapping) o; + return id == that.id && timestamp == that.timestamp; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); + return result; + } + + @Override + public String toString() { + return "SequenceIdRevisionEntity(id = " + id + + ", revisionDate = " + DateTimeFormatter.INSTANCE.format(getRevisionDate() ) + + ")"; + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionEntity.java b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionEntity.java index 36df1e35ade1..21e3d8bfb555 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionEntity.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionEntity.java @@ -4,18 +4,7 @@ */ package org.hibernate.envers.enhanced; -import java.util.HashSet; -import java.util.Set; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.MappedSuperclass; - -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchMode; -import org.hibernate.envers.ModifiedEntityNames; +import jakarta.persistence.Entity; /** * Extension of standard {@link SequenceIdRevisionEntity} that allows tracking entity names changed in each revision. @@ -24,57 +13,6 @@ * * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ -@MappedSuperclass -public class SequenceIdTrackingModifiedEntitiesRevisionEntity extends SequenceIdRevisionEntity { - @ElementCollection(fetch = FetchType.EAGER) - @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV")) - @Column(name = "ENTITYNAME") - @Fetch(FetchMode.JOIN) - @ModifiedEntityNames - private Set modifiedEntityNames = new HashSet<>(); - - @SuppressWarnings("unused") - public Set getModifiedEntityNames() { - return modifiedEntityNames; - } - - @SuppressWarnings("unused") - public void setModifiedEntityNames(Set modifiedEntityNames) { - this.modifiedEntityNames = modifiedEntityNames; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !(o instanceof SequenceIdTrackingModifiedEntitiesRevisionEntity) ) { - return false; - } - if ( !super.equals( o ) ) { - return false; - } - - final SequenceIdTrackingModifiedEntitiesRevisionEntity that = (SequenceIdTrackingModifiedEntitiesRevisionEntity) o; - - if ( modifiedEntityNames == null ) { - return that.modifiedEntityNames == null; - } - else { - return modifiedEntityNames.equals( that.modifiedEntityNames ); - } - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "SequenceIdTrackingModifiedEntitiesRevisionEntity(" + super.toString() - + ", modifiedEntityNames = " + modifiedEntityNames + ")"; - } +@Entity +public final class SequenceIdTrackingModifiedEntitiesRevisionEntity extends SequenceIdTrackingModifiedEntitiesRevisionMapping { } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionMapping.java b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionMapping.java new file mode 100644 index 000000000000..dd4ff098b4b4 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/enhanced/SequenceIdTrackingModifiedEntitiesRevisionMapping.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.envers.enhanced; + +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.envers.ModifiedEntityNames; + +import java.util.HashSet; +import java.util.Set; + +/** + * Extension of standard {@link SequenceIdRevisionMapping} that allows tracking entity names changed in each revision. + * This revision entity is implicitly used when {@code org.hibernate.envers.track_entities_changed_in_revision} + * parameter is set to {@code true}. + * + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +public class SequenceIdTrackingModifiedEntitiesRevisionMapping extends SequenceIdRevisionMapping { + @ElementCollection(fetch = FetchType.EAGER) + @JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV")) + @Column(name = "ENTITYNAME") + @Fetch(FetchMode.JOIN) + @ModifiedEntityNames + private Set modifiedEntityNames = new HashSet<>(); + + @SuppressWarnings("unused") + public Set getModifiedEntityNames() { + return modifiedEntityNames; + } + + @SuppressWarnings("unused") + public void setModifiedEntityNames(Set modifiedEntityNames) { + this.modifiedEntityNames = modifiedEntityNames; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !(o instanceof SequenceIdTrackingModifiedEntitiesRevisionMapping) ) { + return false; + } + if ( !super.equals( o ) ) { + return false; + } + + final SequenceIdTrackingModifiedEntitiesRevisionMapping that = (SequenceIdTrackingModifiedEntitiesRevisionMapping) o; + + if ( modifiedEntityNames == null ) { + return that.modifiedEntityNames == null; + } + else { + return modifiedEntityNames.equals( that.modifiedEntityNames ); + } + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SequenceIdTrackingModifiedEntitiesRevisionEntity(" + super.toString() + + ", modifiedEntityNames = " + modifiedEntityNames + ")"; + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java index d4cf25475642..c938c1bafb63 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java @@ -547,7 +547,7 @@ private UpdateContext getUpdateContext( final EntityPersister rootEntity = getEntityPersister( entity.getRootEntityName(), session ); final EntityPersister auditEntity = getEntityPersister( auditEntityName, session ); final EntityPersister rootAuditEntity = getEntityPersister( auditEntity.getRootEntityName(), session ); - final EntityPersister revisionEntity = getEntityPersister( configuration.getRevisionInfo().getRevisionInfoEntityName(), session ); + final EntityPersister revisionEntity = getEntityPersister( configuration.getRevisionInfo().getRevisionInfoClass().getName(), session ); final Number revisionNumber = getRevisionNumber( configuration, revision ); diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/CustomRevisionEntityTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/CustomRevisionEntityTest.java index 03b0c1fb64ac..561a551a7c8f 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/CustomRevisionEntityTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/CustomRevisionEntityTest.java @@ -8,7 +8,7 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.envers.Audited; -import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.RevisionMapping; import org.hibernate.envers.RevisionEntity; import org.hibernate.envers.RevisionListener; @@ -124,7 +124,7 @@ public String get() { @Entity(name = "CustomRevisionEntity") @Table(name = "CUSTOM_REV_INFO") @RevisionEntity(CustomRevisionEntityListener.class) - public static class CustomRevisionEntity extends DefaultRevisionEntity { + public static class CustomRevisionEntity extends RevisionMapping { private String username; diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditDefaultTrackingTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditDefaultTrackingTest.java index f81a8d7d0bb9..1f856e7d1f5c 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditDefaultTrackingTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditDefaultTrackingTest.java @@ -19,7 +19,7 @@ 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.TrackingModifiedEntitiesRevisionMapping; import org.hibernate.envers.RevisionEntity; import org.hibernate.jpa.boot.spi.Bootstrap; @@ -198,7 +198,7 @@ public void setCreatedOn(Date createdOn) { @Table(name = "TRACKING_REV_INFO") @RevisionEntity public static class CustomTrackingRevisionEntity - extends DefaultTrackingModifiedEntitiesRevisionEntity { + extends TrackingModifiedEntitiesRevisionMapping { } //end::envers-tracking-modified-entities-revchanges-example[] diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditTest.java index cd6a778f6211..b43288e739e3 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/EntityTypeChangeAuditTest.java @@ -25,7 +25,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.envers.AuditReaderFactory; import org.hibernate.envers.Audited; -import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.RevisionMapping; import org.hibernate.envers.ModifiedEntityNames; import org.hibernate.envers.RevisionEntity; import org.hibernate.jpa.boot.spi.Bootstrap; @@ -238,7 +238,7 @@ public void setCreatedOn(Date createdOn) { @Entity(name = "CustomTrackingRevisionEntity") @Table(name = "TRACKING_REV_INFO") @RevisionEntity - public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity { + public static class CustomTrackingRevisionEntity extends RevisionMapping { @ElementCollection @JoinTable( diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/reventity/trackmodifiedentities/ExtendedRevisionEntity.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/reventity/trackmodifiedentities/ExtendedRevisionEntity.java index 582caaed9bd0..ffe4e352c761 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/reventity/trackmodifiedentities/ExtendedRevisionEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/entities/reventity/trackmodifiedentities/ExtendedRevisionEntity.java @@ -8,14 +8,14 @@ import jakarta.persistence.Entity; import org.hibernate.envers.RevisionEntity; -import org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity; +import org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionMapping; /** * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ @Entity @RevisionEntity(ExtendedRevisionListener.class) -public class ExtendedRevisionEntity extends SequenceIdTrackingModifiedEntitiesRevisionEntity { +public class ExtendedRevisionEntity extends SequenceIdTrackingModifiedEntitiesRevisionMapping { @Column(name = "USER_COMMENT") private String comment; diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java new file mode 100644 index 000000000000..e10e4a825354 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/metamodel/RevisionEntitiesMetamodelTest.java @@ -0,0 +1,108 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.metamodel; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.envers.Audited; +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metamodel.internal.MetadataContext; +import org.hibernate.testing.logger.LogInspectionHelper; +import org.hibernate.testing.logger.TriggerOnPrefixLogListener; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.jboss.logging.Logger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.lang.invoke.MethodHandles; +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@Jira( "https://hibernate.atlassian.net/browse/HHH-17612" ) +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) +public class RevisionEntitiesMetamodelTest { + private TriggerOnPrefixLogListener trigger; + + @BeforeAll + public void setUp() { + trigger = new TriggerOnPrefixLogListener( "HHH015007: Illegal argument on static metamodel field injection" ); + LogInspectionHelper.registerListener( + trigger, + Logger.getMessageLogger( + MethodHandles.lookup(), + CoreMessageLogger.class, + MetadataContext.class.getName() + ) + ); + } + + @Test + public void testDefaultRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( false, true )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testSequenceIdRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( false, false )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testDefaultTrackingModifiedEntitiesRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( true, true )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @Test + public void testSequenceIdTrackingModifiedEntitiesRevisionEntity() { + try (final SessionFactoryImplementor ignored = buildSessionFactory( true, false )) { + assertThat( trigger.wasTriggered() ).isFalse(); + } + } + + @SuppressWarnings( "resource" ) + private static SessionFactoryImplementor buildSessionFactory(boolean trackEntities, boolean nativeId) { + final StandardServiceRegistryBuilder registryBuilder = ServiceRegistryUtil.serviceRegistryBuilder(); + registryBuilder.applySetting( EnversSettings.TRACK_ENTITIES_CHANGED_IN_REVISION, trackEntities ); + registryBuilder.applySetting( EnversSettings.USE_REVISION_ENTITY_WITH_NATIVE_ID, nativeId ); + return new MetadataSources( registryBuilder.build() ) + .addAnnotatedClasses( Customer.class ) + .buildMetadata() + .buildSessionFactory() + .unwrap( SessionFactoryImplementor.class ); + } + + @Audited + @Entity( name = "Customer" ) + @SuppressWarnings( "unused" ) + public static class Customer { + @Id + private Long id; + + private String firstName; + + private String lastName; + + @Column( name = "created_on" ) + @CreationTimestamp + private Instant createdOn; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/RevisionEntityQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/RevisionEntityQueryTest.java new file mode 100644 index 000000000000..220edd9c6096 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/query/RevisionEntityQueryTest.java @@ -0,0 +1,144 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.envers.integration.query; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; +import org.hibernate.orm.test.envers.Priority; +import org.hibernate.orm.test.envers.entities.StrIntTestEntity; +import org.hibernate.orm.test.envers.entities.ids.EmbId; +import org.hibernate.orm.test.envers.entities.ids.EmbIdTestEntity; +import org.hibernate.orm.test.envers.entities.ids.MulId; +import org.hibernate.orm.test.envers.entities.ids.MulIdTestEntity; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +public class RevisionEntityQueryTest extends BaseEnversJPAFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {StrIntTestEntity.class, MulIdTestEntity.class, EmbIdTestEntity.class}; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + final EntityManager em = getEntityManager(); + em.getTransaction().begin(); + + StrIntTestEntity site1 = new StrIntTestEntity( "a", 10 ); + StrIntTestEntity site2 = new StrIntTestEntity( "a", 10 ); + StrIntTestEntity site3 = new StrIntTestEntity( "b", 5 ); + + em.persist( site1 ); + em.persist( site2 ); + em.persist( site3 ); + + final Integer id1 = site1.getId(); + final Integer id2 = site2.getId(); + final Integer id3 = site3.getId(); + + em.getTransaction().commit(); + + // Revision 2 + em.getTransaction().begin(); + + final MulId mulId1 = new MulId( 1, 2 ); + em.persist( new MulIdTestEntity( mulId1.getId1(), mulId1.getId2(), "data" ) ); + + final EmbId embId1 = new EmbId( 3, 4 ); + em.persist( new EmbIdTestEntity( embId1, "something" ) ); + + site1 = em.find( StrIntTestEntity.class, id1 ); + site2 = em.find( StrIntTestEntity.class, id2 ); + + site1.setStr1( "aBc" ); + site2.setNumber( 20 ); + + em.getTransaction().commit(); + + // Revision 3 + em.getTransaction().begin(); + + site3 = em.find( StrIntTestEntity.class, id3 ); + + site3.setStr1( "a" ); + + em.getTransaction().commit(); + + // Revision 4 + em.getTransaction().begin(); + + site1 = em.find( StrIntTestEntity.class, id1 ); + + em.remove( site1 ); + + em.getTransaction().commit(); + } + + @Test + public void testRevisionEntityHqlQuery() { + final EntityManager em = getEntityManager(); + em.getTransaction().begin(); + + final List resultList = em.createQuery( + "select e from SequenceIdRevisionEntity e", + SequenceIdRevisionEntity.class + ).getResultList(); + + assertThat( resultList ).hasSize( 4 ); + + assertThat( em.createQuery( + String.format( "select e from %s e", SequenceIdRevisionEntity.class.getName() ), + SequenceIdRevisionEntity.class + ).getResultList() ).containsAll( resultList ); + + em.getTransaction().commit(); + } + + @Test + public void testRevisionEntityCriteriaQuery() { + final EntityManager em = getEntityManager(); + em.getTransaction().begin(); + + final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + final CriteriaQuery query = criteriaBuilder.createQuery( Integer.class ); + final Root from = query.from( SequenceIdRevisionEntity.class ); + final List resultList = em.createQuery( query.select( from.get( "id" ) ) ).getResultList(); + + assertThat( resultList ).hasSize( 4 ).allSatisfy( Assertions::assertNotNull ); + + em.getTransaction().commit(); + } + + @Test + public void testQueryForRevisionsOfEntity() { + final EntityManager em = getEntityManager(); + em.getTransaction().begin(); + + //noinspection unchecked + final List resultList = getAuditReader().createQuery() + .forRevisionsOfEntity( StrIntTestEntity.class, true ) + .add( AuditEntity.id().eq( 1 ) ) + .add( AuditEntity.revisionNumber().between( 1, 3 ) ) + .getResultList(); + + assertThat( resultList ).hasSize( 2 ).allMatch( r -> r instanceof SequenceIdRevisionEntity ); + + em.getTransaction().commit(); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/InheritedRevEntity.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/InheritedRevEntity.java index 26f2319650bc..23c57d946f90 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/InheritedRevEntity.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/InheritedRevEntity.java @@ -7,12 +7,12 @@ import jakarta.persistence.Entity; import org.hibernate.envers.RevisionEntity; -import org.hibernate.envers.enhanced.SequenceIdRevisionEntity; +import org.hibernate.envers.enhanced.SequenceIdRevisionMapping; /** * @author Adam Warski (adam at warski dot org) */ @Entity @RevisionEntity -public class InheritedRevEntity extends SequenceIdRevisionEntity { +public class InheritedRevEntity extends SequenceIdRevisionMapping { } diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java index 07c4d1f89dd4..6aa24f12644e 100644 --- a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java @@ -34,8 +34,7 @@ protected void addConfigOptions(Map options) { @Override protected Class[] getAnnotatedClasses() { return new Class[] { - StrTestEntity.class, ListOwnedEntity.class, ListOwningEntity.class, - getRevisionEntityClass() + StrTestEntity.class, ListOwnedEntity.class, ListOwningEntity.class }; } diff --git a/migration-guide.adoc b/migration-guide.adoc index 71dd79038c6a..63cb4ec5acb4 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -122,6 +122,34 @@ Previous versions allowed some questionable (at best) attribute naming patterns. String isDefault(); ---- +[[envers-rev-types]] +== Hibernate Envers and custom revision entities + +Users that wanted to customize the `@RevisionEntity` used by Envers could do so by extending one on the four default revision entity types: + +[source] +---- +org.hibernate.envers.DefaultRevisionEntity +org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity +org.hibernate.envers.enhanced.SequenceIdRevisionEntity +org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity +---- + +These types are annotated with `@MappedSuperclass` to enable this custom extension. When no custom revision entity was specified, though, +the same class was mapped as an entity type by Envers internals. This caused problems when dealing with the domain metamodel and static +metamodel aspect of these types, so we chose to create *new separate classes* annotated `@MappedSuperclass` from which revision entities, +meaning the default ones as well as yours, *should extend from*. These types are (in the same order): + +[source] +---- +org.hibernate.envers.RevisionMapping +org.hibernate.envers.TrackingModifiedEntitiesRevisionMapping +org.hibernate.envers.enhanced.SequenceIdRevisionMapping +org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionMapping +---- + +Also, you can now write HQL queries using the simple class name of default revision entities to retrieve all revision information. +Find out more in link:{user-guide-url}#envers-querying-revision-info[this user guide chapter]. [[create-query]] == Queries with implicit `select` list and no explicit result type