targetAuditMode=RelationTargetAuditMode.NOT_AUDITED should allow for missing entity targets

Description

If you use targetAuditMode=RelationTargetAuditMode.NOT_AUDITED on a relation and delete the (non-audited) target entity, queries for audits that contain links to the deleted target entity will throw an exception. Where it is thrown depends on how the source entity is implemented: if it implements hashCode() and includes the target entity in the hash, it'll be deep within the query itself.

Here's a concrete example:

class Foo { @Audited(withModifiedFlag=true) String interestingField; @Audited(targetAuditMode=RelationTargetAuditMode.NOT_AUDITED) Bar myBar; public boolean equals(Object obj) { // compares interestingField and myBar } public int hashCode() { // hashes interestingField and myBar } } class Bar { ... }

Now suppose we issue three transactions:

  1. Transaction 1 creates and persists a Foo and a Bar.

  2. Transaction 2 deletes both the Foo and the Bar.

  3. Transaction 3 queries for all revisions.

The query will throw an exception like so:

javax.persistence.EntityNotFoundException: Unable to find Bar with id 1 at org.hibernate.ejb.Ejb3Configuration$Ejb3EntityNotFoundDelegate.handleEntityNotFound(Ejb3Configuration.java:155) at org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:171) at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:160) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:195) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) ... at org.hibernate.envers.tools.Triple.hashCode(Triple.java:74) at java.util.HashMap.put(HashMap.java:372) at org.hibernate.envers.reader.FirstLevelCache.putOnEntityNameCache(FirstLevelCache.java:87) at org.hibernate.envers.entities.EntityInstantiator.createInstanceFromVersionsEntity(EntityInstantiator.java:104) at org.hibernate.envers.query.impl.RevisionsOfEntityQuery.list(RevisionsOfEntityQuery.java:134) at org.hibernate.envers.query.impl.AbstractAuditQuery.getResultList(AbstractAuditQuery.java:105) ...

It would be nice to fail gracefully in the face of non-existent entity targets, by nullifying the relation, perhaps.

Activity

Show:

Chris Cranford February 17, 2020 at 8:59 PM

It did but unfortunately what is now wip/6.0 is not the same branch in which I worked on before I moved to the DBZ team. I will re-prepare the fix against master and submit a new PR.

Former user February 13, 2020 at 9:51 PM

, it doesn't look like the fix made it to 6.0.alpha1...

Steve Ebersole December 6, 2018 at 3:43 PM

Preparing Alpha1 release

Chris Cranford April 12, 2017 at 5:05 PM

Upon testing this in action, I believe I'd rather see a few slight changes.

First, the RelationTargetNotFoundAction enum will maintain 3 values rather than 2:

public enum RelationTargetNotFoundAction { // Use the default identified by EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG. DEFAULT, // Throw an error should one occur if the target relation doesn't exist. ERROR, // Ignore the error should one occur if the target relation doesn't exist. IGNORE }

Secondly, the Audited annotation will use a different default:

public @interface Audited { /** * Specifies if the entity that is the target isn't found how should we react. * The default is to ignore the failure if the target entity doesn't exist. */ @Incubating RelationTargetNotFoundAction targetNotFoundAction() default RelationTargetNotFoundAction.DEFAULT; }

Lastly, the configuration property:

/** * Specifies whether the legacy not-found behavior of throwing an EntityNotFoundException should be used. * Defaults to {@code true}. */ String GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG = "org.hibernate.envers.global_relation_not_found_legacy_flag";

What this leads to is that users upgrading to 6.0 will see no change in behavior. The EntityNotFoundException will continue to be thrown without users having to make a single change to their classes, annotations, or configurations. This allows easy backward compatibility but flexibility for users to engage in the new behavior as needed.

So in order to actually ignore these EntityNotFoundException cases, users have several options.

The first is to enable the feature globally. Users can do that by changing the org.hibernate.envers.global_relation_not_found_legacy_flag to false. In other words, the default enum value of DEFAULT directly translates to whether legacy behavior is ON or OFF based on the configuration setting.

The second option is to enable the feature for a specific entity class by specifying the targetNotFoundAction attribute at the class level's @Audited annotation. This means if the property uses the DEFAULT value but the class specifies IGNORE or ERROR, the properties will use the value defined at the class level. The only exception is for cases where your property defines itself explicitly as IGNORE or ERROR and then the class-level value won't have precedence.

And obviously the third is to enable the feature by specifying the targetNotFoundAction attribute at the property level's @Audited annotation as mentioned above.

Users can also mix-n-match the global configuration property & annotation values as needed to address their individual needs.

Chris Cranford April 11, 2017 at 7:49 PM

I have also decided to add the following configuration setting:

/** * Globally specifies whether to gracefully handle missing to-one relations. * Defaults to {@code true}. * * By specifying this flag as {@code false}, you restore legacy behavior without having to * change all your {@code Audited} annotations which use this feature by default. * * @since 6.0 */ String GLOBAL_RELATION_NOT_FOUND_FLAG = "org.hibernate.envers.global_relation_not_found_flag";

By default, Envers will assume the IGNORE behavior. This means any @Audited to-one property will be handled to be ignored if not found. But some users may prefer the legacy behavior without having to modify all their @Audited annotations and rather be explicit about the cases where they'd like IGNORE to be used. For those situations, users can set this global configuration to false to invert the default behavior to do precisely that.

Fixed

Details

Assignee

Reporter

Fix versions

Priority

Created March 5, 2013 at 7:58 PM
Updated January 18, 2024 at 1:50 PM
Resolved December 16, 2021 at 6:05 AM