HHH-7940 - Fix NullPointerException when using IndexColumn/OrderColumn without AuditMappedBy.

This commit is contained in:
Chris Cranford 2016-12-14 15:26:33 -05:00
parent 94f2401b3c
commit 8db194e8a6
13 changed files with 262 additions and 36 deletions

View File

@ -12,18 +12,24 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.hibernate.MappingException;
import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData;
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
import org.hibernate.envers.internal.EnversMessageLogger;
import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.mapping.List;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.jboss.logging.Logger;
/**
* A helper class holding auditing meta-data for all persistent classes.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class ClassesAuditingData {
private static final EnversMessageLogger LOG = Logger.getMessageLogger(
@ -65,6 +71,7 @@ public class ClassesAuditingData {
* After all meta-data is read, updates calculated fields. This includes:
* <ul>
* <li>setting {@code forceInsertable} to {@code true} for properties specified by {@code @AuditMappedBy}</li>
* <li>adding {@code synthetic} properties to mappedBy relations which have {@code IndexColumn} or {@code OrderColumn}.</li>
* </ul>
*/
public void updateCalculatedFields() {
@ -72,25 +79,79 @@ public class ClassesAuditingData {
final PersistentClass pc = classAuditingDataEntry.getKey();
final ClassAuditingData classAuditingData = classAuditingDataEntry.getValue();
for ( String propertyName : classAuditingData.getPropertyNames() ) {
final PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData( propertyName );
updateCalculatedProperty( pc, classAuditingData, propertyName );
}
}
}
private void updateCalculatedProperty(
PersistentClass pc,
ClassAuditingData classAuditingData,
String propertyName) {
final PropertyAuditingData propertyAuditingData = classAuditingData.getPropertyAuditingData( propertyName );
final boolean isAuditMappedBy = propertyAuditingData.getAuditMappedBy() != null;
final boolean isRelationMappedBy = propertyAuditingData.getRelationMappedBy() != null;
if ( isAuditMappedBy || isRelationMappedBy ) {
final Property property = pc.getProperty( propertyName );
final String referencedEntityName = MappingTools.getReferencedEntityName( property.getValue() );
final ClassAuditingData referencedAuditData = entityNameToAuditingData.get( referencedEntityName );
if ( isAuditMappedBy ) {
// If a property had the @AuditMappedBy annotation, setting the referenced fields to be always insertable.
if ( propertyAuditingData.getAuditMappedBy() != null ) {
final String referencedEntityName = MappingTools.getReferencedEntityName(
pc.getProperty( propertyName ).getValue()
);
setAuditMappedByInsertable( referencedEntityName, pc.getEntityName(), referencedAuditData, propertyAuditingData );
}
else if ( isRelationMappedBy && ( property.getValue() instanceof List ) ) {
// If a property has mappedBy= and @Indexed and isn't @AuditMappedBy, add synthetic support.
addSyntheticIndexProperty(
(List) property.getValue(),
property.getPropertyAccessorName(),
referencedAuditData
);
}
}
}
final ClassAuditingData referencedClassAuditingData = entityNameToAuditingData.get( referencedEntityName );
private void setAuditMappedByInsertable(
String referencedEntityName,
String entityName,
ClassAuditingData referencedAuditData,
PropertyAuditingData propertyAuditingData) {
forcePropertyInsertable(
referencedAuditData,
propertyAuditingData.getAuditMappedBy(),
entityName,
referencedEntityName
);
forcePropertyInsertable(
referencedClassAuditingData, propertyAuditingData.getAuditMappedBy(),
pc.getEntityName(), referencedEntityName
);
forcePropertyInsertable(
referencedAuditData,
propertyAuditingData.getPositionMappedBy(),
entityName,
referencedEntityName
);
}
forcePropertyInsertable(
referencedClassAuditingData, propertyAuditingData.getPositionMappedBy(),
pc.getEntityName(), referencedEntityName
);
}
private void addSyntheticIndexProperty(List value, String propertyAccessorName, ClassAuditingData classAuditingData) {
final Value indexValue = value.getIndex();
if ( indexValue != null && indexValue.getColumnIterator().hasNext() ) {
final String indexColumnName = indexValue.getColumnIterator().next().getText();
if ( indexColumnName != null ) {
final PropertyAuditingData auditingData = new PropertyAuditingData(
indexColumnName,
propertyAccessorName,
ModificationStore.FULL,
RelationTargetAuditMode.AUDITED,
null,
null,
false,
true,
indexValue
);
classAuditingData.addPropertyAuditingData( indexColumnName, auditingData );
}
}
}

View File

@ -694,6 +694,9 @@ public final class AuditMetadataGenerator {
createJoins( pc, classMapping, auditingData );
addJoins( pc, propertyMapper, auditingData, pc.getEntityName(), xmlMappingData, true );
// HHH-7940 - New synthetic property support for @IndexColumn/@OrderColumn dynamic properties
addSynthetics( classMapping, auditingData, propertyMapper, xmlMappingData, pc.getEntityName(), true );
// Storing the generated configuration
final EntityConfiguration entityCfg = new EntityConfiguration(
auditEntityName,
@ -705,6 +708,28 @@ public final class AuditMetadataGenerator {
entitiesConfigurations.put( pc.getEntityName(), entityCfg );
}
private void addSynthetics(
Element classMapping,
ClassAuditingData auditingData,
CompositeMapperBuilder currentMapper,
EntityXmlMappingData xmlMappingData,
String entityName,
boolean firstPass) {
for ( PropertyAuditingData propertyAuditingData : auditingData.getSyntheticProperties() ) {
addValue(
classMapping,
propertyAuditingData.getValue(),
currentMapper,
entityName,
xmlMappingData,
propertyAuditingData,
true,
firstPass,
false
);
}
}
@SuppressWarnings({"unchecked"})
public void generateSecondPass(
PersistentClass pc,

View File

@ -157,7 +157,7 @@ public final class CollectionMetadataGenerator {
if ( oneToManyAttachedType && (inverseOneToMany || fakeOneToManyBidirectional || owningManyToOneWithJoinTableBidirectional) ) {
// A one-to-many relation mapped using @ManyToOne and @OneToMany(mappedBy="...")
addOneToManyAttached( fakeOneToManyBidirectional );
addOneToManyAttached( fakeOneToManyBidirectional );
}
else {
// All other kinds of relations require a middle (join) table.
@ -183,6 +183,10 @@ public final class CollectionMetadataGenerator {
propertyName
);
// check whether the property has an @IndexColumn or @OrderColumn because its part of an
// IndexedCollection mapping type.
final boolean indexed = ( propertyValue instanceof IndexedCollection ) && ( (IndexedCollection) propertyValue ).getIndex() != null;
final String mappedBy = getMappedBy( propertyValue );
final IdMappingData referencedIdMapping = mainGenerator.getReferencedIdMappingData(
@ -239,10 +243,16 @@ public final class CollectionMetadataGenerator {
PropertyMapper fakeBidirectionalRelationMapper;
PropertyMapper fakeBidirectionalRelationIndexMapper;
if ( fakeOneToManyBidirectional ) {
if ( fakeOneToManyBidirectional || indexed ) {
// In case of a fake many-to-one bidirectional relation, we have to generate a mapper which maps
// the mapped-by property name to the id of the related entity (which is the owner of the collection).
final String auditMappedBy = propertyAuditingData.getAuditMappedBy();
final String auditMappedBy;
if ( fakeOneToManyBidirectional ) {
auditMappedBy = propertyAuditingData.getAuditMappedBy();
}
else {
auditMappedBy = propertyValue.getMappedByProperty();
}
// Creating a prefixed relation mapper.
final IdMapper relMapper = referencingIdMapping.getIdMapper().prefixMappedProperties(
@ -257,9 +267,20 @@ public final class CollectionMetadataGenerator {
referencingEntityName, false
);
final String positionMappedBy;
if ( fakeOneToManyBidirectional ) {
positionMappedBy = propertyAuditingData.getPositionMappedBy();
}
else if ( indexed ) {
final Value indexValue = ( (IndexedCollection) propertyValue ).getIndex();
positionMappedBy = indexValue.getColumnIterator().next().getText();
}
else {
positionMappedBy = null;
}
// Checking if there's an index defined. If so, adding a mapper for it.
if ( propertyAuditingData.getPositionMappedBy() != null ) {
final String positionMappedBy = propertyAuditingData.getPositionMappedBy();
if ( positionMappedBy != null ) {
fakeBidirectionalRelationIndexMapper = new SinglePropertyMapper(
new PropertyData(
positionMappedBy,
@ -294,7 +315,8 @@ public final class CollectionMetadataGenerator {
referencedEntityName,
referencingIdData.getPrefixedMapper(),
fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper
fakeBidirectionalRelationIndexMapper,
indexed
);
}

View File

@ -7,6 +7,7 @@
package org.hibernate.envers.configuration.internal.metadata.reader;
import java.util.Map;
import java.util.stream.Collectors;
import org.hibernate.envers.AuditTable;
@ -16,6 +17,7 @@ import static org.hibernate.envers.internal.tools.Tools.newHashMap;
* @author Adam Warski (adam at warski dot org)
* @author Sebastian Komander
* @author Hern&aacut;n Chanfreau
* @author Chris Cranford
*/
public class ClassAuditingData implements AuditedPropertiesHolder {
private final Map<String, PropertyAuditingData> properties;
@ -77,4 +79,10 @@ public class ClassAuditingData implements AuditedPropertiesHolder {
public boolean contains(String propertyName) {
return properties.containsKey( propertyName );
}
public Iterable<PropertyAuditingData> getSyntheticProperties() {
return properties.values().stream()
.filter( p -> p.isSyntheic() )
.collect( Collectors.toList() );
}
}

View File

@ -15,10 +15,12 @@ import org.hibernate.envers.AuditOverrides;
import org.hibernate.envers.ModificationStore;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.mapping.Value;
/**
* @author Adam Warski (adam at warski dot org)
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public class PropertyAuditingData {
private String name;
@ -35,6 +37,10 @@ public class PropertyAuditingData {
private boolean forceInsertable;
private boolean usingModifiedFlag;
private String modifiedFlagName;
private Value value;
// Synthetic properties are ones which are not part of the actual java model.
// They're properties used for bookkeeping by Hibernate
private boolean syntheic;
public PropertyAuditingData() {
}
@ -44,6 +50,29 @@ public class PropertyAuditingData {
RelationTargetAuditMode relationTargetAuditMode,
String auditMappedBy, String positionMappedBy,
boolean forceInsertable) {
this(
name,
accessType,
store,
relationTargetAuditMode,
auditMappedBy,
positionMappedBy,
forceInsertable,
false,
null
);
}
public PropertyAuditingData(
String name,
String accessType,
ModificationStore store,
RelationTargetAuditMode relationTargetAuditMode,
String auditMappedBy,
String positionMappedBy,
boolean forceInsertable,
boolean syntheic,
Value value) {
this.name = name;
this.beanName = name;
this.accessType = accessType;
@ -52,6 +81,8 @@ public class PropertyAuditingData {
this.auditMappedBy = auditMappedBy;
this.positionMappedBy = positionMappedBy;
this.forceInsertable = forceInsertable;
this.syntheic = syntheic;
this.value = value;
}
public String getName() {
@ -104,8 +135,13 @@ public class PropertyAuditingData {
public PropertyData getPropertyData() {
return new PropertyData(
name, beanName, accessType, store,
usingModifiedFlag, modifiedFlagName
name,
beanName,
accessType,
store,
usingModifiedFlag,
modifiedFlagName,
syntheic
);
}
@ -203,4 +239,11 @@ public class PropertyAuditingData {
this.relationTargetAuditMode = relationTargetAuditMode;
}
public boolean isSyntheic() {
return syntheic;
}
public Value getValue() {
return value;
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityConfiguration;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.synchronization.AuditProcess;
@ -34,6 +35,7 @@ import org.hibernate.persister.collection.AbstractCollectionPersister;
* @author Steve Ebersole
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public abstract class BaseEnversCollectionEventListener extends BaseEnversEventListener {
protected BaseEnversCollectionEventListener(EnversService enversService) {
@ -104,6 +106,25 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL
}
}
protected final void onCollectionActionInversed(
AbstractCollectionEvent event,
PersistentCollection newColl,
Serializable oldColl,
CollectionEntry collectionEntry) {
if ( shouldGenerateRevision( event ) ) {
final String entityName = event.getAffectedOwnerEntityName();
final String ownerEntityName = ( (AbstractCollectionPersister) collectionEntry.getLoadedPersister() ).getOwnerEntityName();
final String referencingPropertyName = collectionEntry.getRole().substring( ownerEntityName.length() + 1 );
final RelationDescription rd = searchForRelationDescription( entityName, referencingPropertyName );
if ( rd != null ) {
if ( rd.getRelationType().equals( RelationType.TO_MANY_NOT_OWNING ) && rd.isIndexed() ) {
onCollectionAction( event, newColl, oldColl, collectionEntry );
}
}
}
}
/**
* Forces persistent collection initialization.
*

View File

@ -17,6 +17,7 @@ import org.hibernate.event.spi.PostCollectionRecreateEventListener;
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
* @author Steve Ebersole
* @author Chris Cranford
*/
public class EnversPostCollectionRecreateEventListenerImpl
extends BaseEnversCollectionEventListener
@ -32,5 +33,8 @@ public class EnversPostCollectionRecreateEventListenerImpl
if ( !collectionEntry.getLoadedPersister().isInverse() ) {
onCollectionAction( event, event.getCollection(), null, collectionEntry );
}
else {
onCollectionActionInversed( event, event.getCollection(), null, collectionEntry );
}
}
}

View File

@ -17,6 +17,7 @@ import org.hibernate.event.spi.PreCollectionUpdateEventListener;
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
* @author Steve Ebersole
* @author Chris Cranford
*/
public class EnversPreCollectionUpdateEventListenerImpl
extends BaseEnversCollectionEventListener
@ -32,5 +33,8 @@ public class EnversPreCollectionUpdateEventListenerImpl
if ( !collectionEntry.getLoadedPersister().isInverse() ) {
onCollectionAction( event, event.getCollection(), collectionEntry.getSnapshot(), collectionEntry );
}
else {
onCollectionActionInversed( event, event.getCollection(), collectionEntry.getSnapshot(), collectionEntry );
}
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
/**
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
* @author Chris Cranford
*/
public class EntityConfiguration {
private String versionsEntityName;
@ -80,12 +81,13 @@ public class EntityConfiguration {
String toEntityName,
IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper) {
PropertyMapper fakeBidirectionalRelationIndexMapper,
boolean indexed) {
relations.put(
fromPropertyName,
RelationDescription.toMany(
fromPropertyName, RelationType.TO_MANY_NOT_OWNING, toEntityName, mappedByPropertyName,
idMapper, fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, true
idMapper, fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, true, indexed
)
);
}
@ -94,7 +96,7 @@ public class EntityConfiguration {
relations.put(
fromPropertyName,
RelationDescription.toMany(
fromPropertyName, RelationType.TO_MANY_MIDDLE, toEntityName, null, null, null, null, true
fromPropertyName, RelationType.TO_MANY_MIDDLE, toEntityName, null, null, null, null, true, false
)
);
}
@ -104,7 +106,7 @@ public class EntityConfiguration {
fromPropertyName,
RelationDescription.toMany(
fromPropertyName, RelationType.TO_MANY_MIDDLE_NOT_OWNING, toEntityName, mappedByPropertyName,
null, null, null, true
null, null, null, true, false
)
);
}

View File

@ -13,6 +13,7 @@ import org.hibernate.internal.util.compare.EqualsHelper;
* Holds information on a property that is audited.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class PropertyData {
private final String name;
@ -24,6 +25,9 @@ public class PropertyData {
private final ModificationStore store;
private boolean usingModifiedFlag;
private String modifiedFlagName;
// Synthetic properties are ones which are not part of the actual java model.
// They're properties used for bookkeeping by Hibernate
private boolean synthetic;
/**
* Copies the given property data, except the name.
@ -64,10 +68,12 @@ public class PropertyData {
String accessType,
ModificationStore store,
boolean usingModifiedFlag,
String modifiedFlagName) {
String modifiedFlagName,
boolean synthetic) {
this( name, beanName, accessType, store );
this.usingModifiedFlag = usingModifiedFlag;
this.modifiedFlagName = modifiedFlagName;
this.synthetic = synthetic;
}
public String getName() {
@ -94,6 +100,10 @@ public class PropertyData {
return modifiedFlagName;
}
public boolean isSynthetic() {
return synthetic;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
@ -108,7 +118,8 @@ public class PropertyData {
&& store == that.store
&& EqualsHelper.equals( accessType, that.accessType )
&& EqualsHelper.equals( beanName, that.beanName )
&& EqualsHelper.equals( name, that.name );
&& EqualsHelper.equals( name, that.name )
&& EqualsHelper.equals( synthetic, that.synthetic );
}
@Override
@ -118,6 +129,7 @@ public class PropertyData {
result = 31 * result + (accessType != null ? accessType.hashCode() : 0);
result = 31 * result + (store != null ? store.hashCode() : 0);
result = 31 * result + (usingModifiedFlag ? 1 : 0);
result = 31 * result + (synthetic ? 1 : 0);
return result;
}
}

View File

@ -11,6 +11,7 @@ import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
/**
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class RelationDescription {
private final String fromPropertyName;
@ -22,6 +23,7 @@ public class RelationDescription {
private final PropertyMapper fakeBidirectionalRelationMapper;
private final PropertyMapper fakeBidirectionalRelationIndexMapper;
private final boolean insertable;
private final boolean indexed;
private boolean bidirectional;
public static RelationDescription toOne(
@ -36,7 +38,7 @@ public class RelationDescription {
boolean ignoreNotFound) {
return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper,
fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, insertable, ignoreNotFound
fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, insertable, ignoreNotFound, false
);
}
@ -48,14 +50,15 @@ public class RelationDescription {
IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper,
boolean insertable) {
boolean insertable,
boolean indexed) {
// Envers populates collections by executing dedicated queries. Special handling of
// @NotFound(action = NotFoundAction.IGNORE) can be omitted in such case as exceptions
// (e.g. EntityNotFoundException, ObjectNotFoundException) are never thrown.
// Therefore assigning false to ignoreNotFound.
return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper, insertable, false
fakeBidirectionalRelationIndexMapper, insertable, false, indexed
);
}
@ -68,7 +71,8 @@ public class RelationDescription {
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper,
boolean insertable,
boolean ignoreNotFound) {
boolean ignoreNotFound,
boolean indexed) {
this.fromPropertyName = fromPropertyName;
this.relationType = relationType;
this.toEntityName = toEntityName;
@ -78,7 +82,7 @@ public class RelationDescription {
this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper;
this.insertable = insertable;
this.indexed = indexed;
this.bidirectional = false;
}
@ -118,6 +122,10 @@ public class RelationDescription {
return insertable;
}
public boolean isIndexed() {
return indexed;
}
public boolean isBidirectional() {
return bidirectional;
}

View File

@ -25,6 +25,7 @@ import org.hibernate.property.access.spi.Getter;
* @author Adam Warski (adam at warski dot org)
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Lukasz Zuchowski (author at zuchos dot com)
* @author Chris Cranford
*/
public class MultiPropertyMapper implements ExtendedPropertyMapper {
protected final Map<PropertyData, PropertyMapper> properties;
@ -102,6 +103,12 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper {
for ( Map.Entry<PropertyData, PropertyMapper> entry : properties.entrySet() ) {
final PropertyData propertyData = entry.getKey();
final PropertyMapper propertyMapper = entry.getValue();
// synthetic properties are not part of the entity model; therefore they should be ignored.
if ( propertyData.isSynthetic() ) {
continue;
}
Getter getter;
if ( newObj != null ) {
getter = ReflectionTools.getGetter( newObj.getClass(), propertyData, session.getFactory().getServiceRegistry() );
@ -131,6 +138,12 @@ public class MultiPropertyMapper implements ExtendedPropertyMapper {
for ( Map.Entry<PropertyData, PropertyMapper> entry : properties.entrySet() ) {
final PropertyData propertyData = entry.getKey();
final PropertyMapper propertyMapper = entry.getValue();
// synthetic properties are not part of the entity model; therefore they should be ignored.
if ( propertyData.isSynthetic() ) {
continue;
}
Getter getter;
if ( newObj != null ) {
getter = ReflectionTools.getGetter( newObj.getClass(), propertyData, session.getFactory().getServiceRegistry() );

View File

@ -29,6 +29,7 @@ import org.hibernate.property.access.spi.SetterFieldImpl;
*
* @author Adam Warski (adam at warski dot org)
* @author Michal Skowronek (mskowr at o2 dot pl)
* @author Chris Cranford
*/
public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder {
private PropertyData propertyData;
@ -71,7 +72,8 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
Map<String, Object> data,
Object newObj,
Object oldObj) {
if ( propertyData.isUsingModifiedFlag() ) {
// Synthetic properties are not subject to withModifiedFlag analysis
if ( propertyData.isUsingModifiedFlag() && !propertyData.isSynthetic() ) {
data.put( propertyData.getModifiedFlagPropertyName(), !EqualsHelper.areEqual( newObj, oldObj ) );
}
}
@ -88,7 +90,8 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
Object primaryKey,
AuditReaderImplementor versionsReader,
Number revision) {
if ( data == null || obj == null ) {
// synthetic properties are not part of the entity model; therefore they should be ignored.
if ( data == null || obj == null || propertyData.isSynthetic() ) {
return;
}