diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/spi/EnversPostUpdateEventListenerImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/spi/EnversPostUpdateEventListenerImpl.java index 4c01fa70991b..edbb37264224 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/spi/EnversPostUpdateEventListenerImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/spi/EnversPostUpdateEventListenerImpl.java @@ -6,6 +6,9 @@ */ package org.hibernate.envers.event.spi; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.internal.synchronization.AuditProcess; import org.hibernate.envers.internal.synchronization.work.AuditWorkUnit; @@ -73,12 +76,30 @@ private Object[] postUpdateDBState(PostUpdateEvent event) { final Object[] newDbState = event.getState().clone(); if ( event.getOldState() != null ) { final EntityPersister entityPersister = event.getPersister(); + final Object entity = event.getEntity(); + final BytecodeEnhancementMetadata instrumentationMetadata = entityPersister.getInstrumentationMetadata(); + final LazyAttributeLoadingInterceptor lazyAttributeLoadingInterceptor; + if ( instrumentationMetadata.isEnhancedForLazyLoading() ) { + lazyAttributeLoadingInterceptor = instrumentationMetadata.extractInterceptor( entity ); + } + else { + lazyAttributeLoadingInterceptor = null; + } for ( int i = 0; i < entityPersister.getPropertyNames().length; ++i ) { if ( !entityPersister.getPropertyUpdateability()[i] ) { // Assuming that PostUpdateEvent#getOldState() returns database state of the record before modification. // Otherwise, we would have to execute SQL query to be sure of @Column(updatable = false) column value. newDbState[i] = event.getOldState()[i]; } + // Properties that have not been initialized need to be fetched in order to bind their value in the + // AUDIT insert statement. + if ( newDbState[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { + assert lazyAttributeLoadingInterceptor != null : "Entity:`" + entityPersister.getEntityName() + "` with uninitialized property:` " + entityPersister.getPropertyNames()[i] + "` hasn't an associated LazyAttributeLoadingInterceptor"; + event.getOldState()[i] = newDbState[i] = lazyAttributeLoadingInterceptor.fetchAttribute( + entity, + entityPersister.getPropertyNames()[i] + ); + } } } return newDbState; diff --git a/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/LazyFieldsTest.java b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/LazyFieldsTest.java new file mode 100644 index 000000000000..df4efcb12421 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/orm/test/envers/integration/lazy/LazyFieldsTest.java @@ -0,0 +1,100 @@ +package org.hibernate.orm.test.envers.integration.lazy; + +import org.hibernate.annotations.LazyGroup; +import org.hibernate.envers.Audited; + +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; + +@JiraKey("") +@BytecodeEnhanced +@DomainModel( + annotatedClasses = { + LazyFieldsTest.TestEntity.class, + } +) +@SessionFactory +public class LazyFieldsTest { + + private static final Long ID = 1L; + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( ID, "test data", "lazyString", "group A" ); + session.persist( testEntity ); + } + ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, ID ); + testEntity.setData( "modified test data" ); + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + @Audited + @Basic(optional = false) + private String data; + + @Audited + @Basic(fetch = FetchType.LAZY) + private String lazyString; + + @Audited + @Basic(fetch = FetchType.LAZY) + @LazyGroup("a") + private String lazyStringGroupA; + + public TestEntity() { + } + + public TestEntity(Long id, String data, String lazyString, String anotherLazyString) { + this.id = id; + this.data = data; + this.lazyString = lazyString; + this.lazyStringGroupA = anotherLazyString; + } + + public Long getId() { + return id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getLazyString() { + return lazyString; + } + + public String getLazyStringGroupA() { + return lazyStringGroupA; + } + } + +}