Foundation · Build

Entity Modeling

@Entity, @Id, @Column, and @Version map a class to a table; identifier strategy and schema ownership are decisions, not defaults to accept.

Primary source: Jakarta Persistence API: Entity Mapping

20 min

Lesson 3 of 17

1d streak

The Architectural Question

ddl-auto=update asks Hibernate to alter a live schema from mapping differences, with dialect-dependent and incomplete behavior. It cannot express deliberate renames or data migrations, so production drift remains uncontrolled.

What this guide proves

  • @Entity
  • @Id
  • GenerationType

Entity identity guidelines: Primary keys and sequence strategies

JPA entities represent relational database rows with persistent identity. Choosing between IDENTITY (database auto-increment) and SEQUENCE has profound impacts on Hibernate batch inserting performance.

StrategyDB MechanismBatch Inserts Supported?When to Choose
GenerationType.IDENTITYPostgreSQL BIGSERIAL / MySQL AUTO_INCREMENTNO (Hibernate must execute INSERT immediately to obtain generated ID)Simple single-row writes, legacy schemas.
GenerationType.SEQUENCEDatabase Sequence with allocationSize (e.g. 50)YES (IDs fetched in blocks of 50, allowing JDBC batch inserts)High-throughput production applications (Recommended).
UUIDv7 (Ordered UUID)Time-ordered 128-bit UUID generated in applicationYES (No DB roundtrip needed for ID generation)Distributed systems, microservices, decoupled client generation.
Product.java
@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
    @SequenceGenerator(name = "product_seq", sequenceName = "product_id_seq", allocationSize = 50)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String sku;

    @Column(nullable = false, precision = 10, scale = 2)
    private BigDecimal price;

    @Version // Optimistic locking guard!
    private Long version;

    protected Product() {} // Required by Hibernate proxies

    public Product(String sku, BigDecimal price) {
        this.sku = Objects.requireNonNull(sku);
        this.price = Objects.requireNonNull(price);
    }
}

The equals and hashCode contract for entities

Architectural Boundary · Do Not Confuse

Concept A

Database Generated @Id in equals/hashCode

Concept B

Natural Business Key in equals/hashCode

Before an entity is saved, its generated @Id is null. If equals/hashCode rely on id, adding a new entity to a HashSet before persisting it and searching for it afterward will fail because the entity's hashCode mutates upon insert! Always base equals/hashCode on a unique natural business key (e.g. sku or email).

Break it deliberately: The mutating hashCode HashSet corruption bug

Using generated IDs in hashCode() causes entities to disappear inside Java Collections after saving:

HashSetCorruptionDemo.java
Set<Product> set = new HashSet<>();
Product product = new Product("SKU-99", new BigDecimal("49.99")); // id is null

set.add(product); // Placed into bucket based on hashCode(null) = 0!

productRepository.save(product); // DB generates id = 42L!
// Now product.hashCode() evaluates to 42, but the object is still in bucket 0!

boolean found = set.contains(product); // FALSE! Object is lost in the Set!

The Golden Rule

Never use Lombok @Data or generated database @Id in entity equals() and hashCode(). Base equality on unique business keys or return getClass().hashCode() for consistent bucket placement.

Check your understanding

Active Recall Checkpoint

Why can GenerationType.IDENTITY prevent Hibernate JDBC batch inserts for those inserts?

Hint: When must the INSERT statement execute?

Show answer & explanation

With identity generation, the database assigns the id during the INSERT, so Hibernate may need to execute that INSERT immediately before it can know the identifier. Sequence-based strategies can batch differently; verify the selected strategy and driver behavior.

Active Recall Checkpoint

Why does using generated @Id in hashCode() break HashSet collections?

Hint: Does hashCode change after persisting?

Show answer & explanation

The entity has a null ID before saving. After saving, the ID is assigned, changing the hashCode and preventing the HashSet from finding the entity in its original bucket.

Runtime Architecture & Flow

Structure under inspection

Entity Modeling

Part 1

@Entity

Part 2

@Id

Part 3

GenerationType

The connectors show containment and responsibility—not time or call order.

Synthesis & Production Verification

Production Acceptance Criteria

  • Map a Product with UUID id, unique SKU column, and @Version field
  • Predict the DDL difference between GenerationType.UUID and IDENTITY
  • Run the app against a Flyway-managed schema in validate mode

Architectural & Technical Review

Core Interview Questions

Q01What are the trade-offs of UUIDv4 versus BIGSERIAL primary keys?Show answer

BIGSERIAL provides sequential B-Tree index locality but leaks business volume; UUIDv4 allows client generation but causes B-Tree index fragmentation.

Q02Why should entity equals() and hashCode() avoid relying on database generated IDs?Show answer

An unpersisted entity has a null ID. Once persisted, its ID changes, corrupting HashSet lookups.

Q03Why should Lombok @Data be avoided on JPA entities?Show answer

@Data generates equals/hashCode methods across all fields, including lazy relationships, triggering accidental N+1 queries.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Using Java records for mutable JPA entities — Java records are immutable and cannot support the runtime proxying and dirty checking mechanisms required by Hibernate.
  • Pitfall: Exposing public setters on all entity fields — Indiscriminate setters bypass domain invariants; prefer meaningful business methods for state changes.

Primary Documentation & Specifications