Description
I have an issue with Spring Data Envers when using a Custom RevisionEntity class for storing revision information in Hibernate Envers.
Here is my Custom RevisionEntity - works well with an H2 Database for testing purposes - up to Spring Boot 2.2.7 it worked flawlessly.
package <TBD>;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import javax.persistence.*;
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "audit_trail" /*, schema = "history"*/)
@RevisionEntity
@GenericGenerator(name = "audit_trail_sequence",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name",value = "audit_trail_sequence"),
@Parameter(name = "initial_value",value = "1"),
@Parameter(name = "increment_size",value = "1")
})
public class CustomRevisionEntity
{
// =========================== Class Variables ===========================
// ============================= Variables =============================
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "audit_trail_sequence")
@RevisionNumber
private Long id;
@RevisionTimestamp
private Long timestamp;
}
I'm using a custom revision entity to allow for more than int
limited number of revisions - hence I'm not using Hibernate's DefaultRevisionEntity
- an extension of this class would not allow for changing the @RevisionNumber
field type.
For Spring Boot 2.3.X (tested with 2.3.1 and 2.3.2):
Spring Data Envers' EnversRevisionRepositoryImpl
uses the AnnotationRevisionMetadata
class to extract and provide revision metadata for any revision loaded from the database. Through debugging I found out that, when aAnnotationRevisionMetadata
instance is constructed for a revision, the @RevisionNumber
and the @RevisionTimestamp
field values are available, however the static methods constructing the respective Lazy
suppliers will only provide appropriate Supplier
functions for this information.
Somehow in the timespan from construction of these Lazy
suppliers to actual usage of these suppliers, the information is "lost" along the way - currently I assume it has something to do with the fact, that my CustomRevisionEntity
is in fact a HibernateProxy
of the actual CustomRevisionEntity
instance.
Generally this seems to have something to do with the deferred loading that is happening through the respectively constructed Lazy
suppliers operating on these HibernateProxy
objects which are liable to "loose" some information in the previously described timespan (possibly different Hibernate Sessions for Entity and Revision Infomation loading ? - just a thought).
I've tried wrapping the respective EnversRevisionRepositoryImpl
calls into transactional business methods, but TransationContext
or not - the result remains unchanged - the revision number and timestamp are not available for use after the EnversRevisionRepositoryImpl
getRevision information methods return their respective result.
One possible solution that occured to me would be to call Hibernate.unproxy
on the entity
constructor parameter of the AnnotationRevisionMetadata
from EnversRevisionRepositoryImpl.createRevisionMetadata
.
RevisionMetadata<?> createRevisionMetadata() {
return metadata instanceof DefaultRevisionEntity //
? new DefaultRevisionMetadata((DefaultRevisionEntity) metadata, revisionType) //
: new AnnotationRevisionMetadata<>(Hibernate.unproxy(metadata), RevisionNumber.class, RevisionTimestamp.class, revisionType);
}
I've tested that idea - it works - though I'm unsure if there are any performance related side effects from that solution.
I'm also considering whether or not the DefaultRevisionEntity
provided by Envers could be subject to the same issue of being returned by Hibernate as a HibernateProxy
instance.
If there are any questions, please contact me through Github.
Thank you