Spring Data JPA Auditing - VittorioDeMarzi/hero-beans GitHub Wiki

This guide explains how we enabled automatic timestamp auditing in our JPA entities, why we use lateinit with restricted setters.

Why we chose @CreatedDate / @LastModifiedDate over @CreationTimestamp / @UpdateTimestamp

  1. Ease of testing & control
  • @CreatedDate / @LastModifiedDate:
    • You can inject custom values (e.g., mock Clock) for reproducible tests.
  • @CreationTimestamp / @UpdateTimestamp:
    • Harder to override in tests because it’s generated at the DB level.
  1. Framework portability
  • @CreatedDate / @LastModifiedDate:
    • These are Spring Data annotations, not tied to Hibernate. If you later switch from Hibernate to EclipseLink or another JPA provider, your auditing will still work.
  • @CreationTimestamp / @UpdateTimestamp:
    • These are Hibernate-only annotations. If you ever move away from Hibernate, you’ll lose this functionality.

What changed

  • Enabled Spring Data JPA Auditing so dates are filled automatically.

  • Added to entities:

    EntityListeners(AuditingEntityListener::class)

    CreatedDate (createdAt) and @LastModifiedDate (lastUpdatedAt)

  • Removed manual timestamp hooks (@PrePersist/@PreUpdate).

  • Kept business-derived fields (totals, shipping fees) in domain methods (addItem/removeItem/changeShippingMethod).

How auditing works

  • @CreatedDate is set once when the entity is first persisted.
  • @LastModifiedDate is updated on each change detected by JPA/Hibernate.
  • The work is done by AuditingEntityListener (bound via @EntityListeners).

Setup (minimum):

  1. Enable auditing in your app:
@EnableJpaAuditing
@SpringBootApplication
class Application
  1. Annotate your entities:
@Entity
@EntityListeners(AuditingEntityListener::class)
class Order { /* ... */ }
  1. Add fields with auditing annotations:
@CreatedDate
@Column(nullable = false, updatable = false)
final lateinit var createdAt: LocalDateTime
    private set

@LastModifiedDate
@Column(nullable = false)
final lateinit var lastUpdatedAt: LocalDateTime
    private set

Why lateinit + restricted setter

  • Not val: the framework must write the value when persisting; val has no setter.

  • Not nullable: lateinit avoids handling null everywhere. Just don’t read before persist.

  • Restricted setter: we expose read‑only access to the app (prevent accidental edits) but allow the framework to assign via reflection. With Kotlin's allOpen plugin, mark the property final to allow private set.

What @EntityListeners(AuditingEntityListener::class) does

It attaches Spring Data’s auditing listener to your entity so that on JPA lifecycle events (persist/update) it automatically fills fields annotated with @CreatedDate, @LastModifiedDate (and, if configured, @CreatedBy, @LastModifiedBy).

What @EnableJpaAuditing does

It turns auditing on for your Spring application by registering the required infrastructure. You typically put it on your main @SpringBootApplication class

More about Auditing