Search code examples
hibernatehibernate-envers

How to read modifications related to association marked with NOT_AUDITED


In my use case a have a Job and this Job has comments. Since you cannot edit comments I do not want to Audit this Entity. Here is code:

@Audited(withModifiedFlag = true)
@Entity
@Table(name = "JOB")
public class Job {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "id")
  private Integer id;

  @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "j_id")
  private List<Comment> comments;
}

@Entity
@Table(name = "COMMENT")
public class Comment {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "id")
  private Integer id;

  @Column(name = "title")
  private String title;

  @Column(name = "text")
  private String text;
}

Now I want to be able to fetch all Modifications related to comments. Of course Hibernate do not generate an COMMENT_AUD table. But it generates a JOB_COMMENT_AUD table.

How can I get the respective Comment entities from the AuditReader? I am only able to read Job entities with the AuditReader. Or in other words: I need to have access to the JOB_COMMENT_AUD entries with the AuditReader.

Somehow it must be possible to get the respective Comment entity with the AuditReader. Why else should hibernate audit the relation into the auto-generated JOB_COMMENT_AUD table?


Solution

  • First it is important to understand what the RelationalTargetAuditMode.NOT_AUDITED does.

    This annotation will make Envers generate a join-table that maintains the foreign key to primary key relationship between the audited entity Job and the ORM data table for Comment entities.

    In other words, Envers does not duplicate any of the columns in the audit schema for the Comment entity but instead captures just the primary key so that when an audited Job instance is populated, it knows how to properly set the collection of Comments.

    From an AuditReader, only Job types can be queried. This is because you've marked only the Job type to be audited and not the Comment type. But that does not mean you cannot fetch the associated Comment when you get an audited instance of a Job.

    final AuditReader auditReader = AuditReaderFactory.get( entityManager );
    
    // Get the first revision of Job with id jobId
    // This Job has 2 comments, so here we can get them easily.
    final Job job = auditReader.find( Job.class, jobId, 1 );
    assertEquals( 2, job.getComments().size() );
    assertEquals( "Hello World", job.getComments().get( 0 ).getText() );
    assertEquals( "Goodbye World", job.getComments().get( 1 ).getText() );
    

    What Envers does is wrap your Comment collection with a proxy so that when your code first attempts to access it, it checks to see if that collection has been initialized. If it hasn't, it will populate the collection based on a specialized query that knows how to take the revision data about the current Job entity, the Job_Comment_AUD table and the base Comment table for your entity and load the entity data.

    In other words, the proxy initialization fires a query like this for you:

    SELECT c.* 
      FROM Comment c, 
           Job_Comment_AUD jc 
     WHERE jc.job_id = :jobId
       AND jc.revision = :jobRevision
       AND jc.comment_id = c.id     
    

    The beauty is your code doesn't need to execute any specific fetch or query using the Hibernate Session or the JPA EntityManager. Envers handles this seamlessly for you when you invoke the job.getComments() collection.