java
56 lines · 7 steps
Automatic audit trails with Spring Data JPA
Wire JPA auditing to your security context so every record stamps who created it and when, with no manual bookkeeping.
Explained by
highlit
1@Configuration
2@EnableJpaAuditing(auditorAwareRef = "auditorAware")
3public class AuditingConfig {
4
5 @Bean
6 public AuditorAware<String> auditorAware() {
7 return () -> Optional.ofNullable(SecurityContextHolder.getContext())
8 .map(SecurityContext::getAuthentication)
9 .filter(Authentication::isAuthenticated)
10 .filter(auth -> !(auth instanceof AnonymousAuthenticationToken))
11 .map(Authentication::getName)
12 .or(() -> Optional.of("system"));
13 }
14}
15
16@Entity
17@Table(name = "audit_records")
18@EntityListeners(AuditingEntityListener.class)
19public class AuditRecord {
20
21 @Id
22 @GeneratedValue(strategy = GenerationType.UUID)
23 private UUID id;
24
25 @Enumerated(EnumType.STRING)
26 @Column(nullable = false, updatable = false)
27 private AuditAction action;
28
29 @Column(nullable = false, updatable = false)
30 private String entityType;
31
32 @Column(nullable = false, updatable = false)
33 private String entityId;
34
35 @Column(columnDefinition = "jsonb", updatable = false)
36 @JdbcTypeCode(SqlTypes.JSON)
37 private Map<String, Object> details;
38
39 @CreatedBy
40 @Column(name = "performed_by", nullable = false, updatable = false)
41 private String performedBy;
42
43 @CreatedDate
44 @Column(name = "performed_at", nullable = false, updatable = false)
45 private Instant performedAt;
46
47 protected AuditRecord() {
48 }
49
50 public AuditRecord(AuditAction action, String entityType, String entityId, Map<String, Object> details) {
51 this.action = action;
52 this.entityType = entityType;
53 this.entityId = entityId;
54 this.details = details;
55 }
56}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1An AuditorAware bean lets JPA resolve the current user automatically at persist time.
- 2Marking audit columns updatable=false enforces that records are append-only.
- 3Entity listeners plus @CreatedBy/@CreatedDate remove hand-written audit plumbing entirely.
Related explainers
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
java
public class TimedSocketReader { private static final int READ_TIMEOUT_MS = 5_000; private static final int CONNECT_TIMEOUT_MS = 3_000;
Reading a socket with connect and read timeouts
sockets
timeouts
io
Intermediate
8 steps
java
public final class EncodingDetector { public enum Encoding { UTF_8, UTF_16LE, UTF_16BE, UTF_32LE, UTF_32BE, ASCII, UNKNOWN
Detecting text encoding from raw bytes in Java
byte-manipulation
encoding-detection
bitwise-operations
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/automatic-audit-trails-with-spring-data-jpa-explained-java-c757/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.