Cheat SheetsHibernate & JPAAdvanced

Advanced — Cheat Sheet

Hibernate & JPA · 7 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Advanced
Hibernate & JPA7 topicsQuick revision reference
1

@Auditing with Spring Data

Enable @EnableJpaAuditing and annotate fields with @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy; an AuditorAware bean supplies the current user identity.

  • @EnableJpaAuditing must reference the AuditorAware bean by name via auditorAwareRef when multiple beans exist.
  • @EntityListeners(AuditingEntityListener.class) must be on the entity or its MappedSuperclass for fields to populate.
  • AuditorAware returns Optional.empty() for unauthenticated requests — @CreatedBy field will be left null.
  • @Column(updatable=false) on @CreatedDate and @CreatedBy prevents accidental overwrite on updates.
  • Hibernate Envers is the right tool for full change history; Spring Auditing only captures latest-modifier metadata.
  • For batch jobs or scheduled tasks without a SecurityContext, return "system" from AuditorAware as a fallback.
Java — AuditableEntity MappedSuperclass
// Enable auditing in any @Configuration class
@Configuration
@EnableJpaAuditing(auditorAwareRef = "currentUserAuditor")
public class JpaConfig {}

// Shared audit base entity
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class AuditableEntity {

    @CreatedDate
    @Column(updatable = false, nullable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updatedAt;

    @CreatedBy
    @Column(updatable = false, length = 100)
    private String createdBy;

    @LastModifiedBy
    @Column(length = 100)
    private String updatedBy;
}

// Domain entity inherits audit fields
@Entity
@Table(name = "orders")
public class Order extends AuditableEntity {
    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String status;
    private BigDecimal amount;
    // createdAt, updatedAt, createdBy, updatedBy inherited
}
2

Hibernate Validator (Bean Validation)

Hibernate Validator is the reference implementation of Jakarta Bean Validation; constraints like @NotNull, @Size, @Email, and @Pattern are enforced on persist and merge.

  • Hibernate Validator enforces constraints automatically before JPA persist/merge operations
  • @NotNull checks for null; @NotBlank also rejects blank strings — prefer @NotBlank for String
  • @Valid in Spring MVC triggers validation before the controller method; @Validated supports groups
  • MethodArgumentNotValidException carries all field errors — handle it to return structured JSON
  • Custom constraints: define annotation with @Constraint + implement ConstraintValidator<A,T>
  • @Valid cascades into @Embedded and nested @Valid-annotated objects
Java — Bean Validation constraints on entity
@Entity
public class User {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    private String name;

    @Email(message = "Invalid email format")
    @NotNull
    @Column(unique = true)
    private String email;

    @Pattern(regexp = "^\\+?[0-9]{10,15}$", message = "Invalid phone number")
    private String phone;

    @Min(value = 0, message = "Age cannot be negative")
    @Max(value = 150)
    private int age;

    @Valid                          // cascade validation into nested object
    @Embedded
    private Address address;
}
3

Schema Generation (hbm2ddl)

spring.jpa.hibernate.ddl-auto controls schema generation: validate (production), update (development), create-drop (tests); use Flyway or Liquibase for production migrations.

  • Never use ddl-auto=update or create-drop in production — use validate or none only.
  • Flyway (or Liquibase) version-controls schema changes, enables rollbacks, and prevents double-application of scripts.
  • Spring Boot auto-configures Flyway; add flyway-core to pom.xml and put scripts in db/migration.
  • Naming convention: V{version}__{description}.sql — double underscore, no spaces.
  • Use baseline-on-migrate=true when introducing Flyway to an existing database.
  • Testcontainers + Flyway in tests gives you a real DB migration smoke test on every build.
Properties — ddl-auto mode reference
# application.properties

# PRODUCTION: validate entity mappings against existing schema, fail fast if mismatch
spring.jpa.hibernate.ddl-auto=validate

# DEVELOPMENT: let Hibernate update schema (adds columns, creates tables)
# WARNING: never on production — update can drop NOT NULL or rename incorrectly
spring.jpa.hibernate.ddl-auto=update

# TESTING: drop and recreate schema before each test run
spring.jpa.hibernate.ddl-auto=create-drop

# CI/TESTING ALTERNATIVE: create schema fresh (no drop on close)
spring.jpa.hibernate.ddl-auto=create

# NONE: disable Hibernate DDL entirely (use with Flyway/Liquibase)
spring.jpa.hibernate.ddl-auto=none

# Show generated DDL (useful during development)
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Export Hibernate-generated DDL to file without executing
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=schema.sql
4

Custom AttributeConverter

AttributeConverter<X,Y> maps between a Java type and a DB column type; use for enums, JSON objects, encrypted values, or custom domain types not natively supported by JPA.

  • AttributeConverter<X,Y>: X = Java type, Y = DB column type (usually String or primitives)
  • autoApply=true applies the converter globally to all fields of that type — no @Convert needed
  • Converters run on every read/write — keep them stateless and fast (avoid heavy I/O)
  • Storing enums by code (not name) decouples code from DB; rename the enum freely without migration
  • @Convert(converter=…) on a field overrides or disables autoApply for that specific field
  • Spring-managed @Component converters can use @Autowired to inject services
Java — enum AttributeConverter with stable code
public enum OrderStatus {
    PENDING("P"), PLACED("PL"), SHIPPED("SH"), DELIVERED("DE"), CANCELLED("CA");

    private final String code;
    OrderStatus(String code) { this.code = code; }
    public String getCode() { return code; }

    public static OrderStatus fromCode(String code) {
        return Arrays.stream(values())
            .filter(s -> s.code.equals(code))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Unknown code: " + code));
    }
}

@Converter(autoApply = true)  // auto-applies to all OrderStatus fields in all entities
public class OrderStatusConverter implements AttributeConverter<OrderStatus, String> {

    @Override
    public String convertToDatabaseColumn(OrderStatus status) {
        return status == null ? null : status.getCode();
    }

    @Override
    public OrderStatus convertToEntityAttribute(String code) {
        return code == null ? null : OrderStatus.fromCode(code);
    }
}
5

Multi-Tenancy in Hibernate

Hibernate supports SCHEMA, DATABASE, and DISCRIMINATOR multi-tenancy strategies; the CurrentTenantIdentifierResolver and ConnectionProvider resolve the tenant per request.

  • SCHEMA: shared DB, separate schema per tenant — good isolation, moderate ops complexity.
  • DATABASE: separate DB instance per tenant — highest isolation, highest resource cost.
  • DISCRIMINATOR: single schema + tenant_id column — simplest ops, weakest isolation.
  • CurrentTenantIdentifierResolver resolves the tenant ID per request (e.g. from a header or JWT claim).
  • AbstractRoutingDataSource routes JDBC connections to the correct tenant data source.
  • @TenantId (Hibernate 6.1+) marks the discriminator field; Hibernate appends the filter automatically.
Java — TenantContext + CurrentTenantIdentifierResolver
// TenantContext — store/clear per request
public class TenantContext {
    private static final ThreadLocal<String> CURRENT = new ThreadLocal<>();
    public static void set(String tenantId)  { CURRENT.set(tenantId); }
    public static String get()               { return CURRENT.get(); }
    public static void clear()               { CURRENT.remove(); }
}

// Servlet filter (or Spring Security filter) sets the tenant
@Component
public class TenantFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        String tenant = ((HttpServletRequest) req).getHeader("X-Tenant-Id");
        TenantContext.set(tenant);
        try { chain.doFilter(req, res); }
        finally { TenantContext.clear(); }
    }
}

// CurrentTenantIdentifierResolver
@Component
public class TenantIdentifierResolver
        implements CurrentTenantIdentifierResolver<String> {
    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenant = TenantContext.get();
        return (tenant != null) ? tenant : "public";  // fallback to default schema
    }
    @Override
    public boolean validateExistingCurrentSessions() { return true; }
}
6

Hibernate Envers (Entity Auditing)

@Audited on an entity instructs Envers to record every change in a revision table; query historical data with AuditReader to retrieve entity state at a specific revision.

  • @Audited creates a mirror _aud table and REVINFO table automatically
  • @NotAudited on a field excludes it from the audit trail (e.g. large binary fields)
  • AuditReader.find(Class, id, revision) retrieves entity state at a specific point in time
  • RevisionType: ADD (insert), MOD (update), DEL (delete) — stored in each _aud row
  • Spring Data Envers RevisionRepository provides findRevisions() and findLastChangeRevision()
  • Envers adds write overhead — every persisted change requires an additional INSERT into the _aud table
Java — @Audited entity and generated audit tables
<!-- pom.xml — Envers is included in spring-boot-starter-data-jpa -->
<!-- For RevisionRepository, add spring-data-envers separately -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-envers</artifactId>
</dependency>

@Entity
@Audited              // audit all fields
public class Product {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private BigDecimal price;

    @NotAudited        // exclude this field from auditing
    private byte[] imageData;
}

// Envers generates:
// products_aud (id, rev, revtype, name, price)
// revinfo       (rev SERIAL PK, revtstmp BIGINT)
7

Hibernate Statistics & Logging

Enable hibernate.generate_statistics and show_sql to see query counts, cache hit ratios, and connection acquisition time — essential for identifying performance bottlenecks.

  • show-sql: true prints SQL to stdout — for production use logging.level.org.hibernate.SQL: DEBUG instead
  • hibernate.generate_statistics=true enables the Statistics API and Micrometer metric collection
  • getQueryExecutionCount() > expected per request is a reliable N+1 detector in integration tests and dev interceptors
  • getCollectionFetchCount() tracks lazy collection initialisations — high counts indicate missing JOIN FETCH or batch fetch
  • Spring Boot 3 auto-configures Micrometer Hibernate metrics when generate_statistics=true is set
  • Cache hit rate (L2 hits / (hits + misses)) < 80% for entities that should be cached indicates cache misconfiguration
YAML — SQL logging configuration for development and production
# application.yml — development SQL visibility
spring:
  jpa:
    show-sql: true                        # print SQL to stdout (dev only)
    properties:
      hibernate:
        format_sql: true                  # pretty-print SQL
        use_sql_comments: true            # add JPQL context as SQL comments
        generate_statistics: true         # enable statistics collection
        session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 50  # log slow queries

# Production-safe: route through SLF4J (respects log level config)
logging:
  level:
    org.hibernate.SQL: DEBUG              # SQL statements
    org.hibernate.orm.jdbc.bind: TRACE    # bind parameter values
    org.hibernate.stat: DEBUG             # statistics logging

# Example console output with show-sql + format_sql:
# /* select generatedAlias0 from Order as generatedAlias0 */
# select
#     o1_0.id, o1_0.customer_id, o1_0.total
# from
#     orders o1_0
# where
#     o1_0.customer_id=?
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/hibernate