[DATAGRAPH-1212] Neo4jAuditing not work because of early ID generation Created: 22/Mar/19  Updated: 29/Mar/19

Status: Open
Project: Spring Data Neo4j
Component/s: CORE
Affects Version/s: 5.2 M2 (Moore), 5.1.5 (Lovelace SR5)
Fix Version/s: None

Type: Bug Priority: Major
Reporter: nioertel Assignee: Gerrit Meier
Resolution: Unresolved Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: File demo.tgz    
Last updater: nioertel

 Description   

Situation

We're trying to use auditing features via @EnableNeo4jAuditing. For our entity classes, we have a common base class (abstract because we don't want it to appear in the label hierarchy):

public abstract class BaseEntity {
  public static final String ATTR_UUID = "UUID";
  public static final String ATTR_CREATED_ON = "createdOn";

  public static final class UuidGenerationStrategy implements IdStrategy {
    @Override
    public Object generateId(Object entity) {
      return UuidHelper.generateTechnicalId();
    }
  }

  @Property(name = ATTR_UUID)
  @Id
  @Index(unique = true)
  @GeneratedValue(strategy = UuidGenerationStrategy.class)
  private String uuid;

  @Property(name = ATTR_CREATED_ON)
  @DateLong
  @CreatedDate
  private Instant createdOn;

  public String getUuid() {
    return uuid;
  }

  public Instant getCreatedOn() {
    return createdOn;
  }
}

When calling Neo4jSession#save, the Neo4jAuditingEventListener is called automatically as expected and internally calls IsNewAwareAuditingHandler#markAudited, which then calls Neo4jPersistentEntity#isNew to decide whether the entity should be marked as created (AuditingHandler#markCreated) or as modified (AuditingHandler#markModified).

Problem

The way SDN is initialised via Neo4jAuditingRegistrar, Neo4jPersistentEntity#isNew uses the IsNewStrategy implemented in Neo4jPersistentEntity.Neo4jIsNewStrategy#isNew which is not working because it makes its decision based on whether the entity has an id assigned - which is happening automatically before any event listener is called (SaveEventDeleage#preSaveCheck -> SaveEventDelegate#visit -> MappingContext#nativeId -> MappingContext#generateIdIfNecessary).
So SDN will always assume that an entity is not new.

Workarounds

We found two workarounds which are neither documented nor nice but I'm adding them here in case anyone else runs into this issue:

  • Implement org.springframework.data.domain.Persistable in BaseEntity and provide a custom isNew() implementation there. In that case Neo4jPersistentEntity would use PersistableIsNewStrategy#INSTANCE which is calling the entity's isNew() method.
  • Implement a custom AuditingHandler, extend Neo4jAuditingRegistrar to use the custom implementation and implement a custom @EnableNeo4jAuditing annotation.

Thoughts on the Solution

I would be happy to create a pull request but I'm not sure what's the preferred way to solve the problem:

  • Generating the Id later would probably require quite some refactoring around SaveEventDelegate and MappingContext and have other side effects. But I feel that this is the only clean solution within the semantics of SDN where each Repository#save call actually results in a MERGE statement.
  • If a @Version property is present in the entity, this field could probably be safely used. This solution is btw the default implementation used in Spring Data's BasicPersistentEntity, which is explicitly overridden in SDN. However that's not applicable to everyone's setup.
  • Easier approaches such as checking if createdOn is null also don't work with the semantics of SDN (save = MERGE) as functionality would be only correct if the user loads the entity from the DB before.


 Comments   
Comment by nioertel [ 22/Mar/19 ]

I also added a small project with unit tests to reproduce the issue.

Comment by nioertel [ 29/Mar/19 ]

Just realised that I created a duplicate to DATAGRAPH-1185.

Generated at Mon Jan 27 05:40:11 UTC 2020 using Jira 7.13.8#713008-sha1:1606a5c1e7006e1ab135aac81f7a9566b2dbc3a6.