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
- 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.
- 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 byJPA/Hibernate
.- The work is done by
AuditingEntityListener
(bound via@EntityListeners
).
Setup (minimum):
- Enable auditing in your app:
@EnableJpaAuditing
@SpringBootApplication
class Application
- Annotate your entities:
@Entity
@EntityListeners(AuditingEntityListener::class)
class Order { /* ... */ }
- 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
lateinit
+ restricted setter
Why -
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