@AuditJoinTable in Envers

Hi @Naros, Why Hibernate Envers uses an @AuditJoinTable for @OneToMany+@JoinColumn? Why audit tables of parent & child entities are not sufficient for auditing?

To be clear, you have a mapping such as the following:

@Entity
@Audited
public class Company {
  @Id
  private Integer id;
  @OneToMany
  @JoinColumn
  private List<Employee> employees;
}

@Entity
@Audited
public class Employee {
  @Id
  private Integer id;
}

In order to understand why Envers builds the mappings the way it does I believe its important to look at how ORM handles this use case first. If we look at the Employee table generated by ORM we’ll see:

CREATE TABLE Employee (
  id integer not null,
  company_id integer,
  employees_id integer,
  primary key(id)
);

As we can see, ORM embeds the @JoinColumn attributes as a part of Employee.

Lets now look at this from the perspective of Envers. Unfortunately Envers cannot work the way that ORM does for this mapping because we have to be cautious about audit record bloat. We want to keep the number of rows generated per change manageable but also keeping audit rows relevant.

What I mean is, without the middle join-table, Envers would be required to generate an audit row for every associated Employee record inside the employees collection for a Company entity, even if all that changed on Company was some superficial attribute, say like its name or something silly like that.

By using a middle-join table

  1. Envers can manage changes for both entity types independently
  2. Auditing employees collection involves managing just foreign-key state changes
  3. Avoids needing to load unnecessary entity state just to build an audit row.

If you want to avoid the middle join-table here, its best to use mappedBy=... on the @OneToMany in conjunction with a @ManyToOne on the owning side of the relationship, then Envers will build and operate on the associate in the same exact way as ORM.

Hope that answers your question.

3 Likes

The explanation you gave was great. Much better than the one on the official docs… which seems to be contradicting or even completely different from your explanation:

https://docs.jboss.org/envers/docs/#exceptions-onetomanyjoincolumn

According to the docs, the middle-join table is created so “when you read the revisions in which the related entity has changed, you don’t get false results”. The docs aren’t very clear here imo. But it seems to be a different reason from the one you gave, which was, if I understood correctly: using the middle-join table makes things easier and more efficient for envers.

Or am I missing something? Thanks.