Auditing and @Embedded in Spring Data JPA

I am having a problem with JPA auditing and for @Embedded members. Consider the following example scenario:

I set up a test table in an Oracle DB:

CREATE TABLE AUDIT_TEST (
  ID            NUMBER(38)   NOT NULL PRIMARY KEY,
  CREATION_DATE TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL
);

I define a JPA @Entity with auditing:

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "AUDIT_TEST")
public class AuditTest {

  private Long id;
  private LocalDateTime creationDate;

  @Id
  @Column(name = "ID")
  public Long getId() { return id; }

  public void setId(Long id) { this.id = id; }

  @CreatedDate
  @Column(name = "CREATION_DATE")
  public LocalDateTime getCreationDate() { return creationDate; }

  public void setCreationDate(LocalDateTime creationDate) {
    this.creationDate = creationDate;
  }

}

Finally, I enable JPA auditing in my @Configuration:

@SpringBootApplication()
@EnableJpaAuditing()
public class AuditTestApplication {
}

So far so good; when I construct an AuditTest instance, assign it an id and commit, the creationDate column gets populated with the current timestamp as expected.

However, things stop working when I encapsulate the audit column in an @Embeddable:

@Embeddable
public class AuditTestEmbeddable {

  private LocalDateTime creationDate;

  @CreatedDate
  @Column(name = "CREATION_DATE")
  public LocalDateTime getCreationDate() { return creationDate; }

  public void setCreationDate(LocalDateTime creationDate) {
    this.creationDate = creationDate;
  }

}

Then I change my entity class to embed the creation date:

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "AUDIT_TEST")
public class AuditTest {

  private Long id;
  private AuditTestEmbeddable auditTestEmbeddable = new AuditTestEmbeddable();

  @Id
  @Column(name = "ID")
  public Long getId() { return id; }

  public void setId(Long id) { this.id = id; }

  @Embedded
  public AuditTestEmbeddable getAuditTestEmbeddable() {
    return auditTestEmbeddable;
  }

  public void setAuditTestEmbeddable(AuditTestEmbeddable auditTestEmbeddable) {
    this.auditTestEmbeddable = auditTestEmbeddable;
  }

}

And unfortunately, the auditing is no longer working.

Is anyone here aware of a way to save the auditing functionality while still using @Embedded classes?


Solution 1:

Update: This functionality has been added to Spring Data 2.1 M2 (Lovelace). https://jira.spring.io/browse/DATACMNS-1274

Spring Data audit annotations in nested (embeddable) classes isn't supported yet. Here's the jira ticket requesting this feature.

However, we could use custom audit listener to set audit information in embeddable classes.

Here's the sample implementation taken from a blog: How to audit entity modifications using the JPA @EntityListeners, @Embedded, and @Embeddable annotations.

Embeddable Audit

@Embeddable
public class Audit {

    @Column(name = "created_on")
    private LocalDateTime createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_on")
    private LocalDateTime updatedOn;

    @Column(name = "updated_by")
    private String updatedBy;

    //Getters and setters omitted for brevity
}

Audit Listener

public class AuditListener {

    @PrePersist
    public void setCreatedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        if(audit == null) {
            audit = new Audit();
            auditable.setAudit(audit);
        }

        audit.setCreatedOn(LocalDateTime.now());
        audit.setCreatedBy(LoggedUser.get());
    }

    @PreUpdate
    public void setUpdadtedOn(Auditable auditable) {
        Audit audit = auditable.getAudit();

        audit.setUpdatedOn(LocalDateTime.now());
        audit.setUpdatedBy(LoggedUser.get());
    }
}

Auditable

public interface Auditable {

    Audit getAudit();

    void setAudit(Audit audit);
}

Sample Entity

@Entity
@EntityListeners(AuditListener.class)
public class Post implements Auditable {

    @Id
    private Long id;

    @Embedded
    private Audit audit;

    private String title;

  }

Solution 2:

With spring-data 2.4.4 the AuditListener works fine with embedded objects, see documentation spring-data

The minimal version of spring-data is bundled in spring-boot version 2.4.3