diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java index c3cb4f3c6b..1a71e9fc95 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java @@ -427,7 +427,7 @@ public final class AuditMetadataGenerator { ExtendedPropertyMapper propertyMapper = null; String parentEntityName = null; EntityConfiguration entityCfg = new EntityConfiguration(entityName, pc.getClassName(), idMapper, propertyMapper, - parentEntityName); + parentEntityName, auditingData.isUsingModifiedFlag()); notAuditedEntitiesConfigurations.put(entityName, entityCfg); return; } @@ -501,7 +501,7 @@ public final class AuditMetadataGenerator { // Storing the generated configuration EntityConfiguration entityCfg = new EntityConfiguration(auditEntityName, pc.getClassName(), idMapper, - propertyMapper, parentEntityName); + propertyMapper, parentEntityName, auditingData.isUsingModifiedFlag()); entitiesConfigurations.put(pc.getEntityName(), entityCfg); } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java index 53b13f5010..d68ae33389 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/ClassAuditingData.java @@ -85,6 +85,18 @@ public class ClassAuditingData implements AuditedPropertiesHolder { public boolean isAudited() { return defaultAudited || properties.size() > 0; } + + /** + * @return {@code true} if any of the properties has modification flag enabled, {@code false} otherwise. + */ + public boolean isUsingModifiedFlag() { + for ( PropertyAuditingData prop : properties.values() ) { + if ( prop.isUsingModifiedFlag() ) { + return true; + } + } + return false; + } public boolean contains(String propertyName) { return properties.containsKey(propertyName); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java index a873531229..17d1cd4635 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/EntityConfiguration.java @@ -42,14 +42,16 @@ public class EntityConfiguration { // Maps from property name private Map relations; private String parentEntityName; + private boolean usingModifiedFlag; public EntityConfiguration(String versionsEntityName, String entityClassName, IdMappingData idMappingData, - ExtendedPropertyMapper propertyMapper, String parentEntityName) { + ExtendedPropertyMapper propertyMapper, String parentEntityName, boolean usingModifiedFlag) { this.versionsEntityName = versionsEntityName; this.entityClassName = entityClassName; this.idMappingData = idMappingData; this.propertyMapper = propertyMapper; this.parentEntityName = parentEntityName; + this.usingModifiedFlag = usingModifiedFlag; this.relations = new HashMap(); } @@ -122,5 +124,12 @@ public class EntityConfiguration { */ public String getEntityClassName() { return entityClassName; - } + } + + /** + * @return {@code true} if the entity or any of its properties use modification flag, {@code false} otherwise. + */ + public boolean isUsingModifiedFlag() { + return usingModifiedFlag; + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java index feaac520da..d584c614c8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/entities/mapper/PropertyMapper.java @@ -72,5 +72,4 @@ public interface PropertyMapper { void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map data, Object newObj, Object oldObj); void mapModifiedFlagsToMapForCollectionChange(String collectionPropertyName, Map data); - } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java index 927cd2fb0f..f5cf2affc5 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/work/ModWorkUnit.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.envers.synchronization.work; + import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -36,12 +37,19 @@ import org.hibernate.persister.entity.EntityPersister; */ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit { private final Map data; - private final boolean changes; + private final boolean changes; + + private final EntityPersister entityPersister; + private final Object[] oldState; + private final Object[] newState; public ModWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg, Serializable id, EntityPersister entityPersister, Object[] newState, Object[] oldState) { super(sessionImplementor, entityName, verCfg, id, RevisionType.MOD); + this.entityPersister = entityPersister; + this.oldState = oldState; + this.newState = newState; data = new HashMap(); changes = verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data, entityPersister.getPropertyNames(), newState, oldState); @@ -66,6 +74,14 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit } public AuditWorkUnit merge(ModWorkUnit second) { + if ( verCfg.getEntCfg().get( getEntityName() ).isUsingModifiedFlag() ) { + // In case of multiple subsequent flushes within single transaction, modification flags need to be + // recalculated against initial and final state of the given entity. + return new ModWorkUnit( + second.sessionImplementor, second.getEntityName(), second.verCfg, second.id, + second.entityPersister, second.newState, this.oldState + ); + } return second; } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java new file mode 100644 index 0000000000..b6d2630cad --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/HasChangedManualFlush.java @@ -0,0 +1,65 @@ +package org.hibernate.envers.test.integration.modifiedflags; + +import java.util.List; +import javax.persistence.EntityManager; + +import org.junit.Test; + +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.integration.basic.BasicTestEntity1; +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.envers.test.tools.TestTools.extractRevisionNumbers; +import static org.hibernate.envers.test.tools.TestTools.makeList; +import static org.junit.Assert.assertEquals; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-7918") +public class HasChangedManualFlush extends AbstractModifiedFlagsEntityTest { + private Integer id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { BasicTestEntity1.class }; + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 + em.getTransaction().begin(); + BasicTestEntity1 entity = new BasicTestEntity1( "str1", 1 ); + em.persist( entity ); + em.getTransaction().commit(); + + id = entity.getId(); + + // Revision 2 - both properties (str1 and long1) should be marked as modified. + em.getTransaction().begin(); + entity = em.find( BasicTestEntity1.class, entity.getId() ); + entity.setStr1( "str2" ); + entity = em.merge( entity ); + em.flush(); + entity.setLong1( 2 ); + entity = em.merge( entity ); + em.flush(); + em.getTransaction().commit(); + + em.close(); + } + + @Test + public void testHasChangedOnDoubleFlush() { + List list = queryForPropertyHasChanged( BasicTestEntity1.class, id, "str1" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + + list = queryForPropertyHasChanged( BasicTestEntity1.class, id, "long1" ); + assertEquals( 2, list.size() ); + assertEquals( makeList( 1, 2 ), extractRevisionNumbers( list ) ); + } +}