HHH-8174 - Envers support for @NotFound

This commit is contained in:
Lukasz Antoniak 2013-06-11 09:08:56 -07:00
parent b4ab20a97b
commit f2d435ddc1
11 changed files with 531 additions and 139 deletions

View File

@ -380,8 +380,12 @@
<para> <para>
If you want to audit a relation, where the target entity is not audited (that is the case for example with If you want to audit a relation, where the target entity is not audited (that is the case for example with
dictionary-like entities, which don't change and don't have to be audited), just annotate it with dictionary-like entities, which don't change and don't have to be audited), just annotate it with
<literal>@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)</literal>. Then, when reading historic <literal>@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)</literal>. Then, while reading historic
versions of your entity, the relation will always point to the "current" related entity. versions of your entity, the relation will always point to the "current" related entity. By default Envers
throws <classname>javax.persistence.EntityNotFoundException</classname> when "current" entity does not
exist in the database. Apply <literal>@NotFound(action = NotFoundAction.IGNORE)</literal> annotation
to silence the exception and assign null value instead. Hereby solution causes implicit eager loading
of to-one relations.
</para> </para>
<para> <para>

View File

@ -77,10 +77,8 @@ public final class ToOneRelationMetadataGenerator {
// Storing information about this relation // Storing information about this relation
mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneRelation( mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneRelation(
propertyAuditingData.getName(), propertyAuditingData.getName(), referencedEntityName, relMapper,
referencedEntityName, insertable, MappingTools.ignoreNotFound( value )
relMapper,
insertable
); );
// If the property isn't insertable, checking if this is not a "fake" bidirectional many-to-one relationship, // If the property isn't insertable, checking if this is not a "fake" bidirectional many-to-one relationship,
@ -154,10 +152,8 @@ public final class ToOneRelationMetadataGenerator {
// Storing information about this relation // Storing information about this relation
mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneNotOwningRelation( mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneNotOwningRelation(
propertyAuditingData.getName(), propertyAuditingData.getName(), owningReferencePropertyName, referencedEntityName,
owningReferencePropertyName, ownedIdMapper, MappingTools.ignoreNotFound( value )
referencedEntityName,
ownedIdMapper
); );
// Adding mapper for the id // Adding mapper for the id
@ -191,10 +187,8 @@ public final class ToOneRelationMetadataGenerator {
// Storing information about this relation // Storing information about this relation
mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneRelation( mainGenerator.getEntitiesConfigurations().get( entityName ).addToOneRelation(
propertyAuditingData.getName(), propertyAuditingData.getName(), referencedEntityName, relMapper, insertable,
referencedEntityName, MappingTools.ignoreNotFound( value )
relMapper,
insertable
); );
// Adding mapper for the id // Adding mapper for the id

View File

@ -46,9 +46,8 @@ public class EntityConfiguration {
private Map<String, RelationDescription> relations; private Map<String, RelationDescription> relations;
private String parentEntityName; private String parentEntityName;
public EntityConfiguration( public EntityConfiguration(String versionsEntityName, String entityClassName, IdMappingData idMappingData,
String versionsEntityName, String entityClassName, IdMappingData idMappingData, ExtendedPropertyMapper propertyMapper, String parentEntityName) {
ExtendedPropertyMapper propertyMapper, String parentEntityName) {
this.versionsEntityName = versionsEntityName; this.versionsEntityName = versionsEntityName;
this.entityClassName = entityClassName; this.entityClassName = entityClassName;
this.idMappingData = idMappingData; this.idMappingData = idMappingData;
@ -58,60 +57,36 @@ public class EntityConfiguration {
this.relations = new HashMap<String, RelationDescription>(); this.relations = new HashMap<String, RelationDescription>();
} }
public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper, boolean insertable) { public void addToOneRelation(String fromPropertyName, String toEntityName, IdMapper idMapper, boolean insertable,
boolean ignoreNotFound) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
new RelationDescription( RelationDescription.toOne(
fromPropertyName, fromPropertyName, RelationType.TO_ONE, toEntityName, null, idMapper, null,
RelationType.TO_ONE, null, insertable, ignoreNotFound
toEntityName,
null,
idMapper,
null,
null,
insertable
) )
); );
} }
public void addToOneNotOwningRelation( public void addToOneNotOwningRelation(String fromPropertyName, String mappedByPropertyName,
String fromPropertyName, String toEntityName, IdMapper idMapper, boolean ignoreNotFound) {
String mappedByPropertyName,
String toEntityName,
IdMapper idMapper) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
new RelationDescription( RelationDescription.toOne(
fromPropertyName, fromPropertyName, RelationType.TO_ONE_NOT_OWNING, toEntityName, mappedByPropertyName,
RelationType.TO_ONE_NOT_OWNING, idMapper, null, null, true, ignoreNotFound
toEntityName,
mappedByPropertyName,
idMapper,
null,
null,
true
) )
); );
} }
public void addToManyNotOwningRelation( public void addToManyNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName,
String fromPropertyName, IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
String mappedByPropertyName, PropertyMapper fakeBidirectionalRelationIndexMapper) {
String toEntityName,
IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
new RelationDescription( RelationDescription.toMany(
fromPropertyName, fromPropertyName, RelationType.TO_MANY_NOT_OWNING, toEntityName, mappedByPropertyName,
RelationType.TO_MANY_NOT_OWNING, idMapper, fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, true
toEntityName,
mappedByPropertyName,
idMapper,
fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper,
true
) )
); );
} }
@ -119,34 +94,18 @@ public class EntityConfiguration {
public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) { public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
new RelationDescription( RelationDescription.toMany(
fromPropertyName, fromPropertyName, RelationType.TO_MANY_MIDDLE, toEntityName, null, null, null, null, true
RelationType.TO_MANY_MIDDLE,
toEntityName,
null,
null,
null,
null,
true
) )
); );
} }
public void addToManyMiddleNotOwningRelation( public void addToManyMiddleNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName) {
String fromPropertyName,
String mappedByPropertyName,
String toEntityName) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
new RelationDescription( RelationDescription.toMany(
fromPropertyName, fromPropertyName, RelationType.TO_MANY_MIDDLE_NOT_OWNING, toEntityName, mappedByPropertyName,
RelationType.TO_MANY_MIDDLE_NOT_OWNING, null, null, null, true
toEntityName,
mappedByPropertyName,
null,
null,
null,
true
) )
); );
} }
@ -175,6 +134,13 @@ public class EntityConfiguration {
return parentEntityName; return parentEntityName;
} }
/**
* @return the className for the configured entity
*/
public String getEntityClassName() {
return entityClassName;
}
// For use by EntitiesConfigurations // For use by EntitiesConfigurations
String getVersionsEntityName() { String getVersionsEntityName() {
@ -184,11 +150,4 @@ public class EntityConfiguration {
Iterable<RelationDescription> getRelationsIterator() { Iterable<RelationDescription> getRelationsIterator() {
return relations.values(); return relations.values();
} }
/**
* @return the className for the configured entity
*/
public String getEntityClassName() {
return entityClassName;
}
} }

View File

@ -34,21 +34,44 @@ public class RelationDescription {
private final RelationType relationType; private final RelationType relationType;
private final String toEntityName; private final String toEntityName;
private final String mappedByPropertyName; private final String mappedByPropertyName;
private final boolean ignoreNotFound;
private final IdMapper idMapper; private final IdMapper idMapper;
private final PropertyMapper fakeBidirectionalRelationMapper; private final PropertyMapper fakeBidirectionalRelationMapper;
private final PropertyMapper fakeBidirectionalRelationIndexMapper; private final PropertyMapper fakeBidirectionalRelationIndexMapper;
private final boolean insertable; private final boolean insertable;
private boolean bidirectional; private boolean bidirectional;
public RelationDescription( public static RelationDescription toOne(String fromPropertyName, RelationType relationType, String toEntityName,
String fromPropertyName, RelationType relationType, String toEntityName, String mappedByPropertyName, IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
String mappedByPropertyName, IdMapper idMapper, PropertyMapper fakeBidirectionalRelationIndexMapper, boolean insertable,
PropertyMapper fakeBidirectionalRelationMapper, boolean ignoreNotFound) {
PropertyMapper fakeBidirectionalRelationIndexMapper, boolean insertable) { return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper, insertable, ignoreNotFound
);
}
public static RelationDescription toMany(String fromPropertyName, RelationType relationType, String toEntityName,
String mappedByPropertyName, IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper, boolean insertable) {
// 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
);
}
private RelationDescription(String fromPropertyName, RelationType relationType, String toEntityName,
String mappedByPropertyName, IdMapper idMapper, PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper, boolean insertable, boolean ignoreNotFound) {
this.fromPropertyName = fromPropertyName; this.fromPropertyName = fromPropertyName;
this.relationType = relationType; this.relationType = relationType;
this.toEntityName = toEntityName; this.toEntityName = toEntityName;
this.mappedByPropertyName = mappedByPropertyName; this.mappedByPropertyName = mappedByPropertyName;
this.ignoreNotFound = ignoreNotFound;
this.idMapper = idMapper; this.idMapper = idMapper;
this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper; this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper; this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper;
@ -73,6 +96,10 @@ public class RelationDescription {
return mappedByPropertyName; return mappedByPropertyName;
} }
public boolean isIgnoreNotFound() {
return ignoreNotFound;
}
public IdMapper getIdMapper() { public IdMapper getIdMapper() {
return idMapper; return idMapper;
} }

View File

@ -35,7 +35,8 @@ import org.hibernate.persister.entity.EntityPersister;
*/ */
public class ToOneEntityLoader { public class ToOneEntityLoader {
/** /**
* Immediately loads historical entity or its current state when excluded from audit process. * Immediately loads historical entity or its current state when excluded from audit process. Returns {@code null}
* reference if entity has not been found in the database.
*/ */
public static Object loadImmediate( public static Object loadImmediate(
AuditReaderImplementor versionsReader, AuditReaderImplementor versionsReader,

View File

@ -45,11 +45,8 @@ public class ToOneIdMapper extends AbstractToOneMapper {
private final String referencedEntityName; private final String referencedEntityName;
private final boolean nonInsertableFake; private final boolean nonInsertableFake;
public ToOneIdMapper( public ToOneIdMapper(IdMapper delegate, PropertyData propertyData, String referencedEntityName,
IdMapper delegate, boolean nonInsertableFake) {
PropertyData propertyData,
String referencedEntityName,
boolean nonInsertableFake) {
super( propertyData ); super( propertyData );
this.delegate = delegate; this.delegate = delegate;
this.referencedEntityName = referencedEntityName; this.referencedEntityName = referencedEntityName;
@ -57,11 +54,8 @@ public class ToOneIdMapper extends AbstractToOneMapper {
} }
@Override @Override
public boolean mapToMapFromEntity( public boolean mapToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj,
SessionImplementor session, Object oldObj) {
Map<String, Object> data,
Object newObj,
Object oldObj) {
final HashMap<String, Object> newData = new HashMap<String, Object>(); final HashMap<String, Object> newData = new HashMap<String, Object>();
// If this property is originally non-insertable, but made insertable because it is in a many-to-one "fake" // If this property is originally non-insertable, but made insertable because it is in a many-to-one "fake"
@ -77,11 +71,8 @@ public class ToOneIdMapper extends AbstractToOneMapper {
} }
@Override @Override
public void mapModifiedFlagsToMapFromEntity( public void mapModifiedFlagsToMapFromEntity(SessionImplementor session, Map<String, Object> data, Object newObj,
SessionImplementor session, Object oldObj) {
Map<String, Object> data,
Object newObj,
Object oldObj) {
if ( getPropertyData().isUsingModifiedFlag() ) { if ( getPropertyData().isUsingModifiedFlag() ) {
data.put( getPropertyData().getModifiedFlagPropertyName(), checkModified( session, newObj, oldObj ) ); data.put( getPropertyData().getModifiedFlagPropertyName(), checkModified( session, newObj, oldObj ) );
} }
@ -103,9 +94,8 @@ public class ToOneIdMapper extends AbstractToOneMapper {
} }
@Override @Override
public void nullSafeMapToEntityFromMap( public void nullSafeMapToEntityFromMap(AuditConfiguration verCfg, Object obj, Map data, Object primaryKey,
AuditConfiguration verCfg, Object obj, Map data, Object primaryKey, AuditReaderImplementor versionsReader, Number revision) {
AuditReaderImplementor versionsReader, Number revision) {
final Object entityId = delegate.mapToIdFromMap( data ); final Object entityId = delegate.mapToIdFromMap( data );
Object value = null; Object value = null;
if ( entityId != null ) { if ( entityId != null ) {
@ -114,27 +104,35 @@ public class ToOneIdMapper extends AbstractToOneMapper {
} }
else { else {
final EntityInfo referencedEntity = getEntityInfo( verCfg, referencedEntityName ); final EntityInfo referencedEntity = getEntityInfo( verCfg, referencedEntityName );
value = ToOneEntityLoader.createProxyOrLoadImmediate( boolean ignoreNotFound = false;
versionsReader, referencedEntity.getEntityClass(), referencedEntityName, if ( !referencedEntity.isAudited() ) {
entityId, revision, RevisionType.DEL.equals( final String referencingEntityName = verCfg.getEntCfg().getEntityNameForVersionsEntityName( (String) data.get( "$type$" ) );
data.get( ignoreNotFound = verCfg.getEntCfg().get( referencingEntityName ).getRelationDescription( getPropertyData().getName() ).isIgnoreNotFound();
verCfg.getAuditEntCfg() }
.getRevisionTypePropName() if ( ignoreNotFound ) {
) // Eagerly loading referenced entity to silence potential (in case of proxy)
), verCfg // EntityNotFoundException or ObjectNotFoundException. Assigning null reference.
); value = ToOneEntityLoader.loadImmediate(
versionsReader, referencedEntity.getEntityClass(), referencedEntityName,
entityId, revision, RevisionType.DEL.equals( data.get( verCfg.getAuditEntCfg().getRevisionTypePropName() ) ),
verCfg
);
}
else {
value = ToOneEntityLoader.createProxyOrLoadImmediate(
versionsReader, referencedEntity.getEntityClass(), referencedEntityName,
entityId, revision, RevisionType.DEL.equals( data.get( verCfg.getAuditEntCfg().getRevisionTypePropName() ) ),
verCfg
);
}
} }
} }
setPropertyValue( obj, value ); setPropertyValue( obj, value );
} }
public void addMiddleEqualToQuery( public void addMiddleEqualToQuery(Parameters parameters, String idPrefix1, String prefix1,
Parameters parameters, String idPrefix2, String prefix2) {
String idPrefix1,
String prefix1,
String idPrefix2,
String prefix2) {
delegate.addIdsEqualToQuery( parameters, prefix1, delegate, prefix2 ); delegate.addIdsEqualToQuery( parameters, prefix1, delegate, prefix2 );
} }
} }

View File

@ -24,6 +24,7 @@
package org.hibernate.envers.internal.tools; package org.hibernate.envers.internal.tools;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.ToOne; import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value; import org.hibernate.mapping.Value;
@ -63,4 +64,18 @@ public abstract class MappingTools {
return null; return null;
} }
/**
* @param value Persistent property.
* @return {@code false} if lack of associated entity shall raise an exception, {@code true} otherwise.
*/
public static boolean ignoreNotFound(Value value) {
if ( value instanceof ManyToOne ) {
return ( (ManyToOne) value ).isIgnoreNotFound();
}
else if ( value instanceof OneToMany ) {
return ( (OneToMany) value ).isIgnoreNotFound();
}
return false;
}
} }

View File

@ -0,0 +1,111 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.test.entities.manytomany.unidirectional;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.test.entities.UnversionedStrTestEntity;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Audited
@Entity
@Table(name = "M2M_N_AUD_NULL")
public class ManyToManyNotAuditedNullEntity implements Serializable {
@Id
private Integer id;
private String data;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@ManyToMany(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE)
private List<UnversionedStrTestEntity> references = new ArrayList<UnversionedStrTestEntity>();
protected ManyToManyNotAuditedNullEntity() {
}
public ManyToManyNotAuditedNullEntity(Integer id, String data) {
this.id = id;
this.data = data;
}
public boolean equals(Object o) {
if ( this == o ) return true;
if ( !( o instanceof ManyToManyNotAuditedNullEntity ) ) return false;
ManyToManyNotAuditedNullEntity that = (ManyToManyNotAuditedNullEntity) o;
if ( data != null ? !data.equals( that.getData() ) : that.getData() != null ) return false;
if ( id != null ? !id.equals( that.getId() ) : that.getId() != null ) return false;
return true;
}
public int hashCode() {
int result = ( id != null ? id.hashCode() : 0 );
result = 31 * result + ( data != null ? data.hashCode() : 0 );
return result;
}
public String toString() {
return "ManyToManyNotAuditedNullEntity(id = " + id + ", data = " + data + ")";
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public List<UnversionedStrTestEntity> getReferences() {
return references;
}
public void setReferences(List<UnversionedStrTestEntity> references) {
this.references = references;
}
}

View File

@ -0,0 +1,110 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.test.entities.manytoone.unidirectional;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.test.entities.UnversionedStrTestEntity;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Audited
@Entity
@Table(name = "M2O_N_AUD_NULL")
public class ManyToOneNotAuditedNullEntity implements Serializable {
@Id
private Integer id;
private String data;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@NotFound(action = NotFoundAction.IGNORE)
private UnversionedStrTestEntity reference;
protected ManyToOneNotAuditedNullEntity() {
}
public ManyToOneNotAuditedNullEntity(Integer id, String data, UnversionedStrTestEntity reference) {
this.id = id;
this.data = data;
this.reference = reference;
}
public boolean equals(Object o) {
if ( this == o ) return true;
if ( !( o instanceof ManyToOneNotAuditedNullEntity ) ) return false;
ManyToOneNotAuditedNullEntity that = (ManyToOneNotAuditedNullEntity) o;
if ( data != null ? !data.equals( that.getData() ) : that.getData() != null ) return false;
if ( id != null ? !id.equals( that.getId() ) : that.getId() != null ) return false;
return true;
}
public int hashCode() {
int result = ( id != null ? id.hashCode() : 0 );
result = 31 * result + ( data != null ? data.hashCode() : 0 );
return result;
}
public String toString() {
return "ManyToOneNotAuditedNullEntity(id = " + id + ", data = " + data + ")";
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public UnversionedStrTestEntity getReference() {
return reference;
}
public void setReference(UnversionedStrTestEntity reference) {
this.reference = reference;
}
}

View File

@ -0,0 +1,111 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.test.entities.onetomany;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.test.entities.UnversionedStrTestEntity;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Audited
@Entity
@Table(name = "O2M_N_AUD_NULL")
public class OneToManyNotAuditedNullEntity implements Serializable {
@Id
private Integer id;
private String data;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@OneToMany(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE)
private List<UnversionedStrTestEntity> references = new ArrayList<UnversionedStrTestEntity>();
protected OneToManyNotAuditedNullEntity() {
}
public OneToManyNotAuditedNullEntity(Integer id, String data) {
this.id = id;
this.data = data;
}
public boolean equals(Object o) {
if ( this == o ) return true;
if ( !( o instanceof OneToManyNotAuditedNullEntity ) ) return false;
OneToManyNotAuditedNullEntity that = (OneToManyNotAuditedNullEntity) o;
if ( data != null ? !data.equals( that.getData() ) : that.getData() != null ) return false;
if ( id != null ? !id.equals( that.getId() ) : that.getId() != null ) return false;
return true;
}
public int hashCode() {
int result = ( id != null ? id.hashCode() : 0 );
result = 31 * result + ( data != null ? data.hashCode() : 0 );
return result;
}
public String toString() {
return "OneToManyNotAuditedNullEntity(id = " + id + ", data = " + data + ")";
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public List<UnversionedStrTestEntity> getReferences() {
return references;
}
public void setReferences(List<UnversionedStrTestEntity> references) {
this.references = references;
}
}

View File

@ -25,26 +25,37 @@ package org.hibernate.envers.test.integration.proxy;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.junit.Assert;
import org.junit.Test;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority; import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.UnversionedStrTestEntity; import org.hibernate.envers.test.entities.UnversionedStrTestEntity;
import org.hibernate.envers.test.entities.manytomany.unidirectional.ManyToManyNotAuditedNullEntity;
import org.hibernate.envers.test.entities.manytoone.unidirectional.ManyToOneNotAuditedNullEntity;
import org.hibernate.envers.test.entities.manytoone.unidirectional.TargetNotAuditedEntity; import org.hibernate.envers.test.entities.manytoone.unidirectional.TargetNotAuditedEntity;
import org.hibernate.envers.test.entities.onetomany.OneToManyNotAuditedNullEntity;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.LazyInitializer;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
/** /**
* @author Eugene Goroschenya * @author Eugene Goroschenya
*/ */
public class ProxyIdentifier extends BaseEnversJPAFunctionalTestCase { public class ProxyIdentifier extends BaseEnversJPAFunctionalTestCase {
private TargetNotAuditedEntity tnae1; private TargetNotAuditedEntity tnae1 = null;
private UnversionedStrTestEntity uste1; private ManyToOneNotAuditedNullEntity mtonane1 = null;
private ManyToManyNotAuditedNullEntity mtmnane1 = null;
private OneToManyNotAuditedNullEntity otmnane1 = null;
private UnversionedStrTestEntity uste1 = null;
private UnversionedStrTestEntity uste2 = null;
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class[] {TargetNotAuditedEntity.class, UnversionedStrTestEntity.class}; return new Class[] {
TargetNotAuditedEntity.class, ManyToOneNotAuditedNullEntity.class, UnversionedStrTestEntity.class,
ManyToManyNotAuditedNullEntity.class, OneToManyNotAuditedNullEntity.class
};
} }
@Test @Test
@ -53,10 +64,12 @@ public class ProxyIdentifier extends BaseEnversJPAFunctionalTestCase {
EntityManager em = getEntityManager(); EntityManager em = getEntityManager();
uste1 = new UnversionedStrTestEntity( "str1" ); uste1 = new UnversionedStrTestEntity( "str1" );
uste2 = new UnversionedStrTestEntity( "str2" );
// No revision // No revision
em.getTransaction().begin(); em.getTransaction().begin();
em.persist( uste1 ); em.persist( uste1 );
em.persist( uste2 );
em.getTransaction().commit(); em.getTransaction().commit();
// Revision 1 // Revision 1
@ -65,24 +78,73 @@ public class ProxyIdentifier extends BaseEnversJPAFunctionalTestCase {
tnae1 = new TargetNotAuditedEntity( 1, "tnae1", uste1 ); tnae1 = new TargetNotAuditedEntity( 1, "tnae1", uste1 );
em.persist( tnae1 ); em.persist( tnae1 );
em.getTransaction().commit(); em.getTransaction().commit();
// Revision 2
em.getTransaction().begin();
uste2 = em.find( UnversionedStrTestEntity.class, uste2.getId() );
mtonane1 = new ManyToOneNotAuditedNullEntity( 2, "mtonane1", uste2 );
mtmnane1 = new ManyToManyNotAuditedNullEntity( 3, "mtmnane1" );
mtmnane1.getReferences().add( uste2 );
otmnane1 = new OneToManyNotAuditedNullEntity( 4, "otmnane1" );
otmnane1.getReferences().add( uste2 );
em.persist( mtonane1 );
em.persist( mtmnane1 );
em.persist( otmnane1 );
em.getTransaction().commit();
em.clear();
// Revision 3
// Remove not audited target entity, so we can verify null reference
// when @NotFound(action = NotFoundAction.IGNORE) applied.
em.getTransaction().begin();
ManyToOneNotAuditedNullEntity tmp1 = em.find( ManyToOneNotAuditedNullEntity.class, mtonane1.getId() );
tmp1.setReference( null );
tmp1 = em.merge( tmp1 );
ManyToManyNotAuditedNullEntity tmp2 = em.find( ManyToManyNotAuditedNullEntity.class, mtmnane1.getId() );
tmp2.setReferences( null );
tmp2 = em.merge( tmp2 );
OneToManyNotAuditedNullEntity tmp3 = em.find( OneToManyNotAuditedNullEntity.class, otmnane1.getId() );
tmp3.setReferences( null );
tmp3 = em.merge( tmp3 );
em.remove( em.getReference( UnversionedStrTestEntity.class, uste2.getId() ) );
em.getTransaction().commit();
em.close();
} }
@Test @Test
public void testProxyIdentifier() { public void testProxyIdentifier() {
TargetNotAuditedEntity rev1 = getAuditReader().find( TargetNotAuditedEntity.class, tnae1.getId(), 1 ); TargetNotAuditedEntity rev1 = getAuditReader().find( TargetNotAuditedEntity.class, tnae1.getId(), 1 );
assert rev1.getReference() instanceof HibernateProxy; Assert.assertTrue( rev1.getReference() instanceof HibernateProxy );
HibernateProxy proxyCreateByEnvers = (HibernateProxy) rev1.getReference(); HibernateProxy proxyCreateByEnvers = (HibernateProxy) rev1.getReference();
LazyInitializer lazyInitializer = proxyCreateByEnvers.getHibernateLazyInitializer(); LazyInitializer lazyInitializer = proxyCreateByEnvers.getHibernateLazyInitializer();
assert lazyInitializer.isUninitialized(); Assert.assertTrue( lazyInitializer.isUninitialized() );
assert lazyInitializer.getIdentifier() != null; Assert.assertNotNull( lazyInitializer.getIdentifier() );
assert lazyInitializer.getIdentifier().equals( tnae1.getId() ); Assert.assertEquals( tnae1.getId(), lazyInitializer.getIdentifier() );
assert lazyInitializer.isUninitialized(); Assert.assertTrue( lazyInitializer.isUninitialized() );
assert rev1.getReference().getId().equals( uste1.getId() ); Assert.assertEquals( uste1.getId(), rev1.getReference().getId() );
assert rev1.getReference().getStr().equals( uste1.getStr() ); Assert.assertEquals( uste1.getStr(), rev1.getReference().getStr() );
assert !lazyInitializer.isUninitialized(); Assert.assertFalse( lazyInitializer.isUninitialized() );
}
@Test
@TestForIssue( jiraKey = "HHH-8174" )
public void testNullReferenceWithNotFoundActionIgnore() {
ManyToOneNotAuditedNullEntity mtoRev2 = getAuditReader().find( ManyToOneNotAuditedNullEntity.class, mtonane1.getId(), 2 );
Assert.assertEquals( mtonane1, mtoRev2 );
Assert.assertNull( mtoRev2.getReference() );
ManyToManyNotAuditedNullEntity mtmRev2 = getAuditReader().find( ManyToManyNotAuditedNullEntity.class, mtmnane1.getId(), 2 );
Assert.assertEquals( mtmnane1, mtmRev2 );
Assert.assertTrue( mtmRev2.getReferences().isEmpty() );
OneToManyNotAuditedNullEntity otmRev2 = getAuditReader().find( OneToManyNotAuditedNullEntity.class, otmnane1.getId(), 2 );
Assert.assertEquals( otmnane1, otmRev2 );
Assert.assertTrue( otmRev2.getReferences().isEmpty() );
} }
} }